initial release
This commit is contained in:
parent
6444e20214
commit
4b3c28c853
11
torchlight_changes_unloze/systemd/torchlight_restart.service
Normal file
11
torchlight_changes_unloze/systemd/torchlight_restart.service
Normal file
@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Restarting torch on ze every 30 minutes
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=gameservers
|
||||
WorkingDirectory=/home/gameservers/css_ze/torchlight3
|
||||
ExecStart=/home/gameservers/css_ze/torchlight3/_start.sh
|
||||
Restart=always
|
||||
RuntimeMaxSec=1800
|
||||
|
@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Restarting torch on mg every 30 minutes
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=gameservers
|
||||
WorkingDirectory=/home/gameservers/css_ze/torchlight3
|
||||
ExecStart=/home/gameservers/css_ze/torchlight3/_start_mg.sh
|
||||
Restart=always
|
||||
RuntimeMaxSec=1800
|
||||
|
27
torchlight_changes_unloze/torchlight3/README.md
Executable file
27
torchlight_changes_unloze/torchlight3/README.md
Executable file
@ -0,0 +1,27 @@
|
||||
# Torchlight3
|
||||
|
||||
## 0. Requirements
|
||||
* Python3.6
|
||||
* FFMPEG
|
||||
* youtube-dl
|
||||
* On game server:
|
||||
* custom sourcemod
|
||||
* sm-ext-AsyncSocket extension
|
||||
* smjansson extension
|
||||
* SMJSONAPI plugin
|
||||
* sm-ext-Voice extension
|
||||
|
||||
## 1. Install
|
||||
* Install python3 and python-virtualenv
|
||||
* Create a virtualenv: `virtualenv venv`
|
||||
* Activate the virtualenv: `. venv/bin/activate`
|
||||
* Install all dependencies: `pip install -r requirements.txt`
|
||||
|
||||
## 2. Usage
|
||||
Set up game server stuff.
|
||||
Adapt config.json.
|
||||
|
||||
##### Make sure you are in the virtualenv! (`. venv/bin/activate`)
|
||||
Run: `python main.py`
|
||||
|
||||
Glacius was here. xd
|
43
torchlight_changes_unloze/torchlight3/Torchlight/AccessManager.py
Executable file
43
torchlight_changes_unloze/torchlight3/Torchlight/AccessManager.py
Executable file
@ -0,0 +1,43 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
|
||||
class AccessManager():
|
||||
ACCESS_FILE = "access.json"
|
||||
def __init__(self):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.AccessDict = OrderedDict()
|
||||
|
||||
def Load(self):
|
||||
self.Logger.info("Loading access from {0}".format(self.ACCESS_FILE))
|
||||
|
||||
with open(self.ACCESS_FILE, "r") as fp:
|
||||
self.AccessDict = json.load(fp, object_pairs_hook = OrderedDict)
|
||||
|
||||
def Save(self):
|
||||
self.Logger.info("Saving access to {0}".format(self.ACCESS_FILE))
|
||||
|
||||
self.AccessDict = OrderedDict(
|
||||
sorted(self.AccessDict.items(), key = lambda x: x[1]["level"], reverse = True))
|
||||
|
||||
with open(self.ACCESS_FILE, "w") as fp:
|
||||
json.dump(self.AccessDict, fp, indent = '\t')
|
||||
|
||||
def __len__(self):
|
||||
return len(self.AccessDict)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in self.AccessDict:
|
||||
return self.AccessDict[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.AccessDict[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
if key in self.AccessDict:
|
||||
del self.AccessDict[key]
|
||||
|
||||
def __iter__(self):
|
||||
return self.AccessDict.items().__iter__()
|
96
torchlight_changes_unloze/torchlight3/Torchlight/AsyncClient.py
Executable file
96
torchlight_changes_unloze/torchlight3/Torchlight/AsyncClient.py
Executable file
@ -0,0 +1,96 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import asyncio
|
||||
import logging
|
||||
import json
|
||||
|
||||
class ClientProtocol(asyncio.Protocol):
|
||||
def __init__(self, loop, master):
|
||||
self.Loop = loop
|
||||
self.Master = master
|
||||
self.Transport = None
|
||||
self.Buffer = bytearray()
|
||||
|
||||
def connection_made(self, transport):
|
||||
self.Transport = transport
|
||||
|
||||
def data_received(self, data):
|
||||
self.Buffer += data
|
||||
|
||||
chunks = self.Buffer.split(b'\0')
|
||||
if data[-1] == b'\0':
|
||||
chunks = chunks[:-1]
|
||||
self.Buffer = bytearray()
|
||||
else:
|
||||
self.Buffer = bytearray(chunks[-1])
|
||||
chunks = chunks[:-1]
|
||||
|
||||
for chunk in chunks:
|
||||
self.Master.OnReceive(chunk)
|
||||
|
||||
def connection_lost(self, exc):
|
||||
self.Transport.close()
|
||||
self.Transport = None
|
||||
self.Master.OnDisconnect(exc)
|
||||
|
||||
def Send(self, data):
|
||||
if self.Transport:
|
||||
self.Transport.write(data)
|
||||
|
||||
class AsyncClient():
|
||||
def __init__(self, loop, host, port, master):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Loop = loop
|
||||
self.Host = host
|
||||
self.Port = port
|
||||
self.Master = master
|
||||
|
||||
self.Protocol = None
|
||||
self.SendLock = asyncio.Lock()
|
||||
self.RecvFuture = None
|
||||
|
||||
async def Connect(self):
|
||||
while True:
|
||||
self.Logger.warn("Reconnecting...")
|
||||
try:
|
||||
_, self.Protocol = await self.Loop.create_connection(lambda: ClientProtocol(self.Loop, self), host = self.Host, port = self.Port)
|
||||
break
|
||||
except:
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
def OnReceive(self, data):
|
||||
Obj = json.loads(data)
|
||||
|
||||
if "method" in Obj and Obj["method"] == "publish":
|
||||
self.Master.OnPublish(Obj)
|
||||
else:
|
||||
if self.RecvFuture:
|
||||
self.RecvFuture.set_result(Obj)
|
||||
|
||||
def OnDisconnect(self, exc):
|
||||
self.Protocol = None
|
||||
if self.RecvFuture:
|
||||
self.RecvFuture.cancel()
|
||||
self.Master.OnDisconnect(exc)
|
||||
|
||||
async def Send(self, obj):
|
||||
if not self.Protocol:
|
||||
return None
|
||||
|
||||
Data = json.dumps(obj, ensure_ascii = False, separators = (',', ':')).encode("UTF-8")
|
||||
|
||||
with (await self.SendLock):
|
||||
if not self.Protocol:
|
||||
return None
|
||||
|
||||
self.RecvFuture = asyncio.Future()
|
||||
self.Protocol.Send(Data)
|
||||
await self.RecvFuture
|
||||
|
||||
if self.RecvFuture.done():
|
||||
Obj = self.RecvFuture.result()
|
||||
else:
|
||||
Obj = None
|
||||
|
||||
self.RecvFuture = None
|
||||
return Obj
|
351
torchlight_changes_unloze/torchlight3/Torchlight/AudioManager.py
Executable file
351
torchlight_changes_unloze/torchlight3/Torchlight/AudioManager.py
Executable file
@ -0,0 +1,351 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import sys
|
||||
import io
|
||||
import math
|
||||
from .FFmpegAudioPlayer import FFmpegAudioPlayerFactory
|
||||
|
||||
class AudioPlayerFactory():
|
||||
AUDIOPLAYER_FFMPEG = 1
|
||||
|
||||
def __init__(self, master):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Master = master
|
||||
self.Torchlight = self.Master.Torchlight
|
||||
|
||||
self.FFmpegAudioPlayerFactory = FFmpegAudioPlayerFactory(self)
|
||||
|
||||
def __del__(self):
|
||||
self.Logger.info("~AudioPlayerFactory()")
|
||||
|
||||
def NewPlayer(self, _type):
|
||||
if _type == self.AUDIOPLAYER_FFMPEG:
|
||||
return self.FFmpegAudioPlayerFactory.NewPlayer()
|
||||
|
||||
|
||||
class AntiSpam():
|
||||
def __init__(self, master):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Master = master
|
||||
self.Torchlight = self.Master.Torchlight
|
||||
|
||||
self.LastClips = dict()
|
||||
self.DisabledTime = None
|
||||
self.SaidHint = False
|
||||
|
||||
def CheckAntiSpam(self, player):
|
||||
if self.DisabledTime and self.DisabledTime > self.Torchlight().Master.Loop.time() and \
|
||||
not (player.Access and player.Access["level"] >= self.Torchlight().Config["AntiSpam"]["ImmunityLevel"]):
|
||||
|
||||
self.Torchlight().SayPrivate(player, "Torchlight is currently on cooldown! ({0} seconds left)".format(
|
||||
math.ceil(self.DisabledTime - self.Torchlight().Master.Loop.time())))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def SpamCheck(self, Delta):
|
||||
Now = self.Torchlight().Master.Loop.time()
|
||||
Duration = 0.0
|
||||
|
||||
for Key, Clip in list(self.LastClips.items()):
|
||||
if not Clip["timestamp"]:
|
||||
continue
|
||||
|
||||
if Clip["timestamp"] + Clip["duration"] + self.Torchlight().Config["AntiSpam"]["MaxUsageSpan"] < Now:
|
||||
if not Clip["active"]:
|
||||
del self.LastClips[Key]
|
||||
continue
|
||||
|
||||
Duration += Clip["duration"]
|
||||
|
||||
if Duration > self.Torchlight().Config["AntiSpam"]["MaxUsageTime"]:
|
||||
self.DisabledTime = self.Torchlight().Master.Loop.time() + self.Torchlight().Config["AntiSpam"]["PunishDelay"]
|
||||
self.Torchlight().SayChat("Blocked voice commands for the next {0} seconds. Used {1} seconds within {2} seconds.".format(
|
||||
self.Torchlight().Config["AntiSpam"]["PunishDelay"], self.Torchlight().Config["AntiSpam"]["MaxUsageTime"], self.Torchlight().Config["AntiSpam"]["MaxUsageSpan"]))
|
||||
|
||||
# Make a copy of the list since AudioClip.Stop() will change the list
|
||||
for AudioClip in self.Master.AudioClips[:]:
|
||||
if AudioClip.Level < self.Torchlight().Config["AntiSpam"]["ImmunityLevel"]:
|
||||
AudioClip.Stop()
|
||||
|
||||
self.LastClips.clear()
|
||||
|
||||
def OnPlay(self, clip):
|
||||
Now = self.Torchlight().Master.Loop.time()
|
||||
self.LastClips[hash(clip)] = dict({"timestamp": Now, "duration": 0.0, "dominant": False, "active": True})
|
||||
|
||||
HasDominant = False
|
||||
for Key, Clip in self.LastClips.items():
|
||||
if Clip["dominant"]:
|
||||
HasDominant = True
|
||||
break
|
||||
|
||||
self.LastClips[hash(clip)]["dominant"] = not HasDominant
|
||||
|
||||
def OnStop(self, clip):
|
||||
if hash(clip) not in self.LastClips:
|
||||
return
|
||||
|
||||
self.LastClips[hash(clip)]["active"] = False
|
||||
|
||||
if self.LastClips[hash(clip)]["dominant"]:
|
||||
for Key, Clip in self.LastClips.items():
|
||||
if Clip["active"]:
|
||||
Clip["dominant"] = True
|
||||
break
|
||||
|
||||
self.LastClips[hash(clip)]["dominant"] = False
|
||||
|
||||
def OnUpdate(self, clip, old_position, new_position):
|
||||
Delta = new_position - old_position
|
||||
Clip = self.LastClips[hash(clip)]
|
||||
|
||||
if not Clip["dominant"]:
|
||||
return
|
||||
|
||||
Clip["duration"] += Delta
|
||||
self.SpamCheck(Delta)
|
||||
|
||||
|
||||
class Advertiser():
|
||||
def __init__(self, master):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Master = master
|
||||
self.Torchlight = self.Master.Torchlight
|
||||
|
||||
self.LastClips = dict()
|
||||
self.AdStop = 0
|
||||
self.NextAdStop = 0
|
||||
|
||||
def Think(self, Delta):
|
||||
Now = self.Torchlight().Master.Loop.time()
|
||||
Duration = 0.0
|
||||
|
||||
for Key, Clip in list(self.LastClips.items()):
|
||||
if not Clip["timestamp"]:
|
||||
continue
|
||||
|
||||
if Clip["timestamp"] + Clip["duration"] + self.Torchlight().Config["Advertiser"]["MaxSpan"] < Now:
|
||||
if not Clip["active"]:
|
||||
del self.LastClips[Key]
|
||||
continue
|
||||
|
||||
Duration += Clip["duration"]
|
||||
|
||||
self.NextAdStop -= Delta
|
||||
CeilDur = math.ceil(Duration)
|
||||
if CeilDur > self.AdStop and self.NextAdStop <= 0 and CeilDur % self.Torchlight().Config["Advertiser"]["AdStop"] == 0:
|
||||
self.Torchlight().SayChat("Hint: Type \x07FF0000!stop(ze) !pls(mg)\x01 to stop all currently playing sounds.")
|
||||
self.AdStop = CeilDur
|
||||
self.NextAdStop = 0
|
||||
elif CeilDur < self.AdStop:
|
||||
self.AdStop = 0
|
||||
self.NextAdStop = self.Torchlight().Config["Advertiser"]["AdStop"] / 2
|
||||
|
||||
def OnPlay(self, clip):
|
||||
Now = self.Torchlight().Master.Loop.time()
|
||||
self.LastClips[hash(clip)] = dict({"timestamp": Now, "duration": 0.0, "dominant": False, "active": True})
|
||||
|
||||
HasDominant = False
|
||||
for Key, Clip in self.LastClips.items():
|
||||
if Clip["dominant"]:
|
||||
HasDominant = True
|
||||
break
|
||||
|
||||
self.LastClips[hash(clip)]["dominant"] = not HasDominant
|
||||
|
||||
def OnStop(self, clip):
|
||||
if hash(clip) not in self.LastClips:
|
||||
return
|
||||
|
||||
self.LastClips[hash(clip)]["active"] = False
|
||||
|
||||
if self.LastClips[hash(clip)]["dominant"]:
|
||||
for Key, Clip in self.LastClips.items():
|
||||
if Clip["active"]:
|
||||
Clip["dominant"] = True
|
||||
break
|
||||
|
||||
self.LastClips[hash(clip)]["dominant"] = False
|
||||
|
||||
def OnUpdate(self, clip, old_position, new_position):
|
||||
Delta = new_position - old_position
|
||||
Clip = self.LastClips[hash(clip)]
|
||||
|
||||
if not Clip["dominant"]:
|
||||
return
|
||||
|
||||
Clip["duration"] += Delta
|
||||
self.Think(Delta)
|
||||
|
||||
|
||||
class AudioManager():
|
||||
def __init__(self, torchlight):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Torchlight = torchlight
|
||||
self.AntiSpam = AntiSpam(self)
|
||||
self.Advertiser = Advertiser(self)
|
||||
self.AudioPlayerFactory = AudioPlayerFactory(self)
|
||||
self.AudioClips = []
|
||||
|
||||
def __del__(self):
|
||||
self.Logger.info("~AudioManager()")
|
||||
|
||||
def CheckLimits(self, player):
|
||||
Level = 0
|
||||
if player.Access:
|
||||
Level = player.Access["level"]
|
||||
|
||||
if str(Level) in self.Torchlight().Config["AudioLimits"]:
|
||||
if self.Torchlight().Config["AudioLimits"][str(Level)]["Uses"] >= 0 and \
|
||||
player.Storage["Audio"]["Uses"] >= self.Torchlight().Config["AudioLimits"][str(Level)]["Uses"]:
|
||||
|
||||
self.Torchlight().SayPrivate(player, "You have used up all of your free uses! ({0} uses)".format(
|
||||
self.Torchlight().Config["AudioLimits"][str(Level)]["Uses"]))
|
||||
return False
|
||||
|
||||
if player.Storage["Audio"]["TimeUsed"] >= self.Torchlight().Config["AudioLimits"][str(Level)]["TotalTime"]:
|
||||
self.Torchlight().SayPrivate(player, "You have used up all of your free time! ({0} seconds)".format(
|
||||
self.Torchlight().Config["AudioLimits"][str(Level)]["TotalTime"]))
|
||||
return False
|
||||
|
||||
TimeElapsed = self.Torchlight().Master.Loop.time() - player.Storage["Audio"]["LastUse"]
|
||||
UseDelay = player.Storage["Audio"]["LastUseLength"] * self.Torchlight().Config["AudioLimits"][str(Level)]["DelayFactor"]
|
||||
|
||||
if TimeElapsed < UseDelay:
|
||||
self.Torchlight().SayPrivate(player, "You are currently on cooldown! ({0} seconds left)".format(
|
||||
round(UseDelay - TimeElapsed)))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def Stop(self, player, extra):
|
||||
Level = 0
|
||||
if player.Access:
|
||||
Level = player.Access["level"]
|
||||
|
||||
for AudioClip in self.AudioClips[:]:
|
||||
if extra and not extra.lower() in AudioClip.Player.Name.lower():
|
||||
continue
|
||||
|
||||
if not Level or (Level < AudioClip.Level and Level < self.Torchlight().Config["AntiSpam"]["StopLevel"]):
|
||||
AudioClip.Stops.add(player.UserID)
|
||||
|
||||
if len(AudioClip.Stops) >= 3:
|
||||
AudioClip.Stop()
|
||||
self.Torchlight().SayPrivate(AudioClip.Player, "Your audio clip was stopped.")
|
||||
if player != AudioClip.Player:
|
||||
self.Torchlight().SayPrivate(player, "Stopped \"{0}\"({1}) audio clip.".format(AudioClip.Player.Name, AudioClip.Player.UserID))
|
||||
else:
|
||||
self.Torchlight().SayPrivate(player, "This audio clip needs {0} more !stop's.".format(3 - len(AudioClip.Stops)))
|
||||
else:
|
||||
AudioClip.Stop()
|
||||
self.Torchlight().SayPrivate(AudioClip.Player, "Your audio clip was stopped.")
|
||||
if player != AudioClip.Player:
|
||||
self.Torchlight().SayPrivate(player, "Stopped \"{0}\"({1}) audio clip.".format(AudioClip.Player.Name, AudioClip.Player.UserID))
|
||||
|
||||
def AudioClip(self, player, uri, _type = AudioPlayerFactory.AUDIOPLAYER_FFMPEG):
|
||||
Level = 0
|
||||
if player.Access:
|
||||
Level = player.Access["level"]
|
||||
|
||||
if self.Torchlight().Disabled and self.Torchlight().Disabled > Level:
|
||||
self.Torchlight().SayPrivate(player, "Torchlight is currently disabled!")
|
||||
return None
|
||||
|
||||
if not self.AntiSpam.CheckAntiSpam(player):
|
||||
return None
|
||||
|
||||
if not self.CheckLimits(player):
|
||||
return None
|
||||
|
||||
Clip = AudioClip(self, player, uri, _type)
|
||||
self.AudioClips.append(Clip)
|
||||
|
||||
if not player.Access or player.Access["level"] < self.Torchlight().Config["AntiSpam"]["ImmunityLevel"]:
|
||||
Clip.AudioPlayer.AddCallback("Play", lambda *args: self.AntiSpam.OnPlay(Clip, *args))
|
||||
Clip.AudioPlayer.AddCallback("Stop", lambda *args: self.AntiSpam.OnStop(Clip, *args))
|
||||
Clip.AudioPlayer.AddCallback("Update", lambda *args: self.AntiSpam.OnUpdate(Clip, *args))
|
||||
|
||||
Clip.AudioPlayer.AddCallback("Play", lambda *args: self.Advertiser.OnPlay(Clip, *args))
|
||||
Clip.AudioPlayer.AddCallback("Stop", lambda *args: self.Advertiser.OnStop(Clip, *args))
|
||||
Clip.AudioPlayer.AddCallback("Update", lambda *args: self.Advertiser.OnUpdate(Clip, *args))
|
||||
|
||||
return Clip
|
||||
|
||||
def OnDisconnect(self, player):
|
||||
for AudioClip in self.AudioClips[:]:
|
||||
if AudioClip.Player == player:
|
||||
AudioClip.Stop()
|
||||
|
||||
|
||||
class AudioClip():
|
||||
def __init__(self, master, player, uri, _type):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Master = master
|
||||
self.Torchlight = self.Master.Torchlight
|
||||
self.Player = player
|
||||
self.Type = _type
|
||||
self.URI = uri
|
||||
self.LastPosition = None
|
||||
self.Stops = set()
|
||||
|
||||
self.Level = 0
|
||||
if self.Player.Access:
|
||||
self.Level = self.Player.Access["level"]
|
||||
|
||||
self.AudioPlayer = self.Master.AudioPlayerFactory.NewPlayer(self.Type)
|
||||
self.AudioPlayer.AddCallback("Play", self.OnPlay)
|
||||
self.AudioPlayer.AddCallback("Stop", self.OnStop)
|
||||
self.AudioPlayer.AddCallback("Update", self.OnUpdate)
|
||||
|
||||
def __del__(self):
|
||||
self.Logger.info("~AudioClip()")
|
||||
|
||||
def Play(self, seconds = None, *args):
|
||||
return self.AudioPlayer.PlayURI(self.URI, seconds, *args)
|
||||
|
||||
def Stop(self):
|
||||
return self.AudioPlayer.Stop()
|
||||
|
||||
def OnPlay(self):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + self.URI)
|
||||
|
||||
self.Player.Storage["Audio"]["Uses"] += 1
|
||||
self.Player.Storage["Audio"]["LastUse"] = self.Torchlight().Master.Loop.time()
|
||||
self.Player.Storage["Audio"]["LastUseLength"] = 0.0
|
||||
|
||||
def OnStop(self):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + self.URI)
|
||||
self.Master.AudioClips.remove(self)
|
||||
|
||||
if self.AudioPlayer.Playing:
|
||||
Delta = self.AudioPlayer.Position - self.LastPosition
|
||||
self.Player.Storage["Audio"]["TimeUsed"] += Delta
|
||||
self.Player.Storage["Audio"]["LastUseLength"] += Delta
|
||||
|
||||
if str(self.Level) in self.Torchlight().Config["AudioLimits"]:
|
||||
if self.Player.Storage:
|
||||
if self.Player.Storage["Audio"]["TimeUsed"] >= self.Torchlight().Config["AudioLimits"][str(self.Level)]["TotalTime"]:
|
||||
self.Torchlight().SayPrivate(self.Player, "You have used up all of your free time! ({0} seconds)".format(
|
||||
self.Torchlight().Config["AudioLimits"][str(self.Level)]["TotalTime"]))
|
||||
elif self.Player.Storage["Audio"]["LastUseLength"] >= self.Torchlight().Config["AudioLimits"][str(self.Level)]["MaxLength"]:
|
||||
self.Torchlight().SayPrivate(self.Player, "Your audio clip exceeded the maximum length! ({0} seconds)".format(
|
||||
self.Torchlight().Config["AudioLimits"][str(self.Level)]["MaxLength"]))
|
||||
|
||||
del self.AudioPlayer
|
||||
|
||||
def OnUpdate(self, old_position, new_position):
|
||||
Delta = new_position - old_position
|
||||
self.LastPosition = new_position
|
||||
|
||||
self.Player.Storage["Audio"]["TimeUsed"] += Delta
|
||||
self.Player.Storage["Audio"]["LastUseLength"] += Delta
|
||||
|
||||
if not str(self.Level) in self.Torchlight().Config["AudioLimits"]:
|
||||
return
|
||||
|
||||
if (self.Player.Storage["Audio"]["TimeUsed"] >= self.Torchlight().Config["AudioLimits"][str(self.Level)]["TotalTime"] or
|
||||
self.Player.Storage["Audio"]["LastUseLength"] >= self.Torchlight().Config["AudioLimits"][str(self.Level)]["MaxLength"]):
|
||||
self.Stop()
|
115
torchlight_changes_unloze/torchlight3/Torchlight/CommandHandler.py
Executable file
115
torchlight_changes_unloze/torchlight3/Torchlight/CommandHandler.py
Executable file
@ -0,0 +1,115 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import asyncio
|
||||
import sys
|
||||
import re
|
||||
import traceback
|
||||
import math
|
||||
from importlib import reload
|
||||
from . import Commands
|
||||
|
||||
class CommandHandler():
|
||||
def __init__(self, Torchlight):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Torchlight = Torchlight
|
||||
self.Commands = []
|
||||
self.NeedsReload = False
|
||||
|
||||
def Setup(self):
|
||||
Counter = len(self.Commands)
|
||||
self.Commands.clear()
|
||||
if Counter:
|
||||
self.Logger.info(sys._getframe().f_code.co_name + " Unloaded {0} commands!".format(Counter))
|
||||
|
||||
Counter = 0
|
||||
for subklass in sorted(Commands.BaseCommand.__subclasses__(), key = lambda x: x.Order, reverse = True):
|
||||
try:
|
||||
Command = subklass(self.Torchlight)
|
||||
if hasattr(Command, "_setup"):
|
||||
Command._setup()
|
||||
except Exception as e:
|
||||
self.Logger.error(traceback.format_exc())
|
||||
else:
|
||||
self.Commands.append(Command)
|
||||
Counter += 1
|
||||
|
||||
self.Logger.info(sys._getframe().f_code.co_name + " Loaded {0} commands!".format(Counter))
|
||||
|
||||
def Reload(self):
|
||||
try:
|
||||
reload(Commands)
|
||||
except Exception as e:
|
||||
self.Logger.error(traceback.format_exc())
|
||||
else:
|
||||
self.Setup()
|
||||
|
||||
async def HandleCommand(self, line, player):
|
||||
Message = line.split(sep = ' ', maxsplit = 1)
|
||||
if len(Message) < 2:
|
||||
Message.append("")
|
||||
Message[1] = Message[1].strip()
|
||||
|
||||
if Message[1] and self.Torchlight().LastUrl:
|
||||
Message[1] = Message[1].replace("!last", self.Torchlight().LastUrl)
|
||||
line = Message[0] + ' ' + Message[1]
|
||||
|
||||
Level = 0
|
||||
if player.Access:
|
||||
Level = player.Access["level"]
|
||||
|
||||
RetMessage = None
|
||||
Ret = None
|
||||
for Command in self.Commands:
|
||||
for Trigger in Command.Triggers:
|
||||
Match = False
|
||||
RMatch = None
|
||||
if isinstance(Trigger, tuple):
|
||||
if Message[0].lower().startswith(Trigger[0], 0, Trigger[1]):
|
||||
Match = True
|
||||
elif isinstance(Trigger, str):
|
||||
if Message[0].lower() == Trigger.lower():
|
||||
Match = True
|
||||
else: # compiled regex
|
||||
RMatch = Trigger.search(line)
|
||||
if RMatch:
|
||||
Match = True
|
||||
|
||||
if not Match:
|
||||
continue
|
||||
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + " \"{0}\" Match -> {1} | {2}".format(player.Name, Command.__class__.__name__, Trigger))
|
||||
|
||||
if Level < Command.Level:
|
||||
RetMessage = "You do not have access to this command! (You: {0} | Required: {1})".format(Level, Command.Level)
|
||||
continue
|
||||
|
||||
try:
|
||||
if RMatch:
|
||||
Ret = await Command._rfunc(line, RMatch, player)
|
||||
else:
|
||||
Ret = await Command._func(Message, player)
|
||||
except Exception as e:
|
||||
self.Logger.error(traceback.format_exc())
|
||||
self.Torchlight().SayChat("Error: {0}".format(str(e)))
|
||||
|
||||
RetMessage = None
|
||||
|
||||
if isinstance(Ret, str):
|
||||
Message = Ret.split(sep = ' ', maxsplit = 1)
|
||||
Ret = None
|
||||
|
||||
if Ret != None and Ret > 0:
|
||||
break
|
||||
|
||||
if Ret != None and Ret >= 0:
|
||||
break
|
||||
|
||||
if RetMessage:
|
||||
self.Torchlight().SayPrivate(player, RetMessage)
|
||||
|
||||
if self.NeedsReload:
|
||||
self.NeedsReload = False
|
||||
self.Reload()
|
||||
|
||||
return Ret
|
116
torchlight_changes_unloze/torchlight3/Torchlight/CommandHandlermg.py
Executable file
116
torchlight_changes_unloze/torchlight3/Torchlight/CommandHandlermg.py
Executable file
@ -0,0 +1,116 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import asyncio
|
||||
import sys
|
||||
import re
|
||||
import traceback
|
||||
import math
|
||||
from importlib import reload
|
||||
from . import Commandsmg
|
||||
|
||||
class CommandHandlermg():
|
||||
def __init__(self, Torchlight):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Torchlight = Torchlight
|
||||
self.Commands = []
|
||||
self.NeedsReload = False
|
||||
|
||||
def Setup(self):
|
||||
Counter = len(self.Commands)
|
||||
self.Commands.clear()
|
||||
if Counter:
|
||||
self.Logger.info(sys._getframe().f_code.co_name + " Unloaded {0} commands!".format(Counter))
|
||||
|
||||
Counter = 0
|
||||
for subklass in sorted(Commandsmg.BaseCommand.__subclasses__(), key = lambda x: x.Order, reverse = True):
|
||||
try:
|
||||
Command = subklass(self.Torchlight)
|
||||
if hasattr(Command, "_setup"):
|
||||
Command._setup()
|
||||
except Exception as e:
|
||||
self.Logger.error(traceback.format_exc())
|
||||
else:
|
||||
self.Commands.append(Command)
|
||||
Counter += 1
|
||||
|
||||
self.Logger.info(sys._getframe().f_code.co_name + " Loaded {0} commands!".format(Counter))
|
||||
|
||||
def Reload(self):
|
||||
try:
|
||||
reload(Commandsmg)
|
||||
except Exception as e:
|
||||
self.Logger.error(traceback.format_exc())
|
||||
else:
|
||||
self.Setup()
|
||||
|
||||
async def HandleCommand(self, line, player):
|
||||
Message = line.split(sep = ' ', maxsplit = 1)
|
||||
if len(Message) < 2:
|
||||
Message.append("")
|
||||
Message[1] = Message[1].strip()
|
||||
|
||||
if Message[1] and self.Torchlight().LastUrl:
|
||||
Message[1] = Message[1].replace("!last", self.Torchlight().LastUrl)
|
||||
line = Message[0] + ' ' + Message[1]
|
||||
|
||||
Level = 0
|
||||
if player.Access:
|
||||
Level = player.Access["level"]
|
||||
|
||||
RetMessage = None
|
||||
Ret = None
|
||||
for Command in self.Commands:
|
||||
for Trigger in Command.Triggers:
|
||||
Match = False
|
||||
RMatch = None
|
||||
if isinstance(Trigger, tuple):
|
||||
if Message[0].lower().startswith(Trigger[0], 0, Trigger[1]):
|
||||
Match = True
|
||||
elif isinstance(Trigger, str):
|
||||
if Message[0].lower() == Trigger.lower():
|
||||
Match = True
|
||||
else: # compiled regex
|
||||
RMatch = Trigger.search(line)
|
||||
if RMatch:
|
||||
Match = True
|
||||
|
||||
if not Match:
|
||||
continue
|
||||
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + " \"{0}\" Match -> {1} | {2}".format(player.Name, Command.__class__.__name__, Trigger))
|
||||
|
||||
if Level < Command.Level:
|
||||
RetMessage = "You do not have access to this command! (You: {0} | Required: {1})".format(Level, Command.Level)
|
||||
continue
|
||||
|
||||
try:
|
||||
if RMatch:
|
||||
Ret = await Command._rfunc(line, RMatch, player)
|
||||
else:
|
||||
Ret = await Command._func(Message, player)
|
||||
except Exception as e:
|
||||
self.Logger.error(traceback.format_exc())
|
||||
self.Torchlight().SayChat("Error: {0}".format(str(e)))
|
||||
|
||||
RetMessage = None
|
||||
|
||||
if isinstance(Ret, str):
|
||||
Message = Ret.split(sep = ' ', maxsplit = 1)
|
||||
Ret = None
|
||||
|
||||
if Ret != None and Ret > 0:
|
||||
break
|
||||
|
||||
if Ret != None and Ret >= 0:
|
||||
break
|
||||
|
||||
if RetMessage:
|
||||
self.Torchlight().SayPrivate(player, RetMessage)
|
||||
|
||||
if self.NeedsReload:
|
||||
self.NeedsReload = False
|
||||
self.Reload()
|
||||
|
||||
return Ret
|
||||
|
928
torchlight_changes_unloze/torchlight3/Torchlight/Commands.py
Executable file
928
torchlight_changes_unloze/torchlight3/Torchlight/Commands.py
Executable file
@ -0,0 +1,928 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import math
|
||||
from .Utils import Utils, DataHolder
|
||||
import traceback
|
||||
|
||||
class BaseCommand():
|
||||
Order = 0
|
||||
def __init__(self, torchlight):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Torchlight = torchlight
|
||||
self.Triggers = []
|
||||
self.Level = 0
|
||||
|
||||
def check_chat_cooldown(self, player):
|
||||
if player.ChatCooldown > self.Torchlight().Master.Loop.time():
|
||||
cooldown = player.ChatCooldown - self.Torchlight().Master.Loop.time()
|
||||
self.Torchlight().SayPrivate(player, "You're on cooldown for the next {0:.1f} seconds.".format(cooldown))
|
||||
return True
|
||||
|
||||
def check_disabled(self, player):
|
||||
Level = 0
|
||||
if player.Access:
|
||||
Level = player.Access["level"]
|
||||
|
||||
Disabled = self.Torchlight().Disabled
|
||||
if Disabled and (Disabled > Level or Disabled == Level and Level < self.Torchlight().Config["AntiSpam"]["ImmunityLevel"]):
|
||||
self.Torchlight().SayPrivate(player, "Torchlight is currently disabled!")
|
||||
return True
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name)
|
||||
|
||||
|
||||
class URLFilter(BaseCommand):
|
||||
Order = 1
|
||||
import re
|
||||
import aiohttp
|
||||
import magic
|
||||
import datetime
|
||||
import json
|
||||
import io
|
||||
from bs4 import BeautifulSoup
|
||||
from PIL import Image
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = [self.re.compile(r'''(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))''', self.re.IGNORECASE)]
|
||||
self.Level = 10
|
||||
self.re_youtube = self.re.compile(r'.*?(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)([a-zA-Z0-9_-]{6,11}).*?')
|
||||
|
||||
async def URLInfo(self, url, yt = False):
|
||||
Text = None
|
||||
Info = None
|
||||
match = self.re_youtube.search(url)
|
||||
if match or yt:
|
||||
Temp = DataHolder()
|
||||
Time = None
|
||||
|
||||
if Temp(url.find("&t=")) != -1 or Temp(url.find("?t=")) != -1 or Temp(url.find("#t=")) != -1:
|
||||
TimeStr = url[Temp.value + 3:].split('&')[0].split('?')[0].split('#')[0]
|
||||
if TimeStr:
|
||||
Time = Utils.ParseTime(TimeStr)
|
||||
|
||||
Proc = await asyncio.create_subprocess_exec("youtube-dl", "--dump-json", "-g", url,
|
||||
stdout = asyncio.subprocess.PIPE)
|
||||
Out, _ = await Proc.communicate()
|
||||
|
||||
parts = Out.split(b'\n')
|
||||
parts.pop() # trailing new line
|
||||
|
||||
Info = parts.pop()
|
||||
url = parts.pop()
|
||||
|
||||
url = url.strip().decode("ascii")
|
||||
Info = self.json.loads(Info)
|
||||
|
||||
if Info["extractor_key"] == "Youtube":
|
||||
self.Torchlight().SayChat("\x07E52D27[YouTube]\x01 {0} | {1} | {2}/5.00 | {3:,}".format(
|
||||
Info["title"], str(self.datetime.timedelta(seconds = Info["duration"])), round(Info["average_rating"], 2), int(Info["view_count"])))
|
||||
else:
|
||||
match = None
|
||||
|
||||
if Time:
|
||||
url += "#t={0}".format(Time)
|
||||
|
||||
else:
|
||||
try:
|
||||
async with self.aiohttp.ClientSession() as session:
|
||||
Response = await asyncio.wait_for(session.get(url), 5)
|
||||
if Response:
|
||||
ContentType = Response.headers.get("Content-Type")
|
||||
ContentLength = Response.headers.get("Content-Length")
|
||||
Content = await asyncio.wait_for(Response.content.read(65536), 5)
|
||||
|
||||
if not ContentLength:
|
||||
ContentLength = -1
|
||||
|
||||
if ContentType.startswith("text"):
|
||||
if ContentType.startswith("text/plain"):
|
||||
Text = Content.decode("utf-8", errors = "ignore")
|
||||
else:
|
||||
Soup = self.BeautifulSoup(Content.decode("utf-8", errors = "ignore"), "lxml")
|
||||
if Soup.title:
|
||||
self.Torchlight().SayChat("[URL] {0}".format(Soup.title.string))
|
||||
elif ContentType.startswith("image"):
|
||||
fp = self.io.BytesIO(Content)
|
||||
im = self.Image.open(fp)
|
||||
self.Torchlight().SayChat("[IMAGE] {0} | Width: {1} | Height: {2} | Size: {3}".format(im.format, im.size[0], im.size[1], Utils.HumanSize(ContentLength)))
|
||||
fp.close()
|
||||
else:
|
||||
Filetype = self.magic.from_buffer(bytes(Content))
|
||||
self.Torchlight().SayChat("[FILE] {0} | Size: {1}".format(Filetype, Utils.HumanSize(ContentLength)))
|
||||
|
||||
Response.close()
|
||||
except Exception as e:
|
||||
self.Torchlight().SayChat("Error: {0}".format(str(e)))
|
||||
self.Logger.error(traceback.format_exc())
|
||||
|
||||
self.Torchlight().LastUrl = url
|
||||
return url, Text
|
||||
|
||||
async def _rfunc(self, line, match, player):
|
||||
Url = match.groups()[0]
|
||||
if not Url.startswith("http") and not Url.startswith("ftp"):
|
||||
Url = "http://" + Url
|
||||
|
||||
if line.startswith("!yt "):
|
||||
URL, _ = await self.URLInfo(Url, True)
|
||||
return "!yt " + URL
|
||||
|
||||
if line.startswith("!dec "):
|
||||
_, text = await self.URLInfo(Url, False)
|
||||
if text:
|
||||
return "!dec " + text
|
||||
|
||||
asyncio.ensure_future(self.URLInfo(Url))
|
||||
return -1
|
||||
|
||||
|
||||
def FormatAccess(Torchlight, player):
|
||||
Answer = "#{0} \"{1}\"({2}) is ".format(player.UserID, player.Name, player.UniqueID)
|
||||
Level = str(0)
|
||||
if player.Access:
|
||||
Level = str(player.Access["level"])
|
||||
Answer += "level {0!s} as {1}.".format(Level, player.Access["name"])
|
||||
else:
|
||||
Answer += "not authenticated."
|
||||
|
||||
if Level in Torchlight().Config["AudioLimits"]:
|
||||
Uses = Torchlight().Config["AudioLimits"][Level]["Uses"]
|
||||
TotalTime = Torchlight().Config["AudioLimits"][Level]["TotalTime"]
|
||||
|
||||
if Uses >= 0:
|
||||
Answer += " Uses: {0}/{1}".format(player.Storage["Audio"]["Uses"], Uses)
|
||||
if TotalTime >= 0:
|
||||
Answer += " Time: {0}/{1}".format(round(player.Storage["Audio"]["TimeUsed"], 2), round(TotalTime, 2))
|
||||
|
||||
return Answer
|
||||
|
||||
class Access(BaseCommand):
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!access"]
|
||||
self.Level = 0
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_chat_cooldown(player):
|
||||
return -1
|
||||
|
||||
Count = 0
|
||||
if message[0] == "!access":
|
||||
if message[1]:
|
||||
return -1
|
||||
|
||||
self.Torchlight().SayChat(FormatAccess(self.Torchlight, player), player)
|
||||
|
||||
return 0
|
||||
|
||||
class Who(BaseCommand):
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!who", "!whois"]
|
||||
self.Level = 1
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
Count = 0
|
||||
if message[0] == "!who":
|
||||
for Player in self.Torchlight().Players:
|
||||
if Player.Name.lower().find(message[1].lower()) != -1:
|
||||
self.Torchlight().SayChat(FormatAccess(self.Torchlight, Player))
|
||||
|
||||
Count += 1
|
||||
if Count >= 3:
|
||||
break
|
||||
|
||||
elif message[0] == "!whois":
|
||||
for UniqueID, Access in self.Torchlight().Access:
|
||||
if Access["name"].lower().find(message[1].lower()) != -1:
|
||||
Player = self.Torchlight().Players.FindUniqueID(UniqueID)
|
||||
if Player:
|
||||
self.Torchlight().SayChat(FormatAccess(self.Torchlight, Player))
|
||||
else:
|
||||
self.Torchlight().SayChat("#? \"{0}\"({1}) is level {2!s} is currently offline.".format(Access["name"], UniqueID, Access["level"]))
|
||||
|
||||
Count += 1
|
||||
if Count >= 3:
|
||||
break
|
||||
return 0
|
||||
|
||||
|
||||
class WolframAlpha(BaseCommand):
|
||||
import urllib.parse
|
||||
import aiohttp
|
||||
import xml.etree.ElementTree as etree
|
||||
import re
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!cc"]
|
||||
self.Level = 10
|
||||
|
||||
def Clean(self, Text):
|
||||
return self.re.sub("[ ]{2,}", " ", Text.replace(' | ', ': ').replace('\n', ' | ').replace('~~', ' ≈ ')).strip()
|
||||
|
||||
async def Calculate(self, Params, player):
|
||||
async with self.aiohttp.ClientSession() as session:
|
||||
Response = await asyncio.wait_for(session.get("http://api.wolframalpha.com/v2/query", params=Params), 10)
|
||||
if not Response:
|
||||
return 1
|
||||
|
||||
Data = await asyncio.wait_for(Response.text(), 5)
|
||||
if not Data:
|
||||
return 2
|
||||
|
||||
Root = self.etree.fromstring(Data)
|
||||
|
||||
|
||||
# Find all pods with plaintext answers
|
||||
# Filter out None -answers, strip strings and filter out the empty ones
|
||||
Pods = list(filter(None, [p.text.strip() for p in Root.findall('.//subpod/plaintext') if p is not None and p.text is not None]))
|
||||
|
||||
# no answer pods found, check if there are didyoumeans-elements
|
||||
if not Pods:
|
||||
Didyoumeans = Root.find("didyoumeans")
|
||||
# no support for future stuff yet, TODO?
|
||||
if not Didyoumeans:
|
||||
# If there's no pods, the question clearly wasn't understood
|
||||
self.Torchlight().SayChat("Sorry, couldn't understand the question.", player)
|
||||
return 3
|
||||
|
||||
Options = []
|
||||
for Didyoumean in Didyoumeans:
|
||||
Options.append("\"{0}\"".format(Didyoumean.text))
|
||||
Line = " or ".join(Options)
|
||||
Line = "Did you mean {0}?".format(Line)
|
||||
self.Torchlight().SayChat(Line, player)
|
||||
return 0
|
||||
|
||||
# If there's only one pod with text, it's probably the answer
|
||||
# example: "integral x²"
|
||||
if len(Pods) == 1:
|
||||
Answer = self.Clean(Pods[0])
|
||||
self.Torchlight().SayChat(Answer, player)
|
||||
return 0
|
||||
|
||||
# If there's multiple pods, first is the question interpretation
|
||||
Question = self.Clean(Pods[0].replace(' | ', ' ').replace('\n', ' '))
|
||||
# and second is the best answer
|
||||
Answer = self.Clean(Pods[1])
|
||||
self.Torchlight().SayChat("{0} = {1}".format(Question, Answer), player)
|
||||
return 0
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_chat_cooldown(player):
|
||||
return -1
|
||||
|
||||
if self.check_disabled(player):
|
||||
return -1
|
||||
|
||||
Params = dict({"input": message[1], "appid": self.Torchlight().Config["WolframAPIKey"]})
|
||||
Ret = await self.Calculate(Params, player)
|
||||
return Ret
|
||||
|
||||
|
||||
class UrbanDictionary(BaseCommand):
|
||||
import aiohttp
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!define", "!ud"]
|
||||
self.Level = 10
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_chat_cooldown(player):
|
||||
return -1
|
||||
|
||||
if self.check_disabled(player):
|
||||
return -1
|
||||
|
||||
async with self.aiohttp.ClientSession() as session:
|
||||
Response = await asyncio.wait_for(session.get("https://api.urbandictionary.com/v0/define?term={0}".format(message[1])), 5)
|
||||
if not Response:
|
||||
return 1
|
||||
|
||||
Data = await asyncio.wait_for(Response.json(), 5)
|
||||
if not Data:
|
||||
return 3
|
||||
|
||||
if not 'list' in Data or not Data["list"]:
|
||||
self.Torchlight().SayChat("[UB] No definition found for: {}".format(message[1]), player)
|
||||
return 4
|
||||
|
||||
def print_item(item):
|
||||
self.Torchlight().SayChat("[UD] {word} ({thumbs_up}/{thumbs_down}): {definition}\n{example}".format(**item), player)
|
||||
|
||||
print_item(Data["list"][0])
|
||||
|
||||
|
||||
class OpenWeather(BaseCommand):
|
||||
import aiohttp
|
||||
import geoip2.database
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.GeoIP = self.geoip2.database.Reader("/usr/share/GeoIP/GeoLite2-City.mmdb")
|
||||
self.Triggers = ["!w", "!vv"]
|
||||
self.Level = 10
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_chat_cooldown(player):
|
||||
return -1
|
||||
|
||||
if self.check_disabled(player):
|
||||
return -1
|
||||
|
||||
if not message[1]:
|
||||
# Use GeoIP location
|
||||
info = self.GeoIP.city(player.Address.split(":")[0])
|
||||
Search = "lat={}&lon={}".format(info.location.latitude, info.location.longitude)
|
||||
else:
|
||||
Search = "q={}".format(message[1])
|
||||
|
||||
async with self.aiohttp.ClientSession() as session:
|
||||
Response = await asyncio.wait_for(session.get("https://api.openweathermap.org/data/2.5/weather?APPID={0}&units=metric&{1}".format(
|
||||
self.Torchlight().Config["OpenWeatherAPIKey"], Search)), 5)
|
||||
if not Response:
|
||||
return 2
|
||||
|
||||
Data = await asyncio.wait_for(Response.json(), 5)
|
||||
if not Data:
|
||||
return 3
|
||||
|
||||
if Data["cod"] != 200:
|
||||
self.Torchlight().SayPrivate(player, "[OW] {0}".format(Data["message"]))
|
||||
return 5
|
||||
|
||||
degToCardinal = lambda d: ["N", "NE", "E", "SE", "S", "SW", "W", "NW"][int(((d + 22.5)/45.0) % 8)]
|
||||
if "deg" in Data["wind"]:
|
||||
windDir = degToCardinal(Data["wind"]["deg"])
|
||||
else:
|
||||
windDir = "?"
|
||||
|
||||
timezone = "{}{}".format('+' if Data["timezone"] > 0 else '', int(Data["timezone"] / 3600))
|
||||
if Data["timezone"] % 3600 != 0:
|
||||
timezone += ":{}".format((Data["timezone"] % 3600) / 60)
|
||||
|
||||
self.Torchlight().SayChat("[{}, {}](UTC{}) {}°C ({}/{}) {}: {} | Wind {} {}kph | Clouds: {}%% | Humidity: {}%%".format(Data["name"], Data["sys"]["country"], timezone,
|
||||
Data["main"]["temp"], Data["main"]["temp_min"], Data["main"]["temp_max"], Data["weather"][0]["main"], Data["weather"][0]["description"],
|
||||
windDir, Data["wind"]["speed"], Data["clouds"]["all"], Data["main"]["humidity"]), player)
|
||||
|
||||
return 0
|
||||
|
||||
'''
|
||||
class WUnderground(BaseCommand):
|
||||
import aiohttp
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!w"]
|
||||
self.Level = 0
|
||||
|
||||
async def _func(self, message, player):
|
||||
if not message[1]:
|
||||
# Use IP address
|
||||
Search = "autoip"
|
||||
Additional = "?geo_ip={0}".format(player.Address.split(":")[0])
|
||||
else:
|
||||
async with self.aiohttp.ClientSession() as session:
|
||||
Response = await asyncio.wait_for(session.get("http://autocomplete.wunderground.com/aq?format=JSON&query={0}".format(message[1])), 5)
|
||||
if not Response:
|
||||
return 2
|
||||
|
||||
Data = await asyncio.wait_for(Response.json(), 5)
|
||||
if not Data:
|
||||
return 3
|
||||
|
||||
if not Data["RESULTS"]:
|
||||
self.Torchlight().SayPrivate(player, "[WU] No cities match your search query.")
|
||||
return 4
|
||||
|
||||
Search = Data["RESULTS"][0]["name"]
|
||||
Additional = ""
|
||||
|
||||
async with self.aiohttp.ClientSession() as session:
|
||||
Response = await asyncio.wait_for(session.get("http://api.wunderground.com/api/{0}/conditions/q/{1}.json{2}".format(
|
||||
self.Torchlight().Config["WundergroundAPIKey"], Search, Additional)), 5)
|
||||
if not Response:
|
||||
return 2
|
||||
|
||||
Data = await asyncio.wait_for(Response.json(), 5)
|
||||
if not Data:
|
||||
return 3
|
||||
|
||||
if "error" in Data["response"]:
|
||||
self.Torchlight().SayPrivate(player, "[WU] {0}.".format(Data["response"]["error"]["description"]))
|
||||
return 5
|
||||
|
||||
if not "current_observation" in Data:
|
||||
Choices = str()
|
||||
NumResults = len(Data["response"]["results"])
|
||||
for i, Result in enumerate(Data["response"]["results"]):
|
||||
Choices += "{0}, {1}".format(Result["city"],
|
||||
Result["state"] if Result["state"] else Result ["country_iso3166"])
|
||||
|
||||
if i < NumResults - 1:
|
||||
Choices += " | "
|
||||
|
||||
self.Torchlight().SayPrivate(player, "[WU] Did you mean: {0}".format(Choices))
|
||||
return 6
|
||||
|
||||
Observation = Data["current_observation"]
|
||||
|
||||
self.Torchlight().SayChat("[{0}, {1}] {2}°C ({3}F) {4} | Wind {5} {6}kph ({7}mph) | Humidity: {8}".format(Observation["display_location"]["city"],
|
||||
Observation["display_location"]["state"] if Observation["display_location"]["state"] else Observation["display_location"]["country_iso3166"],
|
||||
Observation["temp_c"], Observation["temp_f"], Observation["weather"],
|
||||
Observation["wind_dir"], Observation["wind_kph"], Observation["wind_mph"],
|
||||
Observation["relative_humidity"]))
|
||||
|
||||
return 0
|
||||
'''
|
||||
|
||||
class VoteDisable(BaseCommand):
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!votedisable", "!disablevote"]
|
||||
self.Level = 0
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.Torchlight().Disabled:
|
||||
self.Torchlight().SayPrivate(player, "Torchlight is already disabled for the duration of this map.")
|
||||
return
|
||||
|
||||
self.Torchlight().DisableVotes.add(player.UniqueID)
|
||||
|
||||
have = len(self.Torchlight().DisableVotes)
|
||||
needed = len(self.Torchlight().Players) // 5
|
||||
if have >= needed:
|
||||
self.Torchlight().SayChat("Torchlight has been disabled for the duration of this map.")
|
||||
self.Torchlight().Disabled = 6
|
||||
else:
|
||||
self.Torchlight().SayPrivate(player, "Torchlight needs {0} more disable votes to be disabled.".format(needed - have))
|
||||
|
||||
|
||||
class VoiceCommands(BaseCommand):
|
||||
import json
|
||||
import random
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!random", "!search"]
|
||||
self.Level = 0
|
||||
|
||||
def LoadTriggers(self):
|
||||
try:
|
||||
with open("triggers.json", "r") as fp:
|
||||
Triggers = self.json.load(fp)
|
||||
except ValueError as e:
|
||||
self.Logger.error(sys._getframe().f_code.co_name + ' ' + str(e))
|
||||
self.Torchlight().SayChat(str(e))
|
||||
|
||||
self.VoiceTriggers = dict()
|
||||
for Line in Triggers:
|
||||
for Trigger in Line["names"]:
|
||||
self.VoiceTriggers[Trigger] = Line["sound"]
|
||||
|
||||
def _setup(self):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name)
|
||||
self.LoadTriggers()
|
||||
for Trigger in self.VoiceTriggers.keys():
|
||||
self.Triggers.append(Trigger)
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_disabled(player):
|
||||
return -1
|
||||
|
||||
Level = 0
|
||||
if player.Access:
|
||||
Level = player.Access["level"]
|
||||
|
||||
message[0] = message[0].lower()
|
||||
message[1] = message[1].lower()
|
||||
if message[0][0] != '!' and Level < 2:
|
||||
return 1
|
||||
|
||||
if message[0] == "!search":
|
||||
res = []
|
||||
for key in self.VoiceTriggers.keys():
|
||||
if message[1] in key.lower():
|
||||
res.append(key)
|
||||
self.Torchlight().SayPrivate(player, "{} results: {}".format(len(res), ", ".join(res)))
|
||||
return 0
|
||||
elif Level < 2:
|
||||
return 0
|
||||
|
||||
if message[0] == "!random":
|
||||
Trigger = self.random.choice(list(self.VoiceTriggers.values()))
|
||||
if isinstance(Trigger, list):
|
||||
Sound = self.random.choice(Trigger)
|
||||
else:
|
||||
Sound = Trigger
|
||||
else:
|
||||
Sounds = self.VoiceTriggers[message[0]]
|
||||
|
||||
try:
|
||||
Num = int(message[1])
|
||||
except ValueError:
|
||||
Num = None
|
||||
|
||||
if isinstance(Sounds, list):
|
||||
if Num and Num > 0 and Num <= len(Sounds):
|
||||
Sound = Sounds[Num - 1]
|
||||
|
||||
elif message[1]:
|
||||
searching = message[1].startswith('?')
|
||||
search = message[1][1:] if searching else message[1]
|
||||
Sound = None
|
||||
names = []
|
||||
matches = []
|
||||
for sound in Sounds:
|
||||
name = os.path.splitext(os.path.basename(sound))[0]
|
||||
names.append(name)
|
||||
|
||||
if search and search in name.lower():
|
||||
matches.append((name, sound))
|
||||
|
||||
if matches:
|
||||
matches.sort(key=lambda t: len(t[0]))
|
||||
mlist = [t[0] for t in matches]
|
||||
if searching:
|
||||
self.Torchlight().SayPrivate(player, "{} results: {}".format(len(mlist), ", ".join(mlist)))
|
||||
return 0
|
||||
|
||||
Sound = matches[0][1]
|
||||
if len(matches) > 1:
|
||||
self.Torchlight().SayPrivate(player, "Multiple matches: {}".format(", ".join(mlist)))
|
||||
|
||||
if not Sound and not Num:
|
||||
if not searching:
|
||||
self.Torchlight().SayPrivate(player, "Couldn't find {} in list of sounds.".format(message[1]))
|
||||
self.Torchlight().SayPrivate(player, ", ".join(names))
|
||||
return 1
|
||||
|
||||
elif Num:
|
||||
self.Torchlight().SayPrivate(player, "Number {} is out of bounds, max {}.".format(Num, len(Sounds)))
|
||||
return 1
|
||||
|
||||
else:
|
||||
Sound = self.random.choice(Sounds)
|
||||
else:
|
||||
Sound = Sounds
|
||||
|
||||
if not Sound:
|
||||
return 1
|
||||
|
||||
Path = os.path.abspath(os.path.join("sounds", Sound))
|
||||
AudioClip = self.Torchlight().AudioManager.AudioClip(player, "file://" + Path)
|
||||
if not AudioClip:
|
||||
return 1
|
||||
|
||||
return AudioClip.Play()
|
||||
|
||||
|
||||
class YouTube(BaseCommand):
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!yt"]
|
||||
self.Level = 3
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_disabled(player):
|
||||
return -1
|
||||
|
||||
if self.Torchlight().LastUrl:
|
||||
message[1] = message[1].replace("!last", self.Torchlight().LastUrl)
|
||||
|
||||
Temp = DataHolder()
|
||||
Time = None
|
||||
|
||||
if Temp(message[1].find("&t=")) != -1 or Temp(message[1].find("?t=")) != -1 or Temp(message[1].find("#t=")) != -1:
|
||||
TimeStr = message[1][Temp.value + 3:].split('&')[0].split('?')[0].split('#')[0]
|
||||
if TimeStr:
|
||||
Time = Utils.ParseTime(TimeStr)
|
||||
|
||||
AudioClip = self.Torchlight().AudioManager.AudioClip(player, message[1])
|
||||
if not AudioClip:
|
||||
return 1
|
||||
|
||||
return AudioClip.Play(Time)
|
||||
|
||||
class YouTubeSearch(BaseCommand):
|
||||
import json
|
||||
import datetime
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!yts"]
|
||||
self.Level = 3
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_disabled(player):
|
||||
return -1
|
||||
|
||||
Temp = DataHolder()
|
||||
Time = None
|
||||
|
||||
if Temp(message[1].find("&t=")) != -1 or Temp(message[1].find("?t=")) != -1 or Temp(message[1].find("#t=")) != -1:
|
||||
TimeStr = message[1][Temp.value + 3:].split('&')[0].split('?')[0].split('#')[0]
|
||||
if TimeStr:
|
||||
Time = Utils.ParseTime(TimeStr)
|
||||
message[1] = message[1][:Temp.value]
|
||||
|
||||
Proc = await asyncio.create_subprocess_exec("youtube-dl", "--dump-json", "-xg", "ytsearch:" + message[1],
|
||||
stdout = asyncio.subprocess.PIPE)
|
||||
Out, _ = await Proc.communicate()
|
||||
|
||||
print('out value: ', Out)
|
||||
url, Info = Out.split(b'\n', maxsplit = 1)
|
||||
url = url.strip().decode("ascii")
|
||||
Info = self.json.loads(Info)
|
||||
|
||||
if Info["extractor_key"] == "Youtube":
|
||||
self.Torchlight().SayChat("\x07E52D27[YouTube]\x01 {0} | {1} | {2}/5.00 | {3:,}".format(
|
||||
Info["title"], str(self.datetime.timedelta(seconds = Info["duration"])), round(Info["average_rating"] or 0, 2), int(Info["view_count"])))
|
||||
AudioClip = self.Torchlight().AudioManager.AudioClip(player, url)
|
||||
if not AudioClip:
|
||||
return 1
|
||||
self.Torchlight().LastUrl = url
|
||||
return AudioClip.Play(Time)
|
||||
|
||||
|
||||
class Say(BaseCommand):
|
||||
import gtts
|
||||
import tempfile
|
||||
VALID_LANGUAGES = [lang for lang in gtts.lang.tts_langs().keys()]
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = [("!say", 4)]
|
||||
self.Level = 2
|
||||
|
||||
async def Say(self, player, language, message):
|
||||
GTTS = self.gtts.gTTS(text = message, lang = language)
|
||||
|
||||
TempFile = self.tempfile.NamedTemporaryFile(delete = False)
|
||||
GTTS.write_to_fp(TempFile)
|
||||
TempFile.close()
|
||||
|
||||
AudioClip = self.Torchlight().AudioManager.AudioClip(player, "file://" + TempFile.name)
|
||||
if not AudioClip:
|
||||
os.unlink(TempFile.name)
|
||||
return 1
|
||||
|
||||
if AudioClip.Play():
|
||||
AudioClip.AudioPlayer.AddCallback("Stop", lambda: os.unlink(TempFile.name))
|
||||
return 0
|
||||
else:
|
||||
os.unlink(TempFile.name)
|
||||
return 1
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_disabled(player):
|
||||
return -1
|
||||
|
||||
if not message[1]:
|
||||
return 1
|
||||
|
||||
Language = "en"
|
||||
if len(message[0]) > 4:
|
||||
Language = message[0][4:]
|
||||
|
||||
if not Language in self.VALID_LANGUAGES:
|
||||
return 1
|
||||
|
||||
asyncio.ensure_future(self.Say(player, Language, message[1]))
|
||||
return 0
|
||||
|
||||
'''
|
||||
class DECTalk(BaseCommand):
|
||||
import tempfile
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!dec"]
|
||||
self.Level = 0
|
||||
|
||||
async def Say(self, player, message):
|
||||
message = "[:phoneme on]" + message
|
||||
TempFile = self.tempfile.NamedTemporaryFile(delete = False)
|
||||
TempFile.close()
|
||||
|
||||
Proc = await asyncio.create_subprocess_exec("wine", "say.exe", "-w", TempFile.name,
|
||||
cwd = "dectalk", stdin = asyncio.subprocess.PIPE)
|
||||
await Proc.communicate(message.encode('utf-8', errors='ignore'))
|
||||
|
||||
AudioClip = self.Torchlight().AudioManager.AudioClip(player, "file://" + TempFile.name)
|
||||
if not AudioClip:
|
||||
os.unlink(TempFile.name)
|
||||
return 1
|
||||
|
||||
if AudioClip.Play(None, "-af", "volume=10dB"):
|
||||
AudioClip.AudioPlayer.AddCallback("Stop", lambda: os.unlink(TempFile.name))
|
||||
return 0
|
||||
else:
|
||||
os.unlink(TempFile.name)
|
||||
return 1
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_disabled(player):
|
||||
return -1
|
||||
|
||||
if not message[1]:
|
||||
return 1
|
||||
|
||||
asyncio.ensure_future(self.Say(player, message[1]))
|
||||
return 0
|
||||
'''
|
||||
|
||||
class Stop(BaseCommand):
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!stop"]
|
||||
self.Level = 0
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
self.Torchlight().AudioManager.Stop(player, message[1])
|
||||
return True
|
||||
|
||||
|
||||
class EnableDisable(BaseCommand):
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!enable", "!disable"]
|
||||
self.Level = 3
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if message[0] == "!enable":
|
||||
if self.Torchlight().Disabled:
|
||||
if self.Torchlight().Disabled > player.Access["level"]:
|
||||
self.Torchlight().SayPrivate(player, "You don't have access to enable torchlight, since it was disabled by a higher level user.")
|
||||
return 1
|
||||
self.Torchlight().SayChat("Torchlight has been enabled for the duration of this map - Type !disable to disable it again.")
|
||||
|
||||
self.Torchlight().Disabled = False
|
||||
|
||||
elif message[0] == "!disable":
|
||||
if self.Torchlight().Disabled > player.Access["level"]:
|
||||
self.Torchlight().SayPrivate(player, "You don't have access to disable torchlight, since it was already disabled by a higher level user.")
|
||||
return 1
|
||||
self.Torchlight().SayChat("Torchlight has been disabled for the duration of this map - Type !enable to enable it again.")
|
||||
self.Torchlight().Disabled = player.Access["level"]
|
||||
|
||||
|
||||
class AdminAccess(BaseCommand):
|
||||
from collections import OrderedDict
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!access"]
|
||||
self.Level = 4
|
||||
|
||||
def ReloadValidUsers(self):
|
||||
self.Torchlight().Access.Load()
|
||||
for Player in self.Torchlight().Players:
|
||||
Access = self.Torchlight().Access[Player.UniqueID]
|
||||
Player.Access = Access
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
if not message[1]:
|
||||
return -1
|
||||
|
||||
if message[1].lower() == "reload":
|
||||
self.ReloadValidUsers()
|
||||
self.Torchlight().SayChat("Loaded access list with {0} users".format(len(self.Torchlight().Access)))
|
||||
|
||||
elif message[1].lower() == "save":
|
||||
self.Torchlight().Access.Save()
|
||||
self.Torchlight().SayChat("Saved access list with {0} users".format(len(self.Torchlight().Access)))
|
||||
|
||||
# Modify access
|
||||
else:
|
||||
Player = None
|
||||
Buf = message[1]
|
||||
Temp = Buf.find(" as ")
|
||||
if Temp != -1:
|
||||
try:
|
||||
Regname, Level = Buf[Temp + 4:].rsplit(' ', 1)
|
||||
except ValueError as e:
|
||||
self.Torchlight().SayChat(str(e))
|
||||
return 1
|
||||
|
||||
Regname = Regname.strip()
|
||||
Level = Level.strip()
|
||||
Buf = Buf[:Temp].strip()
|
||||
else:
|
||||
try:
|
||||
Buf, Level = Buf.rsplit(' ', 1)
|
||||
except ValueError as e:
|
||||
self.Torchlight().SayChat(str(e))
|
||||
return 2
|
||||
|
||||
Buf = Buf.strip()
|
||||
Level = Level.strip()
|
||||
|
||||
# Find user by User ID
|
||||
if Buf[0] == '#' and Buf[1:].isnumeric():
|
||||
Player = self.Torchlight().Players.FindUserID(int(Buf[1:]))
|
||||
# Search user by name
|
||||
else:
|
||||
for Player_ in self.Torchlight().Players:
|
||||
if Player_.Name.lower().find(Buf.lower()) != -1:
|
||||
Player = Player_
|
||||
break
|
||||
|
||||
if not Player:
|
||||
self.Torchlight().SayChat("Couldn't find user: {0}".format(Buf))
|
||||
return 3
|
||||
|
||||
if Level.isnumeric() or (Level.startswith('-') and Level[1:].isdigit()):
|
||||
Level = int(Level)
|
||||
|
||||
if Level >= player.Access["level"] and player.Access["level"] < 10:
|
||||
self.Torchlight().SayChat("Trying to assign level {0}, which is higher or equal than your level ({1})".format(Level, player.Access["level"]))
|
||||
return 4
|
||||
|
||||
if Player.Access:
|
||||
if Player.Access["level"] >= player.Access["level"] and player.Access["level"] < 10:
|
||||
self.Torchlight().SayChat("Trying to modify level {0}, which is higher or equal than your level ({1})".format(Player.Access["level"], player.Access["level"]))
|
||||
return 5
|
||||
|
||||
if "Regname" in locals():
|
||||
self.Torchlight().SayChat("Changed \"{0}\"({1}) as {2} level/name from {3} to {4} as {5}".format(
|
||||
Player.Name, Player.UniqueID, Player.Access["name"], Player.Access["level"], Level, Regname))
|
||||
Player.Access["name"] = Regname
|
||||
else:
|
||||
self.Torchlight().SayChat("Changed \"{0}\"({1}) as {2} level from {3} to {4}".format(
|
||||
Player.Name, Player.UniqueID, Player.Access["name"], Player.Access["level"], Level))
|
||||
|
||||
Player.Access["level"] = Level
|
||||
self.Torchlight().Access[Player.UniqueID] = Player.Access
|
||||
else:
|
||||
if not "Regname" in locals():
|
||||
Regname = Player.Name
|
||||
|
||||
self.Torchlight().Access[Player.UniqueID] = self.OrderedDict([("name", Regname), ("level", Level)])
|
||||
Player.Access = self.Torchlight().Access[Player.UniqueID]
|
||||
self.Torchlight().SayChat("Added \"{0}\"({1}) to access list as {2} with level {3}".format(Player.Name, Player.UniqueID, Regname, Level))
|
||||
else:
|
||||
if Level == "revoke" and Player.Access:
|
||||
if Player.Access["level"] >= player.Access["level"] and player.Access["level"] < 10:
|
||||
self.Torchlight().SayChat("Trying to revoke level {0}, which is higher or equal than your level ({1})".format(Player.Access["level"], player.Access["level"]))
|
||||
return 6
|
||||
|
||||
self.Torchlight().SayChat("Removed \"{0}\"({1}) from access list (was {2} with level {3})".format(
|
||||
Player.Name, Player.UniqueID, Player.Access["name"], Player.Access["level"]))
|
||||
del self.Torchlight().Access[Player.UniqueID]
|
||||
Player.Access = None
|
||||
return 0
|
||||
|
||||
class Reload(BaseCommand):
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!reload"]
|
||||
self.Level = 4
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
self.Torchlight().Reload()
|
||||
return 0
|
||||
|
||||
"""
|
||||
class Exec(BaseCommand):
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!exec"]
|
||||
self.Level = 100
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
try:
|
||||
Response = eval(message[1])
|
||||
except Exception as e:
|
||||
self.Torchlight().SayChat("Error: {0}".format(str(e)))
|
||||
return 1
|
||||
self.Torchlight().SayChat(str(Response))
|
||||
return 0
|
||||
"""
|
930
torchlight_changes_unloze/torchlight3/Torchlight/Commandsmg.py
Executable file
930
torchlight_changes_unloze/torchlight3/Torchlight/Commandsmg.py
Executable file
@ -0,0 +1,930 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import math
|
||||
from .Utils import Utils, DataHolder
|
||||
import traceback
|
||||
|
||||
class BaseCommand():
|
||||
Order = 0
|
||||
def __init__(self, torchlight):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Torchlight = torchlight
|
||||
self.Triggers = []
|
||||
self.Level = 0
|
||||
|
||||
def check_chat_cooldown(self, player):
|
||||
if player.ChatCooldown > self.Torchlight().Master.Loop.time():
|
||||
cooldown = player.ChatCooldown - self.Torchlight().Master.Loop.time()
|
||||
self.Torchlight().SayPrivate(player, "You're on cooldown for the next {0:.1f} seconds.".format(cooldown))
|
||||
return True
|
||||
|
||||
def check_disabled(self, player):
|
||||
Level = 0
|
||||
if player.Access:
|
||||
Level = player.Access["level"]
|
||||
|
||||
Disabled = self.Torchlight().Disabled
|
||||
if Disabled and (Disabled > Level or Disabled == Level and Level < self.Torchlight().Config["AntiSpam"]["ImmunityLevel"]):
|
||||
self.Torchlight().SayPrivate(player, "Torchlight is currently disabled!")
|
||||
return True
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name)
|
||||
|
||||
|
||||
class URLFilter(BaseCommand):
|
||||
Order = 1
|
||||
import re
|
||||
import aiohttp
|
||||
import magic
|
||||
import datetime
|
||||
import json
|
||||
import io
|
||||
from bs4 import BeautifulSoup
|
||||
from PIL import Image
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = [self.re.compile(r'''(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))''', self.re.IGNORECASE)]
|
||||
self.Level = 10
|
||||
self.re_youtube = self.re.compile(r'.*?(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)([a-zA-Z0-9_-]{6,11}).*?')
|
||||
|
||||
async def URLInfo(self, url, yt = False):
|
||||
Text = None
|
||||
Info = None
|
||||
match = self.re_youtube.search(url)
|
||||
if match or yt:
|
||||
Temp = DataHolder()
|
||||
Time = None
|
||||
|
||||
if Temp(url.find("&t=")) != -1 or Temp(url.find("?t=")) != -1 or Temp(url.find("#t=")) != -1:
|
||||
TimeStr = url[Temp.value + 3:].split('&')[0].split('?')[0].split('#')[0]
|
||||
if TimeStr:
|
||||
Time = Utils.ParseTime(TimeStr)
|
||||
|
||||
Proc = await asyncio.create_subprocess_exec("youtube-dl", "--dump-json", "-g", url,
|
||||
stdout = asyncio.subprocess.PIPE)
|
||||
Out, _ = await Proc.communicate()
|
||||
|
||||
parts = Out.split(b'\n')
|
||||
parts.pop() # trailing new line
|
||||
|
||||
Info = parts.pop()
|
||||
url = parts.pop()
|
||||
|
||||
url = url.strip().decode("ascii")
|
||||
Info = self.json.loads(Info)
|
||||
|
||||
if Info["extractor_key"] == "Youtube":
|
||||
self.Torchlight().SayChat("\x07E52D27[YouTube]\x01 {0} | {1} | {2}/5.00 | {3:,}".format(
|
||||
Info["title"], str(self.datetime.timedelta(seconds = Info["duration"])), round(Info["average_rating"], 2), int(Info["view_count"])))
|
||||
else:
|
||||
match = None
|
||||
|
||||
if Time:
|
||||
url += "#t={0}".format(Time)
|
||||
|
||||
else:
|
||||
try:
|
||||
async with self.aiohttp.ClientSession() as session:
|
||||
Response = await asyncio.wait_for(session.get(url), 5)
|
||||
if Response:
|
||||
ContentType = Response.headers.get("Content-Type")
|
||||
ContentLength = Response.headers.get("Content-Length")
|
||||
Content = await asyncio.wait_for(Response.content.read(65536), 5)
|
||||
|
||||
if not ContentLength:
|
||||
ContentLength = -1
|
||||
|
||||
if ContentType.startswith("text"):
|
||||
if ContentType.startswith("text/plain"):
|
||||
Text = Content.decode("utf-8", errors = "ignore")
|
||||
else:
|
||||
Soup = self.BeautifulSoup(Content.decode("utf-8", errors = "ignore"), "lxml")
|
||||
if Soup.title:
|
||||
self.Torchlight().SayChat("[URL] {0}".format(Soup.title.string))
|
||||
elif ContentType.startswith("image"):
|
||||
fp = self.io.BytesIO(Content)
|
||||
im = self.Image.open(fp)
|
||||
self.Torchlight().SayChat("[IMAGE] {0} | Width: {1} | Height: {2} | Size: {3}".format(im.format, im.size[0], im.size[1], Utils.HumanSize(ContentLength)))
|
||||
fp.close()
|
||||
else:
|
||||
Filetype = self.magic.from_buffer(bytes(Content))
|
||||
self.Torchlight().SayChat("[FILE] {0} | Size: {1}".format(Filetype, Utils.HumanSize(ContentLength)))
|
||||
|
||||
Response.close()
|
||||
except Exception as e:
|
||||
self.Torchlight().SayChat("Error: {0}".format(str(e)))
|
||||
self.Logger.error(traceback.format_exc())
|
||||
|
||||
self.Torchlight().LastUrl = url
|
||||
return url, Text
|
||||
|
||||
async def _rfunc(self, line, match, player):
|
||||
Url = match.groups()[0]
|
||||
if not Url.startswith("http") and not Url.startswith("ftp"):
|
||||
Url = "http://" + Url
|
||||
|
||||
if line.startswith("!yt "):
|
||||
return
|
||||
#URL, _ = await self.URLInfo(Url, True)
|
||||
#return "!yt " + URL
|
||||
|
||||
if line.startswith("!dec "):
|
||||
_, text = await self.URLInfo(Url, False)
|
||||
if text:
|
||||
return "!dec " + text
|
||||
|
||||
asyncio.ensure_future(self.URLInfo(Url))
|
||||
return -1
|
||||
|
||||
|
||||
def FormatAccess(Torchlight, player):
|
||||
Answer = "#{0} \"{1}\"({2}) is ".format(player.UserID, player.Name, player.UniqueID)
|
||||
Level = str(0)
|
||||
if player.Access:
|
||||
Level = str(player.Access["level"])
|
||||
Answer += "level {0!s} as {1}.".format(Level, player.Access["name"])
|
||||
else:
|
||||
Answer += "not authenticated."
|
||||
|
||||
if Level in Torchlight().Config["AudioLimits"]:
|
||||
Uses = Torchlight().Config["AudioLimits"][Level]["Uses"]
|
||||
TotalTime = Torchlight().Config["AudioLimits"][Level]["TotalTime"]
|
||||
|
||||
if Uses >= 0:
|
||||
Answer += " Uses: {0}/{1}".format(player.Storage["Audio"]["Uses"], Uses)
|
||||
if TotalTime >= 0:
|
||||
Answer += " Time: {0}/{1}".format(round(player.Storage["Audio"]["TimeUsed"], 2), round(TotalTime, 2))
|
||||
|
||||
return Answer
|
||||
|
||||
class Access(BaseCommand):
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!access"]
|
||||
self.Level = 0
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_chat_cooldown(player):
|
||||
return -1
|
||||
|
||||
Count = 0
|
||||
if message[0] == "!access":
|
||||
if message[1]:
|
||||
return -1
|
||||
|
||||
self.Torchlight().SayChat(FormatAccess(self.Torchlight, player), player)
|
||||
|
||||
return 0
|
||||
|
||||
class Who(BaseCommand):
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!who", "!whois"]
|
||||
self.Level = 1
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
Count = 0
|
||||
if message[0] == "!who":
|
||||
for Player in self.Torchlight().Players:
|
||||
if Player.Name.lower().find(message[1].lower()) != -1:
|
||||
self.Torchlight().SayChat(FormatAccess(self.Torchlight, Player))
|
||||
|
||||
Count += 1
|
||||
if Count >= 3:
|
||||
break
|
||||
|
||||
elif message[0] == "!whois":
|
||||
for UniqueID, Access in self.Torchlight().Access:
|
||||
if Access["name"].lower().find(message[1].lower()) != -1:
|
||||
Player = self.Torchlight().Players.FindUniqueID(UniqueID)
|
||||
if Player:
|
||||
self.Torchlight().SayChat(FormatAccess(self.Torchlight, Player))
|
||||
else:
|
||||
self.Torchlight().SayChat("#? \"{0}\"({1}) is level {2!s} is currently offline.".format(Access["name"], UniqueID, Access["level"]))
|
||||
|
||||
Count += 1
|
||||
if Count >= 3:
|
||||
break
|
||||
return 0
|
||||
|
||||
|
||||
class WolframAlpha(BaseCommand):
|
||||
import urllib.parse
|
||||
import aiohttp
|
||||
import xml.etree.ElementTree as etree
|
||||
import re
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!cc"]
|
||||
self.Level = 10
|
||||
|
||||
def Clean(self, Text):
|
||||
return self.re.sub("[ ]{2,}", " ", Text.replace(' | ', ': ').replace('\n', ' | ').replace('~~', ' ≈ ')).strip()
|
||||
|
||||
async def Calculate(self, Params, player):
|
||||
async with self.aiohttp.ClientSession() as session:
|
||||
Response = await asyncio.wait_for(session.get("http://api.wolframalpha.com/v2/query", params=Params), 10)
|
||||
if not Response:
|
||||
return 1
|
||||
|
||||
Data = await asyncio.wait_for(Response.text(), 5)
|
||||
if not Data:
|
||||
return 2
|
||||
|
||||
Root = self.etree.fromstring(Data)
|
||||
|
||||
|
||||
# Find all pods with plaintext answers
|
||||
# Filter out None -answers, strip strings and filter out the empty ones
|
||||
Pods = list(filter(None, [p.text.strip() for p in Root.findall('.//subpod/plaintext') if p is not None and p.text is not None]))
|
||||
|
||||
# no answer pods found, check if there are didyoumeans-elements
|
||||
if not Pods:
|
||||
Didyoumeans = Root.find("didyoumeans")
|
||||
# no support for future stuff yet, TODO?
|
||||
if not Didyoumeans:
|
||||
# If there's no pods, the question clearly wasn't understood
|
||||
self.Torchlight().SayChat("Sorry, couldn't understand the question.", player)
|
||||
return 3
|
||||
|
||||
Options = []
|
||||
for Didyoumean in Didyoumeans:
|
||||
Options.append("\"{0}\"".format(Didyoumean.text))
|
||||
Line = " or ".join(Options)
|
||||
Line = "Did you mean {0}?".format(Line)
|
||||
self.Torchlight().SayChat(Line, player)
|
||||
return 0
|
||||
|
||||
# If there's only one pod with text, it's probably the answer
|
||||
# example: "integral x²"
|
||||
if len(Pods) == 1:
|
||||
Answer = self.Clean(Pods[0])
|
||||
self.Torchlight().SayChat(Answer, player)
|
||||
return 0
|
||||
|
||||
# If there's multiple pods, first is the question interpretation
|
||||
Question = self.Clean(Pods[0].replace(' | ', ' ').replace('\n', ' '))
|
||||
# and second is the best answer
|
||||
Answer = self.Clean(Pods[1])
|
||||
self.Torchlight().SayChat("{0} = {1}".format(Question, Answer), player)
|
||||
return 0
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_chat_cooldown(player):
|
||||
return -1
|
||||
|
||||
if self.check_disabled(player):
|
||||
return -1
|
||||
|
||||
Params = dict({"input": message[1], "appid": self.Torchlight().Config["WolframAPIKey"]})
|
||||
Ret = await self.Calculate(Params, player)
|
||||
return Ret
|
||||
|
||||
|
||||
class UrbanDictionary(BaseCommand):
|
||||
import aiohttp
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!define", "!ud"]
|
||||
self.Level = 10
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_chat_cooldown(player):
|
||||
return -1
|
||||
|
||||
if self.check_disabled(player):
|
||||
return -1
|
||||
|
||||
async with self.aiohttp.ClientSession() as session:
|
||||
Response = await asyncio.wait_for(session.get("https://api.urbandictionary.com/v0/define?term={0}".format(message[1])), 5)
|
||||
if not Response:
|
||||
return 1
|
||||
|
||||
Data = await asyncio.wait_for(Response.json(), 5)
|
||||
if not Data:
|
||||
return 3
|
||||
|
||||
if not 'list' in Data or not Data["list"]:
|
||||
self.Torchlight().SayChat("[UB] No definition found for: {}".format(message[1]), player)
|
||||
return 4
|
||||
|
||||
def print_item(item):
|
||||
self.Torchlight().SayChat("[UD] {word} ({thumbs_up}/{thumbs_down}): {definition}\n{example}".format(**item), player)
|
||||
|
||||
print_item(Data["list"][0])
|
||||
|
||||
|
||||
class OpenWeather(BaseCommand):
|
||||
import aiohttp
|
||||
import geoip2.database
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.GeoIP = self.geoip2.database.Reader("/usr/share/GeoIP/GeoLite2-City.mmdb")
|
||||
self.Triggers = ["!w", "!vv"]
|
||||
self.Level = 10
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_chat_cooldown(player):
|
||||
return -1
|
||||
|
||||
if self.check_disabled(player):
|
||||
return -1
|
||||
|
||||
if not message[1]:
|
||||
# Use GeoIP location
|
||||
info = self.GeoIP.city(player.Address.split(":")[0])
|
||||
Search = "lat={}&lon={}".format(info.location.latitude, info.location.longitude)
|
||||
else:
|
||||
Search = "q={}".format(message[1])
|
||||
|
||||
async with self.aiohttp.ClientSession() as session:
|
||||
Response = await asyncio.wait_for(session.get("https://api.openweathermap.org/data/2.5/weather?APPID={0}&units=metric&{1}".format(
|
||||
self.Torchlight().Config["OpenWeatherAPIKey"], Search)), 5)
|
||||
if not Response:
|
||||
return 2
|
||||
|
||||
Data = await asyncio.wait_for(Response.json(), 5)
|
||||
if not Data:
|
||||
return 3
|
||||
|
||||
if Data["cod"] != 200:
|
||||
self.Torchlight().SayPrivate(player, "[OW] {0}".format(Data["message"]))
|
||||
return 5
|
||||
|
||||
degToCardinal = lambda d: ["N", "NE", "E", "SE", "S", "SW", "W", "NW"][int(((d + 22.5)/45.0) % 8)]
|
||||
if "deg" in Data["wind"]:
|
||||
windDir = degToCardinal(Data["wind"]["deg"])
|
||||
else:
|
||||
windDir = "?"
|
||||
|
||||
timezone = "{}{}".format('+' if Data["timezone"] > 0 else '', int(Data["timezone"] / 3600))
|
||||
if Data["timezone"] % 3600 != 0:
|
||||
timezone += ":{}".format((Data["timezone"] % 3600) / 60)
|
||||
|
||||
self.Torchlight().SayChat("[{}, {}](UTC{}) {}°C ({}/{}) {}: {} | Wind {} {}kph | Clouds: {}%% | Humidity: {}%%".format(Data["name"], Data["sys"]["country"], timezone,
|
||||
Data["main"]["temp"], Data["main"]["temp_min"], Data["main"]["temp_max"], Data["weather"][0]["main"], Data["weather"][0]["description"],
|
||||
windDir, Data["wind"]["speed"], Data["clouds"]["all"], Data["main"]["humidity"]), player)
|
||||
|
||||
return 0
|
||||
|
||||
'''
|
||||
class WUnderground(BaseCommand):
|
||||
import aiohttp
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!w"]
|
||||
self.Level = 0
|
||||
|
||||
async def _func(self, message, player):
|
||||
if not message[1]:
|
||||
# Use IP address
|
||||
Search = "autoip"
|
||||
Additional = "?geo_ip={0}".format(player.Address.split(":")[0])
|
||||
else:
|
||||
async with self.aiohttp.ClientSession() as session:
|
||||
Response = await asyncio.wait_for(session.get("http://autocomplete.wunderground.com/aq?format=JSON&query={0}".format(message[1])), 5)
|
||||
if not Response:
|
||||
return 2
|
||||
|
||||
Data = await asyncio.wait_for(Response.json(), 5)
|
||||
if not Data:
|
||||
return 3
|
||||
|
||||
if not Data["RESULTS"]:
|
||||
self.Torchlight().SayPrivate(player, "[WU] No cities match your search query.")
|
||||
return 4
|
||||
|
||||
Search = Data["RESULTS"][0]["name"]
|
||||
Additional = ""
|
||||
|
||||
async with self.aiohttp.ClientSession() as session:
|
||||
Response = await asyncio.wait_for(session.get("http://api.wunderground.com/api/{0}/conditions/q/{1}.json{2}".format(
|
||||
self.Torchlight().Config["WundergroundAPIKey"], Search, Additional)), 5)
|
||||
if not Response:
|
||||
return 2
|
||||
|
||||
Data = await asyncio.wait_for(Response.json(), 5)
|
||||
if not Data:
|
||||
return 3
|
||||
|
||||
if "error" in Data["response"]:
|
||||
self.Torchlight().SayPrivate(player, "[WU] {0}.".format(Data["response"]["error"]["description"]))
|
||||
return 5
|
||||
|
||||
if not "current_observation" in Data:
|
||||
Choices = str()
|
||||
NumResults = len(Data["response"]["results"])
|
||||
for i, Result in enumerate(Data["response"]["results"]):
|
||||
Choices += "{0}, {1}".format(Result["city"],
|
||||
Result["state"] if Result["state"] else Result ["country_iso3166"])
|
||||
|
||||
if i < NumResults - 1:
|
||||
Choices += " | "
|
||||
|
||||
self.Torchlight().SayPrivate(player, "[WU] Did you mean: {0}".format(Choices))
|
||||
return 6
|
||||
|
||||
Observation = Data["current_observation"]
|
||||
|
||||
self.Torchlight().SayChat("[{0}, {1}] {2}°C ({3}F) {4} | Wind {5} {6}kph ({7}mph) | Humidity: {8}".format(Observation["display_location"]["city"],
|
||||
Observation["display_location"]["state"] if Observation["display_location"]["state"] else Observation["display_location"]["country_iso3166"],
|
||||
Observation["temp_c"], Observation["temp_f"], Observation["weather"],
|
||||
Observation["wind_dir"], Observation["wind_kph"], Observation["wind_mph"],
|
||||
Observation["relative_humidity"]))
|
||||
|
||||
return 0
|
||||
'''
|
||||
|
||||
class VoteDisable(BaseCommand):
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!votedisable", "!disablevote"]
|
||||
self.Level = 0
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.Torchlight().Disabled:
|
||||
self.Torchlight().SayPrivate(player, "Torchlight is already disabled for the duration of this map.")
|
||||
return
|
||||
|
||||
self.Torchlight().DisableVotes.add(player.UniqueID)
|
||||
|
||||
have = len(self.Torchlight().DisableVotes)
|
||||
needed = len(self.Torchlight().Players) // 5
|
||||
if have >= needed:
|
||||
self.Torchlight().SayChat("Torchlight has been disabled for the duration of this map.")
|
||||
self.Torchlight().Disabled = 6
|
||||
else:
|
||||
self.Torchlight().SayPrivate(player, "Torchlight needs {0} more disable votes to be disabled.".format(needed - have))
|
||||
|
||||
|
||||
class VoiceCommands(BaseCommand):
|
||||
import json
|
||||
import random
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!random", "!search"]
|
||||
self.Level = 0
|
||||
|
||||
def LoadTriggers(self):
|
||||
try:
|
||||
with open("triggers.json", "r") as fp:
|
||||
Triggers = self.json.load(fp)
|
||||
except ValueError as e:
|
||||
self.Logger.error(sys._getframe().f_code.co_name + ' ' + str(e))
|
||||
self.Torchlight().SayChat(str(e))
|
||||
|
||||
self.VoiceTriggers = dict()
|
||||
for Line in Triggers:
|
||||
for Trigger in Line["names"]:
|
||||
self.VoiceTriggers[Trigger] = Line["sound"]
|
||||
|
||||
def _setup(self):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name)
|
||||
self.LoadTriggers()
|
||||
for Trigger in self.VoiceTriggers.keys():
|
||||
self.Triggers.append(Trigger)
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_disabled(player):
|
||||
return -1
|
||||
|
||||
Level = 0
|
||||
if player.Access:
|
||||
Level = player.Access["level"]
|
||||
|
||||
message[0] = message[0].lower()
|
||||
message[1] = message[1].lower()
|
||||
if message[0][0] != '!' and Level < 2:
|
||||
return 1
|
||||
|
||||
if message[0] == "!search":
|
||||
res = []
|
||||
for key in self.VoiceTriggers.keys():
|
||||
if message[1] in key.lower():
|
||||
res.append(key)
|
||||
self.Torchlight().SayPrivate(player, "{} results: {}".format(len(res), ", ".join(res)))
|
||||
return 0
|
||||
elif Level < 2:
|
||||
return 0
|
||||
|
||||
if message[0] == "!random":
|
||||
Trigger = self.random.choice(list(self.VoiceTriggers.values()))
|
||||
if isinstance(Trigger, list):
|
||||
Sound = self.random.choice(Trigger)
|
||||
else:
|
||||
Sound = Trigger
|
||||
else:
|
||||
Sounds = self.VoiceTriggers[message[0]]
|
||||
|
||||
try:
|
||||
Num = int(message[1])
|
||||
except ValueError:
|
||||
Num = None
|
||||
|
||||
if isinstance(Sounds, list):
|
||||
if Num and Num > 0 and Num <= len(Sounds):
|
||||
Sound = Sounds[Num - 1]
|
||||
|
||||
elif message[1]:
|
||||
searching = message[1].startswith('?')
|
||||
search = message[1][1:] if searching else message[1]
|
||||
Sound = None
|
||||
names = []
|
||||
matches = []
|
||||
for sound in Sounds:
|
||||
name = os.path.splitext(os.path.basename(sound))[0]
|
||||
names.append(name)
|
||||
|
||||
if search and search in name.lower():
|
||||
matches.append((name, sound))
|
||||
|
||||
if matches:
|
||||
matches.sort(key=lambda t: len(t[0]))
|
||||
mlist = [t[0] for t in matches]
|
||||
if searching:
|
||||
self.Torchlight().SayPrivate(player, "{} results: {}".format(len(mlist), ", ".join(mlist)))
|
||||
return 0
|
||||
|
||||
Sound = matches[0][1]
|
||||
if len(matches) > 1:
|
||||
self.Torchlight().SayPrivate(player, "Multiple matches: {}".format(", ".join(mlist)))
|
||||
|
||||
if not Sound and not Num:
|
||||
if not searching:
|
||||
self.Torchlight().SayPrivate(player, "Couldn't find {} in list of sounds.".format(message[1]))
|
||||
self.Torchlight().SayPrivate(player, ", ".join(names))
|
||||
return 1
|
||||
|
||||
elif Num:
|
||||
self.Torchlight().SayPrivate(player, "Number {} is out of bounds, max {}.".format(Num, len(Sounds)))
|
||||
return 1
|
||||
|
||||
else:
|
||||
Sound = self.random.choice(Sounds)
|
||||
else:
|
||||
Sound = Sounds
|
||||
|
||||
if not Sound:
|
||||
return 1
|
||||
|
||||
Path = os.path.abspath(os.path.join("sounds", Sound))
|
||||
AudioClip = self.Torchlight().AudioManager.AudioClip(player, "file://" + Path)
|
||||
if not AudioClip:
|
||||
return 1
|
||||
|
||||
return AudioClip.Play()
|
||||
|
||||
|
||||
class YouTube(BaseCommand):
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!yt"]
|
||||
self.Level = 3
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_disabled(player):
|
||||
return -1
|
||||
|
||||
if self.Torchlight().LastUrl:
|
||||
message[1] = message[1].replace("!last", self.Torchlight().LastUrl)
|
||||
|
||||
Temp = DataHolder()
|
||||
Time = None
|
||||
|
||||
if Temp(message[1].find("&t=")) != -1 or Temp(message[1].find("?t=")) != -1 or Temp(message[1].find("#t=")) != -1:
|
||||
TimeStr = message[1][Temp.value + 3:].split('&')[0].split('?')[0].split('#')[0]
|
||||
if TimeStr:
|
||||
Time = Utils.ParseTime(TimeStr)
|
||||
|
||||
AudioClip = self.Torchlight().AudioManager.AudioClip(player, message[1])
|
||||
if not AudioClip:
|
||||
return 1
|
||||
|
||||
return AudioClip.Play(Time)
|
||||
|
||||
class YouTubeSearch(BaseCommand):
|
||||
import json
|
||||
import datetime
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!yts"]
|
||||
self.Level = 3
|
||||
|
||||
async def _func(self, message, player):
|
||||
return -1
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_disabled(player):
|
||||
return -1
|
||||
|
||||
Temp = DataHolder()
|
||||
Time = None
|
||||
|
||||
if Temp(message[1].find("&t=")) != -1 or Temp(message[1].find("?t=")) != -1 or Temp(message[1].find("#t=")) != -1:
|
||||
TimeStr = message[1][Temp.value + 3:].split('&')[0].split('?')[0].split('#')[0]
|
||||
if TimeStr:
|
||||
Time = Utils.ParseTime(TimeStr)
|
||||
message[1] = message[1][:Temp.value]
|
||||
|
||||
Proc = await asyncio.create_subprocess_exec("youtube-dl", "--dump-json", "-xg", "ytsearch:" + message[1],
|
||||
stdout = asyncio.subprocess.PIPE)
|
||||
Out, _ = await Proc.communicate()
|
||||
|
||||
print('out value: ', Out)
|
||||
url, Info = Out.split(b'\n', maxsplit = 1)
|
||||
url = url.strip().decode("ascii")
|
||||
Info = self.json.loads(Info)
|
||||
|
||||
if Info["extractor_key"] == "Youtube":
|
||||
self.Torchlight().SayChat("\x07E52D27[YouTube]\x01 {0} | {1} | {2}/5.00 | {3:,}".format(
|
||||
Info["title"], str(self.datetime.timedelta(seconds = Info["duration"])), round(Info["average_rating"] or 0, 2), int(Info["view_count"])))
|
||||
AudioClip = self.Torchlight().AudioManager.AudioClip(player, url)
|
||||
if not AudioClip:
|
||||
return 1
|
||||
self.Torchlight().LastUrl = url
|
||||
return AudioClip.Play(Time)
|
||||
|
||||
|
||||
class Say(BaseCommand):
|
||||
import gtts
|
||||
import tempfile
|
||||
VALID_LANGUAGES = [lang for lang in gtts.lang.tts_langs().keys()]
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = [("!say", 4)]
|
||||
self.Level = 2
|
||||
|
||||
async def Say(self, player, language, message):
|
||||
GTTS = self.gtts.gTTS(text = message, lang = language)
|
||||
|
||||
TempFile = self.tempfile.NamedTemporaryFile(delete = False)
|
||||
GTTS.write_to_fp(TempFile)
|
||||
TempFile.close()
|
||||
|
||||
AudioClip = self.Torchlight().AudioManager.AudioClip(player, "file://" + TempFile.name)
|
||||
if not AudioClip:
|
||||
os.unlink(TempFile.name)
|
||||
return 1
|
||||
|
||||
if AudioClip.Play():
|
||||
AudioClip.AudioPlayer.AddCallback("Stop", lambda: os.unlink(TempFile.name))
|
||||
return 0
|
||||
else:
|
||||
os.unlink(TempFile.name)
|
||||
return 1
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_disabled(player):
|
||||
return -1
|
||||
|
||||
if not message[1]:
|
||||
return 1
|
||||
|
||||
Language = "en"
|
||||
if len(message[0]) > 4:
|
||||
Language = message[0][4:]
|
||||
|
||||
if not Language in self.VALID_LANGUAGES:
|
||||
return 1
|
||||
|
||||
asyncio.ensure_future(self.Say(player, Language, message[1]))
|
||||
return 0
|
||||
|
||||
'''
|
||||
class DECTalk(BaseCommand):
|
||||
import tempfile
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!dec"]
|
||||
self.Level = 0
|
||||
|
||||
async def Say(self, player, message):
|
||||
message = "[:phoneme on]" + message
|
||||
TempFile = self.tempfile.NamedTemporaryFile(delete = False)
|
||||
TempFile.close()
|
||||
|
||||
Proc = await asyncio.create_subprocess_exec("wine", "say.exe", "-w", TempFile.name,
|
||||
cwd = "dectalk", stdin = asyncio.subprocess.PIPE)
|
||||
await Proc.communicate(message.encode('utf-8', errors='ignore'))
|
||||
|
||||
AudioClip = self.Torchlight().AudioManager.AudioClip(player, "file://" + TempFile.name)
|
||||
if not AudioClip:
|
||||
os.unlink(TempFile.name)
|
||||
return 1
|
||||
|
||||
if AudioClip.Play(None, "-af", "volume=10dB"):
|
||||
AudioClip.AudioPlayer.AddCallback("Stop", lambda: os.unlink(TempFile.name))
|
||||
return 0
|
||||
else:
|
||||
os.unlink(TempFile.name)
|
||||
return 1
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if self.check_disabled(player):
|
||||
return -1
|
||||
|
||||
if not message[1]:
|
||||
return 1
|
||||
|
||||
asyncio.ensure_future(self.Say(player, message[1]))
|
||||
return 0
|
||||
'''
|
||||
|
||||
class Stop(BaseCommand):
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!pls"]
|
||||
self.Level = 0
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
self.Torchlight().AudioManager.Stop(player, message[1])
|
||||
return True
|
||||
|
||||
|
||||
class EnableDisable(BaseCommand):
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!enable", "!disable"]
|
||||
self.Level = 3
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
|
||||
if message[0] == "!enable":
|
||||
if self.Torchlight().Disabled:
|
||||
if self.Torchlight().Disabled > player.Access["level"]:
|
||||
self.Torchlight().SayPrivate(player, "You don't have access to enable torchlight, since it was disabled by a higher level user.")
|
||||
return 1
|
||||
self.Torchlight().SayChat("Torchlight has been enabled for the duration of this map - Type !disable to disable it again.")
|
||||
|
||||
self.Torchlight().Disabled = False
|
||||
|
||||
elif message[0] == "!disable":
|
||||
if self.Torchlight().Disabled > player.Access["level"]:
|
||||
self.Torchlight().SayPrivate(player, "You don't have access to disable torchlight, since it was already disabled by a higher level user.")
|
||||
return 1
|
||||
self.Torchlight().SayChat("Torchlight has been disabled for the duration of this map - Type !enable to enable it again.")
|
||||
self.Torchlight().Disabled = player.Access["level"]
|
||||
|
||||
|
||||
class AdminAccess(BaseCommand):
|
||||
from collections import OrderedDict
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!access"]
|
||||
self.Level = 4
|
||||
|
||||
def ReloadValidUsers(self):
|
||||
self.Torchlight().Access.Load()
|
||||
for Player in self.Torchlight().Players:
|
||||
Access = self.Torchlight().Access[Player.UniqueID]
|
||||
Player.Access = Access
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
if not message[1]:
|
||||
return -1
|
||||
|
||||
if message[1].lower() == "reload":
|
||||
self.ReloadValidUsers()
|
||||
self.Torchlight().SayChat("Loaded access list with {0} users".format(len(self.Torchlight().Access)))
|
||||
|
||||
elif message[1].lower() == "save":
|
||||
self.Torchlight().Access.Save()
|
||||
self.Torchlight().SayChat("Saved access list with {0} users".format(len(self.Torchlight().Access)))
|
||||
|
||||
# Modify access
|
||||
else:
|
||||
Player = None
|
||||
Buf = message[1]
|
||||
Temp = Buf.find(" as ")
|
||||
if Temp != -1:
|
||||
try:
|
||||
Regname, Level = Buf[Temp + 4:].rsplit(' ', 1)
|
||||
except ValueError as e:
|
||||
self.Torchlight().SayChat(str(e))
|
||||
return 1
|
||||
|
||||
Regname = Regname.strip()
|
||||
Level = Level.strip()
|
||||
Buf = Buf[:Temp].strip()
|
||||
else:
|
||||
try:
|
||||
Buf, Level = Buf.rsplit(' ', 1)
|
||||
except ValueError as e:
|
||||
self.Torchlight().SayChat(str(e))
|
||||
return 2
|
||||
|
||||
Buf = Buf.strip()
|
||||
Level = Level.strip()
|
||||
|
||||
# Find user by User ID
|
||||
if Buf[0] == '#' and Buf[1:].isnumeric():
|
||||
Player = self.Torchlight().Players.FindUserID(int(Buf[1:]))
|
||||
# Search user by name
|
||||
else:
|
||||
for Player_ in self.Torchlight().Players:
|
||||
if Player_.Name.lower().find(Buf.lower()) != -1:
|
||||
Player = Player_
|
||||
break
|
||||
|
||||
if not Player:
|
||||
self.Torchlight().SayChat("Couldn't find user: {0}".format(Buf))
|
||||
return 3
|
||||
|
||||
if Level.isnumeric() or (Level.startswith('-') and Level[1:].isdigit()):
|
||||
Level = int(Level)
|
||||
|
||||
if Level >= player.Access["level"] and player.Access["level"] < 10:
|
||||
self.Torchlight().SayChat("Trying to assign level {0}, which is higher or equal than your level ({1})".format(Level, player.Access["level"]))
|
||||
return 4
|
||||
|
||||
if Player.Access:
|
||||
if Player.Access["level"] >= player.Access["level"] and player.Access["level"] < 10:
|
||||
self.Torchlight().SayChat("Trying to modify level {0}, which is higher or equal than your level ({1})".format(Player.Access["level"], player.Access["level"]))
|
||||
return 5
|
||||
|
||||
if "Regname" in locals():
|
||||
self.Torchlight().SayChat("Changed \"{0}\"({1}) as {2} level/name from {3} to {4} as {5}".format(
|
||||
Player.Name, Player.UniqueID, Player.Access["name"], Player.Access["level"], Level, Regname))
|
||||
Player.Access["name"] = Regname
|
||||
else:
|
||||
self.Torchlight().SayChat("Changed \"{0}\"({1}) as {2} level from {3} to {4}".format(
|
||||
Player.Name, Player.UniqueID, Player.Access["name"], Player.Access["level"], Level))
|
||||
|
||||
Player.Access["level"] = Level
|
||||
self.Torchlight().Access[Player.UniqueID] = Player.Access
|
||||
else:
|
||||
if not "Regname" in locals():
|
||||
Regname = Player.Name
|
||||
|
||||
self.Torchlight().Access[Player.UniqueID] = self.OrderedDict([("name", Regname), ("level", Level)])
|
||||
Player.Access = self.Torchlight().Access[Player.UniqueID]
|
||||
self.Torchlight().SayChat("Added \"{0}\"({1}) to access list as {2} with level {3}".format(Player.Name, Player.UniqueID, Regname, Level))
|
||||
else:
|
||||
if Level == "revoke" and Player.Access:
|
||||
if Player.Access["level"] >= player.Access["level"] and player.Access["level"] < 10:
|
||||
self.Torchlight().SayChat("Trying to revoke level {0}, which is higher or equal than your level ({1})".format(Player.Access["level"], player.Access["level"]))
|
||||
return 6
|
||||
|
||||
self.Torchlight().SayChat("Removed \"{0}\"({1}) from access list (was {2} with level {3})".format(
|
||||
Player.Name, Player.UniqueID, Player.Access["name"], Player.Access["level"]))
|
||||
del self.Torchlight().Access[Player.UniqueID]
|
||||
Player.Access = None
|
||||
return 0
|
||||
|
||||
class Reload(BaseCommand):
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!reload"]
|
||||
self.Level = 4
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
self.Torchlight().Reload()
|
||||
return 0
|
||||
|
||||
"""
|
||||
class Exec(BaseCommand):
|
||||
def __init__(self, torchlight):
|
||||
super().__init__(torchlight)
|
||||
self.Triggers = ["!exec"]
|
||||
self.Level = 100
|
||||
|
||||
async def _func(self, message, player):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
|
||||
try:
|
||||
Response = eval(message[1])
|
||||
except Exception as e:
|
||||
self.Torchlight().SayChat("Error: {0}".format(str(e)))
|
||||
return 1
|
||||
self.Torchlight().SayChat(str(Response))
|
||||
return 0
|
||||
"""
|
29
torchlight_changes_unloze/torchlight3/Torchlight/Config.py
Executable file
29
torchlight_changes_unloze/torchlight3/Torchlight/Config.py
Executable file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import json
|
||||
import sys
|
||||
|
||||
class Config():
|
||||
def __init__(self):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Config = dict()
|
||||
if len(sys.argv) >= 2:
|
||||
self.ConfigPath = sys.argv[1]
|
||||
else:
|
||||
self.ConfigPath = "config.json"
|
||||
self.Load()
|
||||
|
||||
def Load(self):
|
||||
try:
|
||||
with open(self.ConfigPath, "r") as fp:
|
||||
self.Config = json.load(fp)
|
||||
except ValueError as e:
|
||||
self.Logger.error(sys._getframe().f_code.co_name + ' ' + str(e))
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in self.Config:
|
||||
return self.Config[key]
|
||||
return None
|
26
torchlight_changes_unloze/torchlight3/Torchlight/Constants.py
Executable file
26
torchlight_changes_unloze/torchlight3/Torchlight/Constants.py
Executable file
@ -0,0 +1,26 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
MAXPLAYERS = 65
|
||||
|
||||
ADMFLAG_RESERVATION = (1<<0)
|
||||
ADMFLAG_GENERIC = (1<<1)
|
||||
ADMFLAG_KICK = (1<<2)
|
||||
ADMFLAG_BAN = (1<<3)
|
||||
ADMFLAG_UNBAN = (1<<4)
|
||||
ADMFLAG_SLAY = (1<<5)
|
||||
ADMFLAG_CHANGEMAP = (1<<6)
|
||||
ADMFLAG_CONVARS = (1<<7)
|
||||
ADMFLAG_CONFIG = (1<<8)
|
||||
ADMFLAG_CHAT = (1<<9)
|
||||
ADMFLAG_VOTE = (1<<10)
|
||||
ADMFLAG_PASSWORD = (1<<11)
|
||||
ADMFLAG_RCON = (1<<12)
|
||||
ADMFLAG_CHEATS = (1<<13)
|
||||
ADMFLAG_ROOT = (1<<14)
|
||||
ADMFLAG_CUSTOM1 = (1<<15)
|
||||
ADMFLAG_CUSTOM2 = (1<<16)
|
||||
ADMFLAG_CUSTOM3 = (1<<17)
|
||||
ADMFLAG_CUSTOM4 = (1<<18)
|
||||
ADMFLAG_CUSTOM5 = (1<<19)
|
||||
ADMFLAG_CUSTOM6 = (1<<20)
|
180
torchlight_changes_unloze/torchlight3/Torchlight/FFmpegAudioPlayer.py
Executable file
180
torchlight_changes_unloze/torchlight3/Torchlight/FFmpegAudioPlayer.py
Executable file
@ -0,0 +1,180 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import traceback
|
||||
import asyncio
|
||||
import datetime
|
||||
import time
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
|
||||
SAMPLEBYTES = 2
|
||||
|
||||
class FFmpegAudioPlayerFactory():
|
||||
VALID_CALLBACKS = ["Play", "Stop", "Update"]
|
||||
|
||||
def __init__(self, master):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Master = master
|
||||
self.Torchlight = self.Master.Torchlight
|
||||
|
||||
def __del__(self):
|
||||
self.Master.Logger.info("~FFmpegAudioPlayerFactory()")
|
||||
self.Quit()
|
||||
|
||||
def NewPlayer(self):
|
||||
self.Logger.debug(sys._getframe().f_code.co_name)
|
||||
Player = FFmpegAudioPlayer(self)
|
||||
return Player
|
||||
|
||||
def Quit(self):
|
||||
self.Master.Logger.info("FFmpegAudioPlayerFactory->Quit()")
|
||||
|
||||
|
||||
class FFmpegAudioPlayer():
|
||||
def __init__(self, master):
|
||||
self.Master = master
|
||||
self.Torchlight = self.Master.Torchlight
|
||||
self.Playing = False
|
||||
|
||||
self.Host = (
|
||||
self.Torchlight().Config["VoiceServer"]["Host"],
|
||||
self.Torchlight().Config["VoiceServer"]["Port"]
|
||||
)
|
||||
self.SampleRate = float(self.Torchlight().Config["VoiceServer"]["SampleRate"])
|
||||
|
||||
self.StartedPlaying = None
|
||||
self.StoppedPlaying = None
|
||||
self.Seconds = 0.0
|
||||
|
||||
self.Writer = None
|
||||
self.Process = None
|
||||
|
||||
self.Callbacks = []
|
||||
|
||||
def __del__(self):
|
||||
self.Master.Logger.debug("~FFmpegAudioPlayer()")
|
||||
self.Stop()
|
||||
|
||||
def PlayURI(self, uri, position, *args):
|
||||
if position:
|
||||
PosStr = str(datetime.timedelta(seconds = position))
|
||||
Command = ["/usr/bin/ffmpeg", "-ss", PosStr, "-i", uri, "-acodec", "pcm_s16le", "-ac", "1", "-ar", str(int(self.SampleRate)), "-f", "s16le", "-vn", *args, "-"]
|
||||
else:
|
||||
Command = ["/usr/bin/ffmpeg", "-i", uri, "-acodec", "pcm_s16le", "-ac", "1", "-ar", str(int(self.SampleRate)), "-f", "s16le", "-vn", *args, "-"]
|
||||
|
||||
print(Command)
|
||||
|
||||
self.Playing = True
|
||||
asyncio.ensure_future(self._stream_subprocess(Command))
|
||||
return True
|
||||
|
||||
def Stop(self, force = True):
|
||||
if not self.Playing:
|
||||
return False
|
||||
|
||||
if self.Process:
|
||||
try:
|
||||
self.Process.terminate()
|
||||
self.Process.kill()
|
||||
self.Process = None
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
|
||||
if self.Writer:
|
||||
if force:
|
||||
Socket = self.Writer.transport.get_extra_info("socket")
|
||||
if Socket:
|
||||
Socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
|
||||
struct.pack("ii", 1, 0))
|
||||
|
||||
self.Writer.transport.abort()
|
||||
|
||||
self.Writer.close()
|
||||
|
||||
self.Playing = False
|
||||
|
||||
self.Callback("Stop")
|
||||
del self.Callbacks
|
||||
|
||||
return True
|
||||
|
||||
def AddCallback(self, cbtype, cbfunc):
|
||||
if not cbtype in FFmpegAudioPlayerFactory.VALID_CALLBACKS:
|
||||
return False
|
||||
|
||||
self.Callbacks.append((cbtype, cbfunc))
|
||||
return True
|
||||
|
||||
def Callback(self, cbtype, *args, **kwargs):
|
||||
for Callback in self.Callbacks:
|
||||
if Callback[0] == cbtype:
|
||||
try:
|
||||
Callback[1](*args, **kwargs)
|
||||
except Exception as e:
|
||||
self.Master.Logger.error(traceback.format_exc())
|
||||
|
||||
async def _updater(self):
|
||||
LastSecondsElapsed = 0.0
|
||||
|
||||
while self.Playing:
|
||||
SecondsElapsed = time.time() - self.StartedPlaying
|
||||
|
||||
if SecondsElapsed > self.Seconds:
|
||||
SecondsElapsed = self.Seconds
|
||||
|
||||
self.Callback("Update", LastSecondsElapsed, SecondsElapsed)
|
||||
|
||||
if SecondsElapsed >= self.Seconds:
|
||||
if not self.StoppedPlaying:
|
||||
print("BUFFER UNDERRUN!")
|
||||
self.Stop(False)
|
||||
return
|
||||
|
||||
LastSecondsElapsed = SecondsElapsed
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
async def _read_stream(self, stream, writer):
|
||||
Started = False
|
||||
|
||||
while stream and self.Playing:
|
||||
Data = await stream.read(65536)
|
||||
|
||||
if Data:
|
||||
writer.write(Data)
|
||||
await writer.drain()
|
||||
|
||||
Bytes = len(Data)
|
||||
Samples = Bytes / SAMPLEBYTES
|
||||
Seconds = Samples / self.SampleRate
|
||||
|
||||
self.Seconds += Seconds
|
||||
|
||||
if not Started:
|
||||
Started = True
|
||||
self.Callback("Play")
|
||||
self.StartedPlaying = time.time()
|
||||
asyncio.ensure_future(self._updater())
|
||||
else:
|
||||
self.Process = None
|
||||
break
|
||||
|
||||
self.StoppedPlaying = time.time()
|
||||
|
||||
async def _stream_subprocess(self, cmd):
|
||||
if not self.Playing:
|
||||
return
|
||||
|
||||
_, self.Writer = await asyncio.open_connection(self.Host[0], self.Host[1])
|
||||
|
||||
Process = await asyncio.create_subprocess_exec(*cmd,
|
||||
stdout = asyncio.subprocess.PIPE, stderr = asyncio.subprocess.DEVNULL)
|
||||
self.Process = Process
|
||||
|
||||
await self._read_stream(Process.stdout, self.Writer)
|
||||
await Process.wait()
|
||||
|
||||
if self.Seconds == 0.0:
|
||||
self.Stop()
|
149
torchlight_changes_unloze/torchlight3/Torchlight/GameEvents.py
Executable file
149
torchlight_changes_unloze/torchlight3/Torchlight/GameEvents.py
Executable file
@ -0,0 +1,149 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import asyncio
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
class GameEvents():
|
||||
def __init__(self, master):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Torchlight = master
|
||||
|
||||
self.Callbacks = {}
|
||||
|
||||
def __del__(self):
|
||||
if not len(self.Callbacks) or not self.Torchlight():
|
||||
return
|
||||
|
||||
Obj = {
|
||||
"method": "unsubscribe",
|
||||
"module": "gameevents",
|
||||
"events": self.Callbacks.keys()
|
||||
}
|
||||
|
||||
asyncio.ensure_future(self.Torchlight().Send(Obj))
|
||||
|
||||
async def _Register(self, events):
|
||||
if type(events) is not list:
|
||||
events = [ events ]
|
||||
|
||||
Obj = {
|
||||
"method": "subscribe",
|
||||
"module": "gameevents",
|
||||
"events": events
|
||||
}
|
||||
|
||||
Res = await self.Torchlight().Send(Obj)
|
||||
|
||||
Ret = []
|
||||
for i, ret in enumerate(Res["events"]):
|
||||
if ret >= 0:
|
||||
Ret.append(True)
|
||||
if not events[i] in self.Callbacks:
|
||||
self.Callbacks[events[i]] = set()
|
||||
else:
|
||||
Ret.append(False)
|
||||
|
||||
if len(Ret) == 1:
|
||||
Ret = Ret[0]
|
||||
return Ret
|
||||
|
||||
async def _Unregister(self, events):
|
||||
if type(events) is not list:
|
||||
events = [ events ]
|
||||
|
||||
Obj = {
|
||||
"method": "unsubscribe",
|
||||
"module": "gameevents",
|
||||
"events": events
|
||||
}
|
||||
|
||||
Res = await self.Torchlight().Send(Obj)
|
||||
|
||||
Ret = []
|
||||
for i, ret in enumerate(Res["events"]):
|
||||
if ret >= 0:
|
||||
Ret.append(True)
|
||||
if events[i] in self.Callbacks:
|
||||
del self.Callbacks[events[i]]
|
||||
else:
|
||||
Ret.append(False)
|
||||
|
||||
if len(Ret) == 1:
|
||||
Ret = Ret[0]
|
||||
return Ret
|
||||
|
||||
def HookEx(self, event, callback):
|
||||
asyncio.ensure_future(self.Hook(event, callback))
|
||||
|
||||
def UnhookEx(self, event, callback):
|
||||
asyncio.ensure_future(self.Unhook(event, callback))
|
||||
|
||||
def ReplayEx(self, events):
|
||||
asyncio.ensure_future(self.Replay(events))
|
||||
|
||||
async def Hook(self, event, callback):
|
||||
if not event in self.Callbacks:
|
||||
if not await self._Register(event):
|
||||
return False
|
||||
|
||||
self.Callbacks[event].add(callback)
|
||||
return True
|
||||
|
||||
async def Unhook(self, event, callback):
|
||||
if not event in self.Callbacks:
|
||||
return True
|
||||
|
||||
if not callback in self.Callbacks[event]:
|
||||
return True
|
||||
|
||||
self.Callbacks[event].discard(callback)
|
||||
|
||||
if len(a) == 0:
|
||||
return await self._Unregister(event)
|
||||
|
||||
return True
|
||||
|
||||
async def Replay(self, events):
|
||||
if type(events) is not list:
|
||||
events = [ events ]
|
||||
|
||||
for event in events[:]:
|
||||
if not event in self.Callbacks:
|
||||
events.remove(event)
|
||||
|
||||
Obj = {
|
||||
"method": "replay",
|
||||
"module": "gameevents",
|
||||
"events": events
|
||||
}
|
||||
|
||||
Res = await self.Torchlight().Send(Obj)
|
||||
|
||||
Ret = []
|
||||
for i, ret in enumerate(Res["events"]):
|
||||
if ret >= 0:
|
||||
Ret.append(True)
|
||||
else:
|
||||
Ret.append(False)
|
||||
|
||||
if len(Ret) == 1:
|
||||
Ret = Ret[0]
|
||||
return Ret
|
||||
|
||||
def OnPublish(self, obj):
|
||||
Event = obj["event"]
|
||||
|
||||
if not Event["name"] in self.Callbacks:
|
||||
return False
|
||||
|
||||
Callbacks = self.Callbacks[Event["name"]]
|
||||
|
||||
for Callback in Callbacks:
|
||||
try:
|
||||
Callback(**Event["data"])
|
||||
except Exception as e:
|
||||
self.Logger.error(traceback.format_exc())
|
||||
self.Logger.error(Event)
|
||||
|
||||
return True
|
201
torchlight_changes_unloze/torchlight3/Torchlight/PlayerManager.py
Executable file
201
torchlight_changes_unloze/torchlight3/Torchlight/PlayerManager.py
Executable file
@ -0,0 +1,201 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import asyncio
|
||||
import logging
|
||||
import numpy
|
||||
from .Constants import *
|
||||
|
||||
class PlayerManager():
|
||||
def __init__(self, master):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Torchlight = master
|
||||
|
||||
self.Players = numpy.empty(MAXPLAYERS + 1, dtype = object)
|
||||
self.Storage = self.StorageManager(self)
|
||||
|
||||
self.Torchlight().GameEvents.HookEx("player_connect", self.Event_PlayerConnect)
|
||||
self.Torchlight().GameEvents.HookEx("player_activate", self.Event_PlayerActivate)
|
||||
self.Torchlight().Forwards.HookEx("OnClientPostAdminCheck", self.OnClientPostAdminCheck)
|
||||
self.Torchlight().GameEvents.HookEx("player_info", self.Event_PlayerInfo)
|
||||
self.Torchlight().GameEvents.HookEx("player_disconnect", self.Event_PlayerDisconnect)
|
||||
self.Torchlight().GameEvents.HookEx("server_spawn", self.Event_ServerSpawn)
|
||||
|
||||
def Event_PlayerConnect(self, name, index, userid, networkid, address, bot):
|
||||
index += 1
|
||||
self.Logger.info("OnConnect(name={0}, index={1}, userid={2}, networkid={3}, address={4}, bot={5})"
|
||||
.format(name, index, userid, networkid, address, bot))
|
||||
if self.Players[index] != None:
|
||||
self.Logger.error("!!! Player already exists, overwriting !!!")
|
||||
|
||||
self.Players[index] = self.Player(self, index, userid, networkid, address, name)
|
||||
self.Players[index].OnConnect()
|
||||
|
||||
def Event_PlayerActivate(self, userid):
|
||||
self.Logger.info("Pre_OnActivate(userid={0})".format(userid))
|
||||
index = self.FindUserID(userid).Index
|
||||
self.Logger.info("OnActivate(index={0}, userid={1})".format(index, userid))
|
||||
|
||||
self.Players[index].OnActivate()
|
||||
|
||||
def OnClientPostAdminCheck(self, client):
|
||||
self.Logger.info("OnClientPostAdminCheck(client={0})".format(client))
|
||||
|
||||
asyncio.ensure_future(self.Players[client].OnClientPostAdminCheck())
|
||||
|
||||
def Event_PlayerInfo(self, name, index, userid, networkid, bot):
|
||||
index += 1
|
||||
self.Logger.info("OnInfo(name={0}, index={1}, userid={2}, networkid={3}, bot={4})"
|
||||
.format(name, index, userid, networkid, bot))
|
||||
|
||||
# We've connected to the server and receive info events about the already connected players
|
||||
# Emulate connect message
|
||||
if not self.Players[index]:
|
||||
self.Event_PlayerConnect(name, index - 1, userid, networkid, bot)
|
||||
else:
|
||||
self.Players[index].OnInfo(name)
|
||||
|
||||
def Event_PlayerDisconnect(self, userid, reason, name, networkid, bot):
|
||||
index = self.FindUserID(userid).Index
|
||||
self.Logger.info("OnDisconnect(index={0}, userid={1}, reason={2}, name={3}, networkid={4}, bot={5})"
|
||||
.format(index, userid, reason, name, networkid, bot))
|
||||
|
||||
self.Players[index].OnDisconnect(reason)
|
||||
self.Players[index] = None
|
||||
|
||||
def Event_ServerSpawn(self, hostname, address, ip, port, game, mapname, maxplayers, os, dedicated, password):
|
||||
self.Logger.info("ServerSpawn(mapname={0})"
|
||||
.format(mapname))
|
||||
|
||||
self.Storage.Reset()
|
||||
|
||||
for i in range(1, self.Players.size):
|
||||
if self.Players[i]:
|
||||
self.Players[i].OnDisconnect("mapchange")
|
||||
self.Players[i].OnConnect()
|
||||
|
||||
def FindUniqueID(self, uniqueid):
|
||||
for Player in self.Players:
|
||||
if Player and Player.UniqueID == uniqueid:
|
||||
return Player
|
||||
|
||||
def FindUserID(self, userid):
|
||||
for Player in self.Players:
|
||||
if Player and Player.UserID == userid:
|
||||
return Player
|
||||
|
||||
def FindName(self, name):
|
||||
for Player in self.Players:
|
||||
if Player and Player.Name == name:
|
||||
return Player
|
||||
|
||||
def __len__(self):
|
||||
Count = 0
|
||||
for i in range(1, self.Players.size):
|
||||
if self.Players[i]:
|
||||
Count += 1
|
||||
return Count
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key > 0 and key <= MAXPLAYERS:
|
||||
self.Players[key] = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key > 0 and key <= MAXPLAYERS:
|
||||
return self.Players[key]
|
||||
|
||||
def __iter__(self):
|
||||
for i in range(1, self.Players.size):
|
||||
if self.Players[i]:
|
||||
yield self.Players[i]
|
||||
|
||||
class StorageManager():
|
||||
def __init__(self, master):
|
||||
self.PlayerManager = master
|
||||
self.Storage = dict()
|
||||
|
||||
def Reset(self):
|
||||
self.Storage = dict()
|
||||
|
||||
def __getitem__(self, key):
|
||||
if not key in self.Storage:
|
||||
self.Storage[key] = dict()
|
||||
|
||||
return self.Storage[key]
|
||||
|
||||
class Admin():
|
||||
def __init__(self):
|
||||
self._FlagBits = 0
|
||||
|
||||
def FlagBits(self):
|
||||
return self._FlagBits
|
||||
|
||||
def Reservation(self): return (self._FlagBits & ADMFLAG_RESERVATION)
|
||||
def Generic(self): return (self._FlagBits & ADMFLAG_GENERIC)
|
||||
def Kick(self): return (self._FlagBits & ADMFLAG_KICK)
|
||||
def Ban(self): return (self._FlagBits & ADMFLAG_BAN)
|
||||
def Unban(self): return (self._FlagBits & ADMFLAG_UNBAN)
|
||||
def Slay(self): return (self._FlagBits & ADMFLAG_SLAY)
|
||||
def Changemap(self): return (self._FlagBits & ADMFLAG_CHANGEMAP)
|
||||
def Convars(self): return (self._FlagBits & ADMFLAG_CONVARS)
|
||||
def Config(self): return (self._FlagBits & ADMFLAG_CONFIG)
|
||||
def Chat(self): return (self._FlagBits & ADMFLAG_CHAT)
|
||||
def Vote(self): return (self._FlagBits & ADMFLAG_VOTE)
|
||||
def Password(self): return (self._FlagBits & ADMFLAG_PASSWORD)
|
||||
def RCON(self): return (self._FlagBits & ADMFLAG_RCON)
|
||||
def Cheats(self): return (self._FlagBits & ADMFLAG_CHEATS)
|
||||
def Root(self): return (self._FlagBits & ADMFLAG_ROOT)
|
||||
def Custom1(self): return (self._FlagBits & ADMFLAG_CUSTOM1)
|
||||
def Custom2(self): return (self._FlagBits & ADMFLAG_CUSTOM2)
|
||||
def Custom3(self): return (self._FlagBits & ADMFLAG_CUSTOM3)
|
||||
def Custom4(self): return (self._FlagBits & ADMFLAG_CUSTOM4)
|
||||
def Custom5(self): return (self._FlagBits & ADMFLAG_CUSTOM5)
|
||||
def Custom6(self): return (self._FlagBits & ADMFLAG_CUSTOM6)
|
||||
|
||||
class Player():
|
||||
def __init__(self, master, index, userid, uniqueid, address, name):
|
||||
self.PlayerManager = master
|
||||
self.Torchlight = self.PlayerManager.Torchlight
|
||||
self.Index = index
|
||||
self.UserID = userid
|
||||
self.UniqueID = uniqueid
|
||||
self.Address = address
|
||||
self.Name = name
|
||||
self.Access = None
|
||||
self.Admin = self.PlayerManager.Admin()
|
||||
self.Storage = None
|
||||
self.Active = False
|
||||
self.ChatCooldown = 0
|
||||
|
||||
def OnConnect(self):
|
||||
self.Storage = self.PlayerManager.Storage[self.UniqueID]
|
||||
|
||||
if not "Audio" in self.Storage:
|
||||
self.Storage["Audio"] = dict({"Uses": 0, "LastUse": 0.0, "LastUseLength": 0.0, "TimeUsed": 0.0})
|
||||
|
||||
self.Access = self.Torchlight().Access[self.UniqueID]
|
||||
|
||||
def OnActivate(self):
|
||||
self.Active = True
|
||||
|
||||
async def OnClientPostAdminCheck(self):
|
||||
self.Admin._FlagBits = (await self.Torchlight().API.GetUserFlagBits(self.Index))["result"]
|
||||
self.PlayerManager.Logger.info("#{0} \"{1}\"({2}) FlagBits: {3}".format(self.UserID, self.Name, self.UniqueID, self.Admin._FlagBits))
|
||||
if not self.Access:
|
||||
if self.Admin.RCON():
|
||||
self.Access = dict({"level": 6, "name": "SAdmin"})
|
||||
elif self.Admin.Generic():
|
||||
self.Access = dict({"level": 3, "name": "Admin"})
|
||||
elif self.Admin.Custom1():
|
||||
self.Access = dict({"level": 1, "name": "VIP"})
|
||||
|
||||
if self.PlayerManager.Torchlight().Config["DefaultLevel"]:
|
||||
if self.Access and self.Access["level"] < self.PlayerManager.Torchlight().Config["DefaultLevel"]:
|
||||
self.Access = dict({"level": self.PlayerManager.Torchlight().Config["DefaultLevel"], "name": "Default"})
|
||||
|
||||
def OnInfo(self, name):
|
||||
self.Name = name
|
||||
|
||||
def OnDisconnect(self, message):
|
||||
self.Active = False
|
||||
self.Storage = None
|
||||
self.Torchlight().AudioManager.OnDisconnect(self)
|
27
torchlight_changes_unloze/torchlight3/Torchlight/SourceModAPI.py
Executable file
27
torchlight_changes_unloze/torchlight3/Torchlight/SourceModAPI.py
Executable file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import functools
|
||||
|
||||
class SourceModAPI:
|
||||
def __init__(self, master):
|
||||
self.Torchlight = master
|
||||
|
||||
def __getattr__(self, attr):
|
||||
try:
|
||||
return super(SourceModAPI, self).__getattr__(attr)
|
||||
except AttributeError:
|
||||
return functools.partial(self._MakeCall, attr)
|
||||
|
||||
async def _MakeCall(self, function, *args, **kwargs):
|
||||
Obj = {
|
||||
"method": "function",
|
||||
"function": function,
|
||||
"args": args
|
||||
}
|
||||
|
||||
Res = await self.Torchlight().Send(Obj)
|
||||
|
||||
if Res["error"]:
|
||||
raise Exception("{0}({1})\n{2}".format(function, args, Res["error"]))
|
||||
|
||||
return Res
|
106
torchlight_changes_unloze/torchlight3/Torchlight/SourceRCONServer.py
Executable file
106
torchlight_changes_unloze/torchlight3/Torchlight/SourceRCONServer.py
Executable file
@ -0,0 +1,106 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import asyncio
|
||||
import sys
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
import traceback
|
||||
from importlib import reload
|
||||
from .PlayerManager import PlayerManager
|
||||
|
||||
class SourceRCONServer():
|
||||
class SourceRCONClient():
|
||||
def __init__(self, Server, Socket, Name):
|
||||
self.Loop = Server.Loop
|
||||
self.Server = Server
|
||||
self._sock = Socket
|
||||
self.Name = Name
|
||||
self.Authenticated = False
|
||||
asyncio.Task(self._peer_handler())
|
||||
|
||||
def send(self, data):
|
||||
return self.Loop.sock_sendall(self._sock, data)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _peer_handler(self):
|
||||
try:
|
||||
yield from self._peer_loop()
|
||||
except IOError:
|
||||
pass
|
||||
finally:
|
||||
self.Server.Remove(self)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _peer_loop(self):
|
||||
while True:
|
||||
Data = yield from self.Loop.sock_recv(self._sock, 1024)
|
||||
if Data == b'':
|
||||
break
|
||||
|
||||
while Data:
|
||||
p_size = struct.unpack("<l", Data[:4])[0]
|
||||
if len(Data) < p_size+4:
|
||||
break
|
||||
self.ParsePacket(Data[:p_size+4])
|
||||
Data = Data[p_size+4:]
|
||||
|
||||
def p_send(self, p_id, p_type, p_body):
|
||||
Data = struct.pack('<l', p_id) + struct.pack('<l', p_type) + p_body.encode("UTF-8") + b'\x00\x00'
|
||||
self.send(struct.pack('<l', len(Data)) + Data)
|
||||
|
||||
def ParsePacket(self, Data):
|
||||
p_size, p_id, p_type = struct.unpack('<lll', Data[:12])
|
||||
Data = Data[12:p_size+2].decode(encoding="UTF-8", errors="ignore").split('\x00')[0]
|
||||
|
||||
if not self.Authenticated:
|
||||
if p_type == 3:
|
||||
if Data == self.Server.Password:
|
||||
self.Authenticated = True
|
||||
self.Server.Logger.info(sys._getframe().f_code.co_name + " Connection authenticated from {0}".format(self.Name))
|
||||
self.p_send(p_id, 0 , '')
|
||||
self.p_send(p_id, 2 , '')
|
||||
self.p_send(p_id, 0, "Welcome to torchlight! - Authenticated!\n")
|
||||
else:
|
||||
self.Server.Logger.info(sys._getframe().f_code.co_name + " Connection denied from {0}".format(self.Name))
|
||||
self.p_send(p_id, 0 , '')
|
||||
self.p_send(-1, 2 , '')
|
||||
self._sock.close()
|
||||
"""else:
|
||||
if p_type == 2:
|
||||
if Data:
|
||||
Data = Data.strip('"')
|
||||
self.Server.Logger.info(sys._getframe().f_code.co_name + " Exec: \"{0}\"".format(Data))
|
||||
Player = PlayerManager.Player(self.Server.TorchlightHandler.Torchlight.Players, 0, 0, "[CONSOLE]", "127.0.0.1", "CONSOLE")
|
||||
Player.Access = dict({"name": "CONSOLE", "level": 9001})
|
||||
Player.Storage = dict({"Audio": {"Uses": 0, "LastUse": 0.0, "LastUseLength": 0.0, "TimeUsed": 0.0}})
|
||||
asyncio.Task(self.Server.TorchlightHandler.Torchlight.CommandHandler.HandleCommand(Data, Player))
|
||||
#self.p_send(p_id, 0, self._server.torchlight.GetLine())
|
||||
"""
|
||||
|
||||
def __init__(self, Loop, TorchlightHandler, Host="", Port=27015, Password="secret"):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Loop = Loop
|
||||
self._serv_sock = socket.socket()
|
||||
self._serv_sock.setblocking(0)
|
||||
self._serv_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._serv_sock.bind((Host, Port))
|
||||
self._serv_sock.listen(5)
|
||||
self.Peers = []
|
||||
self.TorchlightHandler = TorchlightHandler
|
||||
self.Password = Password
|
||||
"""asyncio.Task(self._server())
|
||||
|
||||
def Remove(self, Peer):
|
||||
self.Logger.info(sys._getframe().f_code.co_name + " Peer {0} disconnected!".format(Peer.Name))
|
||||
self.Peers.remove(Peer)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _server(self):
|
||||
while True:
|
||||
PeerSocket, PeerName = yield from self.Loop.sock_accept(self._serv_sock)
|
||||
PeerSocket.setblocking(0)
|
||||
Peer = self.SourceRCONClient(self, PeerSocket, PeerName)
|
||||
self.Peers.append(Peer)
|
||||
self.Logger.info(sys._getframe().f_code.co_name + " Peer {0} connected!".format(Peer.Name))"""
|
159
torchlight_changes_unloze/torchlight3/Torchlight/Subscribe.py
Executable file
159
torchlight_changes_unloze/torchlight3/Torchlight/Subscribe.py
Executable file
@ -0,0 +1,159 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import asyncio
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
class SubscribeBase():
|
||||
def __init__(self, master, module):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Torchlight = master
|
||||
self.Module = module
|
||||
|
||||
self.Callbacks = {}
|
||||
|
||||
def __del__(self):
|
||||
if not len(self.Callbacks) or not self.Torchlight():
|
||||
return
|
||||
|
||||
Obj = {
|
||||
"method": "unsubscribe",
|
||||
"module": self.Module,
|
||||
"events": self.Callbacks.keys()
|
||||
}
|
||||
|
||||
asyncio.ensure_future(self.Torchlight().Send(Obj))
|
||||
|
||||
async def _Register(self, events):
|
||||
if type(events) is not list:
|
||||
events = [ events ]
|
||||
|
||||
Obj = {
|
||||
"method": "subscribe",
|
||||
"module": self.Module,
|
||||
"events": events
|
||||
}
|
||||
|
||||
Res = await self.Torchlight().Send(Obj)
|
||||
|
||||
Ret = []
|
||||
for i, ret in enumerate(Res["events"]):
|
||||
if ret >= 0:
|
||||
Ret.append(True)
|
||||
if not events[i] in self.Callbacks:
|
||||
self.Callbacks[events[i]] = set()
|
||||
else:
|
||||
Ret.append(False)
|
||||
|
||||
if len(Ret) == 1:
|
||||
Ret = Ret[0]
|
||||
return Ret
|
||||
|
||||
async def _Unregister(self, events):
|
||||
if type(events) is not list:
|
||||
events = [ events ]
|
||||
|
||||
Obj = {
|
||||
"method": "unsubscribe",
|
||||
"module": self.Module,
|
||||
"events": events
|
||||
}
|
||||
|
||||
Res = await self.Torchlight().Send(Obj)
|
||||
|
||||
Ret = []
|
||||
for i, ret in enumerate(Res["events"]):
|
||||
if ret >= 0:
|
||||
Ret.append(True)
|
||||
if events[i] in self.Callbacks:
|
||||
del self.Callbacks[events[i]]
|
||||
else:
|
||||
Ret.append(False)
|
||||
|
||||
if len(Ret) == 1:
|
||||
Ret = Ret[0]
|
||||
return Ret
|
||||
|
||||
def HookEx(self, event, callback):
|
||||
asyncio.ensure_future(self.Hook(event, callback))
|
||||
|
||||
def UnhookEx(self, event, callback):
|
||||
asyncio.ensure_future(self.Unhook(event, callback))
|
||||
|
||||
def ReplayEx(self, events):
|
||||
asyncio.ensure_future(self.Replay(events))
|
||||
|
||||
async def Hook(self, event, callback):
|
||||
if not event in self.Callbacks:
|
||||
if not await self._Register(event):
|
||||
return False
|
||||
|
||||
self.Callbacks[event].add(callback)
|
||||
return True
|
||||
|
||||
async def Unhook(self, event, callback):
|
||||
if not event in self.Callbacks:
|
||||
return True
|
||||
|
||||
if not callback in self.Callbacks[event]:
|
||||
return True
|
||||
|
||||
self.Callbacks[event].discard(callback)
|
||||
|
||||
if len(a) == 0:
|
||||
return await self._Unregister(event)
|
||||
|
||||
return True
|
||||
|
||||
async def Replay(self, events):
|
||||
if type(events) is not list:
|
||||
events = [ events ]
|
||||
|
||||
for event in events[:]:
|
||||
if not event in self.Callbacks:
|
||||
events.remove(event)
|
||||
|
||||
Obj = {
|
||||
"method": "replay",
|
||||
"module": self.Module,
|
||||
"events": events
|
||||
}
|
||||
|
||||
Res = await self.Torchlight().Send(Obj)
|
||||
|
||||
Ret = []
|
||||
for i, ret in enumerate(Res["events"]):
|
||||
if ret >= 0:
|
||||
Ret.append(True)
|
||||
else:
|
||||
Ret.append(False)
|
||||
|
||||
if len(Ret) == 1:
|
||||
Ret = Ret[0]
|
||||
return Ret
|
||||
|
||||
def OnPublish(self, obj):
|
||||
Event = obj["event"]
|
||||
|
||||
if not Event["name"] in self.Callbacks:
|
||||
return False
|
||||
|
||||
Callbacks = self.Callbacks[Event["name"]]
|
||||
|
||||
for Callback in Callbacks:
|
||||
try:
|
||||
Callback(**Event["data"])
|
||||
except Exception as e:
|
||||
self.Logger.error(traceback.format_exc())
|
||||
self.Logger.error(Event)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class GameEvents(SubscribeBase):
|
||||
def __init__(self, master):
|
||||
super().__init__(master, "gameevents")
|
||||
|
||||
class Forwards(SubscribeBase):
|
||||
def __init__(self, master):
|
||||
super().__init__(master, "forwards")
|
149
torchlight_changes_unloze/torchlight3/Torchlight/Torchlight.py
Executable file
149
torchlight_changes_unloze/torchlight3/Torchlight/Torchlight.py
Executable file
@ -0,0 +1,149 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import asyncio
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import weakref
|
||||
import traceback
|
||||
import textwrap
|
||||
|
||||
from .AsyncClient import AsyncClient
|
||||
|
||||
from .SourceModAPI import SourceModAPI
|
||||
from .Subscribe import GameEvents, Forwards
|
||||
|
||||
from .Utils import Utils
|
||||
from .Config import Config
|
||||
from .CommandHandler import CommandHandler
|
||||
from .AccessManager import AccessManager
|
||||
from .PlayerManager import PlayerManager
|
||||
from .AudioManager import AudioManager
|
||||
|
||||
class Torchlight():
|
||||
def __init__(self, master):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Master = master
|
||||
self.Config = self.Master.Config
|
||||
self.WeakSelf = weakref.ref(self)
|
||||
|
||||
self.API = SourceModAPI(self.WeakSelf)
|
||||
self.GameEvents = GameEvents(self.WeakSelf)
|
||||
self.Forwards = Forwards(self.WeakSelf)
|
||||
|
||||
self.DisableVotes = set()
|
||||
self.Disabled = 0
|
||||
self.LastUrl = None
|
||||
|
||||
def InitModules(self):
|
||||
self.Access = AccessManager()
|
||||
self.Access.Load()
|
||||
|
||||
self.Players = PlayerManager(self.WeakSelf)
|
||||
|
||||
self.AudioManager = AudioManager(self.WeakSelf)
|
||||
|
||||
self.CommandHandler = CommandHandler(self.WeakSelf)
|
||||
self.CommandHandler.Setup()
|
||||
|
||||
self.GameEvents.HookEx("server_spawn", self.Event_ServerSpawn)
|
||||
self.GameEvents.HookEx("player_say", self.Event_PlayerSay)
|
||||
|
||||
def SayChat(self, message, player=None):
|
||||
message = "\x0700FFFA[Torchlight]: \x01{0}".format(message)
|
||||
if len(message) > 976:
|
||||
message = message[:973] + "..."
|
||||
lines = textwrap.wrap(message, 244, break_long_words = True)
|
||||
for line in lines:
|
||||
asyncio.ensure_future(self.API.PrintToChatAll(line))
|
||||
|
||||
if player:
|
||||
Level = 0
|
||||
if player.Access:
|
||||
Level = player.Access["level"]
|
||||
|
||||
if Level < self.Config["AntiSpam"]["ImmunityLevel"]:
|
||||
cooldown = len(lines) * self.Config["AntiSpam"]["ChatCooldown"]
|
||||
if player.ChatCooldown > self.Master.Loop.time():
|
||||
player.ChatCooldown += cooldown
|
||||
else:
|
||||
player.ChatCooldown = self.Master.Loop.time() + cooldown
|
||||
|
||||
def SayPrivate(self, player, message):
|
||||
message = "\x0700FFFA[Torchlight]: \x01{0}".format(message)
|
||||
if len(message) > 976:
|
||||
message = message[:973] + "..."
|
||||
lines = textwrap.wrap(message, 244, break_long_words = True)
|
||||
for line in lines:
|
||||
asyncio.ensure_future(self.API.PrintToChat(player.Index, line))
|
||||
|
||||
def Reload(self):
|
||||
self.Config.Load()
|
||||
self.CommandHandler.NeedsReload = True
|
||||
|
||||
async def Send(self, data):
|
||||
return await self.Master.Send(data)
|
||||
|
||||
def OnPublish(self, obj):
|
||||
if obj["module"] == "gameevents":
|
||||
self.GameEvents.OnPublish(obj)
|
||||
elif obj["module"] == "forwards":
|
||||
self.Forwards.OnPublish(obj)
|
||||
|
||||
def Event_ServerSpawn(self, hostname, address, ip, port, game, mapname, maxplayers, os, dedicated, password):
|
||||
self.DisableVotes = set()
|
||||
self.Disabled = 0
|
||||
|
||||
def Event_PlayerSay(self, userid, text):
|
||||
if userid == 0:
|
||||
return
|
||||
|
||||
Player = self.Players.FindUserID(userid)
|
||||
asyncio.ensure_future(self.CommandHandler.HandleCommand(text, Player))
|
||||
|
||||
def __del__(self):
|
||||
self.Logger.debug("~Torchlight()")
|
||||
|
||||
|
||||
class TorchlightHandler():
|
||||
def __init__(self, loop):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Loop = loop if loop else asyncio.get_event_loop()
|
||||
self._Client = None
|
||||
self.Torchlight = None
|
||||
self.Config = Config()
|
||||
|
||||
asyncio.ensure_future(self._Connect(), loop = self.Loop)
|
||||
|
||||
async def _Connect(self):
|
||||
# Connect to API
|
||||
self._Client = AsyncClient(self.Loop, self.Config["SMAPIServer"]["Host"], self.Config["SMAPIServer"]["Port"], self)
|
||||
await self._Client.Connect()
|
||||
|
||||
self.Torchlight = Torchlight(self)
|
||||
|
||||
# Pre Hook for late load
|
||||
await self.Torchlight.GameEvents._Register(["player_connect", "player_activate"])
|
||||
await self.Torchlight.Forwards._Register(["OnClientPostAdminCheck"])
|
||||
|
||||
self.Torchlight.InitModules()
|
||||
|
||||
# Late load
|
||||
await self.Torchlight.GameEvents.Replay(["player_connect", "player_activate"])
|
||||
await self.Torchlight.Forwards.Replay(["OnClientPostAdminCheck"])
|
||||
|
||||
async def Send(self, data):
|
||||
return await self._Client.Send(data)
|
||||
|
||||
def OnPublish(self, obj):
|
||||
self.Torchlight.OnPublish(obj)
|
||||
|
||||
def OnDisconnect(self, exc):
|
||||
self.Logger.info("OnDisconnect({0})".format(exc))
|
||||
self.Torchlight = None
|
||||
|
||||
asyncio.ensure_future(self._Connect(), loop = self.Loop)
|
||||
|
||||
def __del__(self):
|
||||
self.Logger.debug("~TorchlightHandler()")
|
150
torchlight_changes_unloze/torchlight3/Torchlight/Torchlightmg.py
Executable file
150
torchlight_changes_unloze/torchlight3/Torchlight/Torchlightmg.py
Executable file
@ -0,0 +1,150 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import asyncio
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import weakref
|
||||
import traceback
|
||||
import textwrap
|
||||
|
||||
from .AsyncClient import AsyncClient
|
||||
|
||||
from .SourceModAPI import SourceModAPI
|
||||
from .Subscribe import GameEvents, Forwards
|
||||
|
||||
from .Utils import Utils
|
||||
from .Config import Config
|
||||
from .CommandHandlermg import CommandHandlermg
|
||||
from .AccessManager import AccessManager
|
||||
from .PlayerManager import PlayerManager
|
||||
from .AudioManager import AudioManager
|
||||
|
||||
class Torchlight():
|
||||
def __init__(self, master):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Master = master
|
||||
self.Config = self.Master.Config
|
||||
self.WeakSelf = weakref.ref(self)
|
||||
|
||||
self.API = SourceModAPI(self.WeakSelf)
|
||||
self.GameEvents = GameEvents(self.WeakSelf)
|
||||
self.Forwards = Forwards(self.WeakSelf)
|
||||
|
||||
self.DisableVotes = set()
|
||||
self.Disabled = 0
|
||||
self.LastUrl = None
|
||||
|
||||
def InitModules(self):
|
||||
self.Access = AccessManager()
|
||||
self.Access.Load()
|
||||
|
||||
self.Players = PlayerManager(self.WeakSelf)
|
||||
|
||||
self.AudioManager = AudioManager(self.WeakSelf)
|
||||
|
||||
self.CommandHandler = CommandHandlermg(self.WeakSelf)
|
||||
self.CommandHandler.Setup()
|
||||
|
||||
self.GameEvents.HookEx("server_spawn", self.Event_ServerSpawn)
|
||||
self.GameEvents.HookEx("player_say", self.Event_PlayerSay)
|
||||
|
||||
def SayChat(self, message, player=None):
|
||||
message = "\x0700FFFA[Torchlight]: \x01{0}".format(message)
|
||||
if len(message) > 976:
|
||||
message = message[:973] + "..."
|
||||
lines = textwrap.wrap(message, 244, break_long_words = True)
|
||||
for line in lines:
|
||||
asyncio.ensure_future(self.API.PrintToChatAll(line))
|
||||
|
||||
if player:
|
||||
Level = 0
|
||||
if player.Access:
|
||||
Level = player.Access["level"]
|
||||
|
||||
if Level < self.Config["AntiSpam"]["ImmunityLevel"]:
|
||||
cooldown = len(lines) * self.Config["AntiSpam"]["ChatCooldown"]
|
||||
if player.ChatCooldown > self.Master.Loop.time():
|
||||
player.ChatCooldown += cooldown
|
||||
else:
|
||||
player.ChatCooldown = self.Master.Loop.time() + cooldown
|
||||
|
||||
def SayPrivate(self, player, message):
|
||||
message = "\x0700FFFA[Torchlight]: \x01{0}".format(message)
|
||||
if len(message) > 976:
|
||||
message = message[:973] + "..."
|
||||
lines = textwrap.wrap(message, 244, break_long_words = True)
|
||||
for line in lines:
|
||||
asyncio.ensure_future(self.API.PrintToChat(player.Index, line))
|
||||
|
||||
def Reload(self):
|
||||
self.Config.Load()
|
||||
self.CommandHandler.NeedsReload = True
|
||||
|
||||
async def Send(self, data):
|
||||
return await self.Master.Send(data)
|
||||
|
||||
def OnPublish(self, obj):
|
||||
if obj["module"] == "gameevents":
|
||||
self.GameEvents.OnPublish(obj)
|
||||
elif obj["module"] == "forwards":
|
||||
self.Forwards.OnPublish(obj)
|
||||
|
||||
def Event_ServerSpawn(self, hostname, address, ip, port, game, mapname, maxplayers, os, dedicated, password):
|
||||
self.DisableVotes = set()
|
||||
self.Disabled = 0
|
||||
|
||||
def Event_PlayerSay(self, userid, text):
|
||||
if userid == 0:
|
||||
return
|
||||
|
||||
Player = self.Players.FindUserID(userid)
|
||||
asyncio.ensure_future(self.CommandHandler.HandleCommand(text, Player))
|
||||
|
||||
def __del__(self):
|
||||
self.Logger.debug("~Torchlight()")
|
||||
|
||||
|
||||
class TorchlightHandler():
|
||||
def __init__(self, loop):
|
||||
self.Logger = logging.getLogger(__class__.__name__)
|
||||
self.Loop = loop if loop else asyncio.get_event_loop()
|
||||
self._Client = None
|
||||
self.Torchlight = None
|
||||
self.Config = Config()
|
||||
|
||||
asyncio.ensure_future(self._Connect(), loop = self.Loop)
|
||||
|
||||
async def _Connect(self):
|
||||
# Connect to API
|
||||
self._Client = AsyncClient(self.Loop, self.Config["SMAPIServer"]["Host"], self.Config["SMAPIServer"]["Port"], self)
|
||||
await self._Client.Connect()
|
||||
|
||||
self.Torchlight = Torchlight(self)
|
||||
|
||||
# Pre Hook for late load
|
||||
await self.Torchlight.GameEvents._Register(["player_connect", "player_activate"])
|
||||
await self.Torchlight.Forwards._Register(["OnClientPostAdminCheck"])
|
||||
|
||||
self.Torchlight.InitModules()
|
||||
|
||||
# Late load
|
||||
await self.Torchlight.GameEvents.Replay(["player_connect", "player_activate"])
|
||||
await self.Torchlight.Forwards.Replay(["OnClientPostAdminCheck"])
|
||||
|
||||
async def Send(self, data):
|
||||
return await self._Client.Send(data)
|
||||
|
||||
def OnPublish(self, obj):
|
||||
self.Torchlight.OnPublish(obj)
|
||||
|
||||
def OnDisconnect(self, exc):
|
||||
self.Logger.info("OnDisconnect({0})".format(exc))
|
||||
self.Torchlight = None
|
||||
|
||||
asyncio.ensure_future(self._Connect(), loop = self.Loop)
|
||||
|
||||
def __del__(self):
|
||||
self.Logger.debug("~TorchlightHandler()")
|
||||
|
94
torchlight_changes_unloze/torchlight3/Torchlight/Utils.py
Executable file
94
torchlight_changes_unloze/torchlight3/Torchlight/Utils.py
Executable file
@ -0,0 +1,94 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import math
|
||||
|
||||
class DataHolder:
|
||||
def __init__(self, value=None, attr_name='value'):
|
||||
self._attr_name = attr_name
|
||||
self.set(value)
|
||||
def __call__(self, value):
|
||||
return self.set(value)
|
||||
def set(self, value):
|
||||
setattr(self, self._attr_name, value)
|
||||
return value
|
||||
def get(self):
|
||||
return getattr(self, self._attr_name)
|
||||
|
||||
class Utils():
|
||||
@staticmethod
|
||||
def GetNum(Text):
|
||||
Ret = ''
|
||||
for c in Text:
|
||||
if c.isdigit():
|
||||
Ret += c
|
||||
elif Ret:
|
||||
break
|
||||
elif c == '-':
|
||||
Ret += c
|
||||
|
||||
return Ret
|
||||
|
||||
@staticmethod
|
||||
def ParseTime(TimeStr):
|
||||
Negative = False
|
||||
Time = 0
|
||||
|
||||
while TimeStr:
|
||||
Val = Utils.GetNum(TimeStr)
|
||||
if not Val:
|
||||
break
|
||||
|
||||
Val = int(Val)
|
||||
if not Val:
|
||||
break
|
||||
|
||||
if Val < 0:
|
||||
TimeStr = TimeStr[1:]
|
||||
if Time == 0:
|
||||
Negative = True
|
||||
Val = abs(Val)
|
||||
|
||||
ValLen = int(math.log10(Val)) + 1
|
||||
if len(TimeStr) > ValLen:
|
||||
Mult = TimeStr[ValLen].lower()
|
||||
TimeStr = TimeStr[ValLen + 1:]
|
||||
if Mult == 'h':
|
||||
Val *= 3600
|
||||
elif Mult == 'm':
|
||||
Val *= 60
|
||||
else:
|
||||
TimeStr = None
|
||||
|
||||
Time += Val
|
||||
|
||||
if Negative:
|
||||
return -Time
|
||||
else:
|
||||
return Time
|
||||
|
||||
|
||||
@staticmethod
|
||||
def HumanSize(size_bytes):
|
||||
"""
|
||||
format a size in bytes into a 'human' file size, e.g. bytes, KB, MB, GB, TB, PB
|
||||
Note that bytes/KB will be reported in whole numbers but MB and above will have greater precision
|
||||
e.g. 1 byte, 43 bytes, 443 KB, 4.3 MB, 4.43 GB, etc
|
||||
"""
|
||||
if size_bytes == 1:
|
||||
# because I really hate unnecessary plurals
|
||||
return "1 byte"
|
||||
|
||||
suffixes_table = [('bytes', 0),('KB', 0),('MB', 1),('GB', 2),('TB', 2), ('PB', 2)]
|
||||
|
||||
num = float(size_bytes)
|
||||
for suffix, precision in suffixes_table:
|
||||
if num < 1024.0:
|
||||
break
|
||||
num /= 1024.0
|
||||
|
||||
if precision == 0:
|
||||
formatted_size = str(int(num))
|
||||
else:
|
||||
formatted_size = str(round(num, ndigits=precision))
|
||||
|
||||
return "{0}{1}".format(formatted_size, suffix)
|
2
torchlight_changes_unloze/torchlight3/Torchlight/__init__.py
Executable file
2
torchlight_changes_unloze/torchlight3/Torchlight/__init__.py
Executable file
@ -0,0 +1,2 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
4
torchlight_changes_unloze/torchlight3/_start.sh
Executable file
4
torchlight_changes_unloze/torchlight3/_start.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
source venv/bin/activate
|
||||
python3 main.py
|
4
torchlight_changes_unloze/torchlight3/_start_mg.sh
Executable file
4
torchlight_changes_unloze/torchlight3/_start_mg.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
source venv/bin/activate
|
||||
python3 mainmg.py config-mg.json
|
22
torchlight_changes_unloze/torchlight3/access.json
Executable file
22
torchlight_changes_unloze/torchlight3/access.json
Executable file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"[U:1:69566635]": {
|
||||
"level": 100,
|
||||
"name": "Jenz"
|
||||
},
|
||||
"[U:1:64494019]": {
|
||||
"level": 100,
|
||||
"name": "Neon"
|
||||
},
|
||||
"[U:1:126555404]": {
|
||||
"level": 3,
|
||||
"name": "Migza"
|
||||
},
|
||||
"[U:1:20383465]": {
|
||||
"name": "WASD",
|
||||
"level": 3
|
||||
},
|
||||
"[U:1:28627906]": {
|
||||
"name": "Berry",
|
||||
"level": 1337
|
||||
}
|
||||
}
|
37
torchlight_changes_unloze/torchlight3/main.py
Executable file
37
torchlight_changes_unloze/torchlight3/main.py
Executable file
@ -0,0 +1,37 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
import gc
|
||||
from importlib import reload
|
||||
|
||||
global TorchMaster
|
||||
|
||||
import Torchlight.Torchlight
|
||||
from Torchlight.SourceRCONServer import SourceRCONServer
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(
|
||||
level = logging.DEBUG,
|
||||
format = "[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s",
|
||||
datefmt = "%H:%M:%S"
|
||||
)
|
||||
|
||||
Loop = asyncio.get_event_loop()
|
||||
|
||||
global TorchMaster
|
||||
TorchMaster = Torchlight.Torchlight.TorchlightHandler(Loop)
|
||||
|
||||
# Handles new connections on 0.0.0.0:27015
|
||||
RCONConfig = TorchMaster.Config["TorchRCON"]
|
||||
"""RCONServer = SourceRCONServer(Loop, TorchMaster,
|
||||
Host = RCONConfig["Host"],
|
||||
Port = RCONConfig["Port"],
|
||||
Password = RCONConfig["Password"])"""
|
||||
|
||||
# Run!
|
||||
Loop.run_forever()
|
37
torchlight_changes_unloze/torchlight3/mainmg.py
Executable file
37
torchlight_changes_unloze/torchlight3/mainmg.py
Executable file
@ -0,0 +1,37 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
import gc
|
||||
from importlib import reload
|
||||
|
||||
global TorchMaster
|
||||
|
||||
import Torchlight.Torchlightmg
|
||||
from Torchlight.SourceRCONServer import SourceRCONServer
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(
|
||||
level = logging.DEBUG,
|
||||
format = "[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s",
|
||||
datefmt = "%H:%M:%S"
|
||||
)
|
||||
|
||||
Loop = asyncio.get_event_loop()
|
||||
|
||||
global TorchMaster
|
||||
TorchMaster = Torchlight.Torchlightmg.TorchlightHandler(Loop)
|
||||
|
||||
# Handles new connections on 0.0.0.0:27015
|
||||
RCONConfig = TorchMaster.Config["TorchRCON"]
|
||||
"""RCONServer = SourceRCONServer(Loop, TorchMaster,
|
||||
Host = RCONConfig["Host"],
|
||||
Port = RCONConfig["Port"],
|
||||
Password = RCONConfig["Password"])"""
|
||||
|
||||
# Run!
|
||||
Loop.run_forever()
|
23
torchlight_changes_unloze/torchlight3/requirements.txt
Executable file
23
torchlight_changes_unloze/torchlight3/requirements.txt
Executable file
@ -0,0 +1,23 @@
|
||||
aiohttp
|
||||
appdirs
|
||||
async-timeout
|
||||
beautifulsoup4
|
||||
certifi
|
||||
chardet
|
||||
gTTS
|
||||
gTTS-token
|
||||
idna
|
||||
lxml
|
||||
multidict
|
||||
numpy
|
||||
olefile
|
||||
packaging
|
||||
Pillow
|
||||
pyparsing
|
||||
python-magic
|
||||
requests
|
||||
six
|
||||
urllib3
|
||||
yarl
|
||||
cython
|
||||
geoip2
|
5
torchlight_changes_unloze/torchlight3/run.sh
Executable file
5
torchlight_changes_unloze/torchlight3/run.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
screen kill torchlight
|
||||
screen kill torchlight-noct
|
||||
screen -L -A -m -d -S torchlight ./_start.sh
|
||||
screen -L -A -m -d -S torchlight-mg ./_start_mg.sh
|
405
torchlight_changes_unloze/torchlight3/triggers.json
Executable file
405
torchlight_changes_unloze/torchlight3/triggers.json
Executable file
@ -0,0 +1,405 @@
|
||||
[
|
||||
{"names": ["!pyramid"], "sound": "ascream7.mp3"},
|
||||
{"names": ["!applause"], "sound": "applause.wav"},
|
||||
{"names": ["!seeyou", "!seeyouagain"], "sound": "sephiseeyou.mp3"},
|
||||
{"names": ["!chosen"], "sound": "sephonlythechosen.mp3"},
|
||||
{"names": ["!laser"], "sound": "bladeout.wav"},
|
||||
{"names": ["!prepare", "!battle"], "sound": "prepareforbattle.mp3"},
|
||||
{"names": ["!tuturu"], "sound": "tuturu.mp3"},
|
||||
{"names": ["!baka"], "sound": "baka.wav"},
|
||||
{"names": ["!omae"], "sound": "Omae.mp3"},
|
||||
{"names": ["!poi"], "sound": "poi.wav"},
|
||||
{"names": ["!shit"], "sound": "shit.wav"},
|
||||
{"names": ["!sugoi"], "sound": "sugoi.wav"},
|
||||
{"names": ["!allahu", "!akbar"], "sound": "allahu_akbar1.mp3"},
|
||||
{"names": ["!allahu2", "!akbar2"], "sound": "AllahuAkbarDubstep.mp3"},
|
||||
{"names": ["!quack"], "sound": "quack.mp3"},
|
||||
{"names": ["!english"], "sound": "english.wav"},
|
||||
{"names": ["!squad", "!suicidesquad"], "sound": "suicidesquad.mp3"},
|
||||
{"names": ["!cena"], "sound": "john.mp3"},
|
||||
{"names": ["!solo"], "sound": "solo.mp3"},
|
||||
{"names": ["!popopo"], "sound": "popopoguys1.mp3"},
|
||||
{"names": ["!tageule"], "sound": "tagueule.wav"},
|
||||
{"names": ["!idiot"], "sound": "omguidiot.mp3"},
|
||||
{"names": ["!game"], "sound": "isonly1.mp3"},
|
||||
{"names": ["!wah"], "sound": "wah wah sound effect.mp3"},
|
||||
{"names": ["!yes"], "sound": ["ohyes.mp3", "yes.mp3", "m-bison-yes-yes.mp3", "yesyesyesjojo.mp3"]},
|
||||
{"names": ["!overconfidence"], "sound": "Monster_Kill.wav"},
|
||||
{"names": ["!beer", "!pussy"], "sound": "grababeer.mp3"},
|
||||
{"names": ["!nein"], "sound": "Nein nein nein2.mp3"},
|
||||
{"names": ["!ayy", "!lmao"], "sound": "AyyyLmao_louer.mp3"},
|
||||
{"names": ["!blyat"], "sound": "BLYAT_louder.mp3"},
|
||||
{"names": ["!monkey", "!language"], "sound": "NomnkyLanguage_louder.mp3"},
|
||||
{"names": ["!wanker"], "sound": "OIYAWANKER_louder.mp3"},
|
||||
{"names": ["!vortex"], "sound": "WAAAAhByVortexx_louder.mp3"},
|
||||
{"names": ["!slutty"], "sound": "sluttyrainbow.wav"},
|
||||
{"names": ["!hello"], "sound": "Hello2.mp3"},
|
||||
{"names": ["!george"], "sound": "George.mp3"},
|
||||
{"names": ["!george2"], "sound": "George2.mp3"},
|
||||
{"names": ["!erebus"], "sound": "erebus.wav"},
|
||||
{"names": ["!kabzor", "!neon"], "sound": "kabzorrage.wav"},
|
||||
{"names": ["!otominas"], "sound": "Otominas.mp3"},
|
||||
{"names": ["!airhorn"], "sound": "AIRHORN.mp3"},
|
||||
{"names": ["!reee", "!reeee"], "sound": "reeeeeeee_v2.mp3"},
|
||||
{"names": ["!wombo"], "sound": "wombo combo2.mp3"},
|
||||
{"names": ["!vaperwave"], "sound": "Vaperwave.mp3"},
|
||||
{"names": ["!roasted"], "sound": "roasted2.mp3"},
|
||||
{"names": ["!ogre"], "sound": "its all ogre now.mp3"},
|
||||
{"names": ["!failed"], "sound": "Mission Failed.mp3"},
|
||||
{"names": ["!youmad"], "sound": "meshlem_arrive2_good.mp3"},
|
||||
{"names": ["!daniel"], "sound": "daniel.mp3"},
|
||||
{"names": ["!action"], "sound": "Action is Coming.mp3"},
|
||||
{"names": ["!brucelee"], "sound": "Ugandan Bruce Lee.mp3"},
|
||||
{"names": ["!gwegwe"], "sound": "Gwe Gwe Gwe.mp3"},
|
||||
{"names": ["!bruceu"], "sound": "We Call Him Bruce U.mp3"},
|
||||
{"names": ["!commando"], "sound": "COMMAND COMMANDO PagChomp.wav"},
|
||||
{"names": ["!nuffin"], "sound": "DINDU NUFFIN.wav"},
|
||||
{"names": ["!dinosaurs"], "sound": "DINOSAURS ZULUL.wav"},
|
||||
{"names": ["!deadlyactions"], "sound": "NON STOP DEADLY ACTIONS PagChomp.wav"},
|
||||
{"names": ["!gimme"], "sound": "GIMME GIMME GIMME GIMME.wav"},
|
||||
{"names": ["!skicker"], "sound": "SUPA KICKER.wav"},
|
||||
{"names": ["!sbiker"], "sound": "SUPA BIKER.wav"},
|
||||
{"names": ["!sfighter"], "sound": "SUPA FIGHTER.wav"},
|
||||
{"names": ["!smafia"], "sound": "SUPA MAFIA.wav"},
|
||||
{"names": ["!tigercage"], "sound": "THE TIGER IS IN THE CAGE.wav"},
|
||||
{"names": ["!serious"], "sound": "THIS IS SERIOUS.wav"},
|
||||
{"names": ["!hehehe"], "sound": "HE HE HE.wav"},
|
||||
{"names": ["!tigermafia"], "sound": "TIGER MAFIA.wav"},
|
||||
{"names": ["!cheeki"], "sound": "AH NU CHEEKI BREEKI IV DAMKE.mp3"},
|
||||
{"names": ["_legend"], "sound": "legend.mp3"},
|
||||
{"names": ["!admun_pls"], "sound": "Admun Please Electro (Short).mp3"},
|
||||
{"names": ["!dolphin"], "sound": "Dolfin - Song.mp3"},
|
||||
{"names": ["!omg_remix"], "sound": "O my Gah (Remix #3).mp3"},
|
||||
{"names": ["!rainbow_panda"], "sound": "ITS RAINBOW PANDA.mp3"},
|
||||
{"names": ["!dong"], "sound": "Ding Dong.mp3"},
|
||||
{"names": ["!ching_club"], "sound": "Ching Chong (Remix #9) Club.mp3"},
|
||||
{"names": ["!tonguetwister"], "sound": "tongue-twister.mp3"},
|
||||
{"names": ["!cooler"], "sound": "cooler.mp3"},
|
||||
{"names": ["!fatality"], "sound": "fatality.mp3"},
|
||||
{"names": ["!inception"], "sound": "inceptionbutton.mp3"},
|
||||
{"names": ["!leroy"], "sound": "leroy.mp3"},
|
||||
{"names": ["!ecchi"], "sound": "ecchidarling.mp3"},
|
||||
{"names": ["!moe"], "sound": "moe.mp3"},
|
||||
{"names": ["!nants"], "sound": "nants_ingonyama_bagithi_baba_-_the_circle_of_life-mp3cut.mp3"},
|
||||
{"names": ["!nyanpasu"], "sound": "nyanpass_2.mp3"},
|
||||
{"names": ["!oww"], "sound": "oww.mp3"},
|
||||
{"names": ["!wololo"], "sound": "wololo.mp3"},
|
||||
{"names": ["!yuuta"], "sound": "yuuuta_itaiiii.mp3"},
|
||||
{"names": ["!zawarudo"], "sound": "za-warudo-stop-time-sound.mp3"},
|
||||
{"names": ["!slow"], "sound": "seph_slow.wav"},
|
||||
{"names": ["!tuturu"], "sound": "Tutturuu_v1.wav"},
|
||||
{"names": ["!baka"], "sound": ["baka.wav", "Baka.mp3"]},
|
||||
{"names": ["!buhi"], "sound": "buhi.wav"},
|
||||
{"names": ["!nyan"], "sound": "nyanpass.wav"},
|
||||
{"names": ["!trial"], "sound": "trial.wav"},
|
||||
{"names": ["!mail"], "sound": "yuu_new_mail.wav"},
|
||||
{"names": ["!balrog"], "sound": "balrog_scream.wav"},
|
||||
{"names": ["!bluescreen", "!error"], "sound": "error.wav"},
|
||||
{"names": ["!hallelujah"], "sound": "hallelujah.wav"},
|
||||
{"names": ["!trap"], "sound": "itsatrap.wav"},
|
||||
{"names": ["!whores"], "sound": "whores.wav"},
|
||||
{"names": ["!gandalf"], "sound": "vozyoucannotpass.wav"},
|
||||
{"names": ["!cat"], "sound": "gato.wav"},
|
||||
{"names": ["!turtle", "!turtles"], "sound": ["iliketurtles.wav", "wherearetheturtles.mp3"]},
|
||||
{"names": ["!laser"], "sound": "blade_out.wav"},
|
||||
{"names": ["!king", "!kingoftheworld"], "sound": "vulcan.wav"},
|
||||
{"names": ["!seeyou", "!seeyouagain"], "sound": "seph_iseeyou.wav"},
|
||||
{"names": ["!chosen"], "sound": "seph_onlythechosen.wav"},
|
||||
{"names": ["!goodbye", "!saygoodbye"], "sound": "seph_saygoodbye.wav"},
|
||||
{"names": ["!slow"], "sound": "seph_slow.wav"},
|
||||
{"names": ["!late", "!toolate"], "sound": "seph_toolate.wav"},
|
||||
{"names": ["!growl"], "sound": "bahamut_growl2.wav"},
|
||||
{"names": ["!buyspins", "!buy_spins"], "sound": "BUYSPINS1.wav"},
|
||||
{"names": ["!death", "!deathscream"], "sound": "d_death_scream.wav"},
|
||||
{"names": ["!attack", "!meetyourend"], "sound": "z_godend.wav"},
|
||||
{"names": ["!die", "!guardian"], "sound": "z_guardianend.wav"},
|
||||
{"names": ["!applause"], "sound": "applause.wav"},
|
||||
{"names": ["!retreat"], "sound": "stage_x_gandalf_retreat-1-0.wav"},
|
||||
{"names": ["!idiot"], "sound": "idiot.wav"},
|
||||
{"names": ["!duck", "!quack"], "sound": "quack.wav"},
|
||||
{"names": ["!triple"], "sound": "triple.wav"},
|
||||
{"names": ["!solo"], "sound": "solo.wav"},
|
||||
{"names": ["!oniichan", "!onii-chan"], "sound": "Onii-chan.wav"},
|
||||
{"names": ["!gameover"], "sound": "Gameover.wav"},
|
||||
{"names": ["!wombo"], "sound": "WomboCombo.wav"},
|
||||
{"names": ["!camera"], "sound": "MOM GET THE CAMERA.wav"},
|
||||
{"names": ["!hihi"], "sound": "skullz.wav"},
|
||||
{"names": ["!yatta"], "sound": "Hiro Yatta.wav"},
|
||||
{"names": ["!over"], "sound": "over.wav"},
|
||||
{"names": ["!cena"], "sound": "cena.wav"},
|
||||
{"names": ["!victory"], "sound": "ffvii_victory.wav"},
|
||||
{"names": ["!2012"], "sound": ["2012_1.wav", "2012_2.wav", "2012_3.wav", "2012_4.wav"]},
|
||||
{"names": ["!nein"], "sound": "hitler_nein.wav"},
|
||||
{"names": ["!allahu", "!akbar"], "sound": "allahu_akbar.wav"},
|
||||
{"names": ["!chopper"], "sound": "gettothechopper.wav"},
|
||||
{"names": ["!payback"], "sound": "blain - payback time.wav"},
|
||||
{"names": ["!scotland"], "sound": "scotland.wav"},
|
||||
{"names": ["!happening"], "sound": "happening.wav"},
|
||||
{"names": ["!topkek"], "sound": "topkek.wav"},
|
||||
{"names": ["!welcome", "!ricefields"], "sound": "welcome_to_the_ricefields.wav"},
|
||||
{"names": ["!inception"], "sound": "inception.wav"},
|
||||
{"names": ["!x"], "sound": "xfiles.wav"},
|
||||
{"names": ["!stfu"], "sound": ["stfu.wav", "stfu2.mp3"]},
|
||||
{"names": ["!goat"], "sound": ["goat1.wav", "goat2.wav", "goat3.wav", "goat4.wav", "goat5.wav", "goat6.wav"]},
|
||||
{"names": ["!doit"], "sound": "doit.wav"},
|
||||
{"names": ["!benis"], "sound": "grossenbenis.wav"},
|
||||
{"names": ["!omg"], "sound": "omg.wav"},
|
||||
{"names": ["!wow"], "sound": ["wow.wav", "rexy_wow.wav"]},
|
||||
{"names": ["!prepare", "!battle"], "sound": "stage_2_preparaos-0-0.wav"},
|
||||
{"names": ["!meme"], "sound": "nicememe.wav"},
|
||||
{"names": ["!nyaa"], "sound": ["nyaa_1.wav", "nyaa_2.wav", "nyaa_3.wav", "nyaa_4.wav", "nyaa_5.wav", "nyaa_6.wav", "nyaa-3.mp3", "nyaa4.mp3"]},
|
||||
{"names": ["!ah"], "sound": "FFVII_Cry.wav"},
|
||||
{"names": ["!feelsbad"], "sound": "feelsbadman.wav"},
|
||||
{"names": ["!admun"], "sound": "admun_please.wav"},
|
||||
{"names": ["!population"], "sound": "population.wav"},
|
||||
{"names": ["!why", "!immigration"], "sound": "immigration.wav"},
|
||||
{"names": ["!tutury"], "sound": "tutury.wav"},
|
||||
{"names": ["!lew"], "sound": "Bewlewlewlew.wav"},
|
||||
{"names": ["!pudi"], "sound": "Pudi Pudi (Short).wav"},
|
||||
{"names": ["!hey", "!listen"], "sound": "Hey Listen.wav"},
|
||||
{"names": ["!comeon"], "sound": "Come on.mp3"},
|
||||
{"names": ["!stopp"], "sound": "itstimetostop.mp3"},
|
||||
{"names": ["!cancer"], "sound": "cancer.mp3"},
|
||||
{"names": ["!heil"], "sound": "heil.mp3"},
|
||||
{"names": ["!nico"], "sound": "niconiconi.mp3"},
|
||||
{"names": ["!cock"], "sound": ["cock1.mp3", "cock2.mp3", "cock3.mp3", "cock4.mp3", "cock5.mp3", "cock6.mp3"]},
|
||||
{"names": ["!rock"], "sound": "LetsRock.mp3"},
|
||||
{"names": ["!fmlaugh"], "sound": "fmlaugh.mp3"},
|
||||
{"names": ["!monopoly"], "sound": "monopoly.mp3"},
|
||||
{"names": ["!kiddin", "!kidding"], "sound": "rexy r u kiddin me.mp3"},
|
||||
{"names": ["!noice"], "sound": "Noice.mp3"},
|
||||
{"names": ["!rexy"], "sound": "rexywaah.mp3"},
|
||||
{"names": ["!kaboom"], "sound": "kaboom.mp3"},
|
||||
{"names": ["!honk"], "sound": "honk.wav"},
|
||||
{"names": ["!spam"], "sound": "No_Spammerino_In_The_Chatterino.mp3"},
|
||||
{"names": ["!ohoho"], "sound": ["ohoho_1.mp3", "ohoho_3.mp3", "ohoho_4.mp3", "ohoho_5.mp3", "ohoho_6.mp3", "ohoho_7.mp3", "ohoho_8.mp3", "ohoho_9.mp3", "ohoho_10.mp3", "ohoho_11.mp3", "ohoho_12.mp3", "ohoho_13.mp3", "ohoho_14.mp3", "ohoho_15.mp3", "ohoho_16.mp3", "ohoho_17.mp3", "ohoho_18.mp3", "ohoho_19.mp3", "ohoho_20.mp3", "ohoho_21.mp3", "ohoho_22.mp3", "ohoho_23.mp3", "ohoho_24.mp3", "ohoho_25.mp3", "ohoho_26.mp3", "ohoho_27.mp3", "ohoho_28.mp3", "ohoho_29.mp3", "ohoho_30.mp3", "ohoho_31.mp3", "ohoho_32.mp3", "ohoho_33.mp3", "ohoho_34.mp3", "ohoho_35.mp3", "ohoho_36.mp3", "ohoho_37.mp3", "ohoho_38.mp3", "ohoho_39.mp3", "ohoho_40.mp3", "ohoho_41.mp3", "ohoho_42.mp3", "ohoho_43.mp3", "ohoho_44.mp3", "ohoho_45.mp3", "ohoho_46.mp3", "ohoho_47.mp3", "ohoho_48.mp3", "ohoho_49.mp3", "ohoho_50.mp3", "ohoho_51.mp3", "ohoho_52.mp3", "ohoho_53.mp3", "ohoho_54.mp3", "ohoho_55.mp3", "ohoho_56.mp3", "ohoho_57.mp3", "ohoho_58.mp3", "ohoho_59.mp3", "ohoho_60.mp3", "ohoho_61.mp3", "ohoho_62.mp3", "ohoho_63.mp3", "ohoho_64.mp3", "ohoho_65.mp3", "ohoho_66.mp3", "ohoho_67.mp3", "ohoho_68.mp3", "ohoho_69.mp3", "ohoho_70.mp3", "ohoho_71.mp3", "ohoho_72.mp3", "ohoho_73.mp3", "ohoho_74.mp3", "ohoho_75.mp3", "ohoho_76.mp3", "ohoho_77.mp3", "ohoho_78.mp3", "ohoho_79.mp3", "ohoho_80.mp3", "ohoho_81.mp3", "ohoho_82.mp3", "ohoho_83.mp3", "ohoho_84.mp3", "ohoho_85.mp3", "ohoho_86.mp3", "ohoho_87.mp3", "ohoho_88.mp3", "ohoho_89.mp3"]},
|
||||
{"names": ["!sugoi"], "sound": "sugoi_sugoi.mp3"},
|
||||
{"names": ["!cry"], "sound": ["cry1.wav", "cry2.wav", "cry3.wav", "cry4.wav", "cry5.wav", "cry6.wav", "cry7.wav"]},
|
||||
{"names": ["!hehe", "!giggle"], "sound": "Giggle.mp3"},
|
||||
{"names": ["!monkey"], "sound": ["chimp1.wav", "chimp2.wav"]},
|
||||
{"names": ["!ka", "!kaka"], "sound": ["Nisemonogatari-Shinobu-Kaka.ogg", "Nisemonogatari-Shinobu-K-ka.ogg"]},
|
||||
{"names": ["!jodel"], "sound": "jodel.mp3"},
|
||||
{"names": ["!nyaaa"], "sound": "nyaaa.mp3"},
|
||||
{"names": ["!run"], "sound": "run.wav"},
|
||||
{"names": ["!goodbye"], "sound": "goodbye.wav"},
|
||||
{"names": ["!noo"], "sound": "LOTR_Noooooo.wav"},
|
||||
{"names": ["!dayum"], "sound": "daaamn.mp3"},
|
||||
{"names": ["!goddammit", "!goddamnit"], "sound": "goddammit.mp3"},
|
||||
{"names": ["!surprise"], "sound": "surprisemotherfucker1.mp3"},
|
||||
{"names": ["!csi"], "sound": "yeeaah.mp3"},
|
||||
{"names": ["!nope"], "sound": "engineer_no01.mp3"},
|
||||
{"names": ["!joke"], "sound": "rimshot.mp3"},
|
||||
{"names": ["!weed"], "sound": "smokeweederryday.mp3"},
|
||||
{"names": ["!toasty"], "sound": "toasty.mp3"},
|
||||
{"names": ["!damn"], "sound": "wheredyoufindthis.mp3"},
|
||||
{"names": ["!nuts"], "sound": "suckmynuts.mp3"},
|
||||
{"names": ["!wake"], "sound": "wakemeup.mp3"},
|
||||
{"names": ["!bye"], "sound": "bye.mp3"},
|
||||
{"names": ["!ilikeit"], "sound": "ilikeit.mp3"},
|
||||
{"names": ["!milk"], "sound": "milk.mp3"},
|
||||
{"names": ["!pussy"], "sound": "pussy.mp3"},
|
||||
{"names": ["!retard"], "sound": ["retard.mp3", "retard2.mp3"]},
|
||||
{"names": ["!sorry"], "sound": "sry.mp3"},
|
||||
{"names": ["!wtf"], "sound": "wtf.mp3"},
|
||||
{"names": ["!brb"], "sound": "brb.mp3"},
|
||||
{"names": ["!cricket"], "sound": "cricket.mp3"},
|
||||
{"names": ["!hax"], "sound": "hax.mp3"},
|
||||
{"names": ["!hi"], "sound": "hi.mp3"},
|
||||
{"names": ["!moo"], "sound": "moo.mp3"},
|
||||
{"names": ["!rape"], "sound": "rape.mp3"},
|
||||
{"names": ["!tada"], "sound": "tada.mp3"},
|
||||
{"names": ["!yay"], "sound": "yay.mp3"},
|
||||
{"names": ["!cyka"], "sound": "cyka.mp3"},
|
||||
{"names": ["!racist"], "sound": "racist.mp3"},
|
||||
{"names": ["!roger"], "sound": "roger.mp3"},
|
||||
{"names": ["!tooslow"], "sound": "tooslow.mp3"},
|
||||
{"names": ["!steam"], "sound": "steam.wav"},
|
||||
{"names": ["!good"], "sound": "that_s_pretty_good.mp3"},
|
||||
{"names": ["!crawling"], "sound": "crawling.mp3"},
|
||||
{"names": ["!nukyun"], "sound": ["nukyun1.mp3", "nukyun2.mp3", "nukyun3.mp3", "nukyun4.mp3", "nukyun5.mp3", "nukyun6.mp3", "nukyun7.mp3", "nukyun8.mp3", "nukyun9.mp3", "nukyun10.mp3", "nukyun11.mp3", "nukyun12.mp3", "nukyun13.mp3", "nukyun14.mp3", "nukyun15.mp3", "nukyun16.mp3", "nukyun17.mp3", "nukyun18.mp3", "nukyun19.mp3", "nukyun20.mp3", "nukyun21.mp3", "nukyun22.mp3", "nukyun23.mp3", "nukyun24.mp3", "nukyun25.mp3", "nukyun26.mp3", "nukyun27.mp3"]},
|
||||
{"names": ["!harambe"], "sound": "harambe.mp3"},
|
||||
{"names": ["!horn"], "sound": "vu_horn_quick.wav"},
|
||||
{"names": ["!hood"], "sound": "hood.mp3"},
|
||||
{"names": ["!gtfo"], "sound": ["gtfo.mp3", "gtfo2.mp3"]},
|
||||
{"names": ["!pomf"], "sound": "pomf.mp3"},
|
||||
{"names": ["!gay"], "sound": "putingay.mp3"},
|
||||
{"names": ["!pedo"], "sound": "pedobear.mp3"},
|
||||
{"names": ["!kys"], "sound": "kys.mp3"},
|
||||
{"names": ["!english"], "sound": "englishonly.mp3"},
|
||||
{"names": ["!knowledge"], "sound": "Knowledge.m4a"},
|
||||
{"names": ["!mana"], "sound": "mana.mp3"},
|
||||
{"names": ["!dodge"], "sound": ["dodge1.mp3", "dodge2.mp3"]},
|
||||
{"names": ["!love"], "sound": "sheep.mp3"},
|
||||
{"names": ["!timotei"], "sound": "Timotei.wav"},
|
||||
{"names": ["!daniel"], "sound": "daniel.mp3"},
|
||||
{"names": ["!cne"], "sound": ["it_is_may.mp3", "cnelaugh.mp3"]},
|
||||
{"names": ["!avocados"], "sound": "avocados.mp3"},
|
||||
{"names": ["!tutu", "!papa"], "sound": "papatutuwawa.mp3"},
|
||||
{"names": ["!nani"], "sound": "nani.mp3"},
|
||||
{"names": ["!squee"], "sound": "squee.mp3"},
|
||||
{"names": ["!ptb"], "sound": "07ptb.wav"},
|
||||
{"names": ["!wall"], "sound": "wall.mp3"},
|
||||
{"names": ["!bomb"], "sound": "bomb.mp3"},
|
||||
{"names": ["!wrong"], "sound": "wrong.mp3"},
|
||||
{"names": ["!china"], "sound": ["china1.mp3", "china2.mp3", "china3.mp3", "china4.mp3", "china5.mp3", "china6.mp3", "china7.mp3"]},
|
||||
{"names": ["!oof"], "sound": "oof.mp3"},
|
||||
{"names": ["!pan"], "sound": "panpakapan.mp3"},
|
||||
{"names": ["!shoulder"], "sound": "oh_my_shoulder.mp3"},
|
||||
{"names": ["!kizuna"], "sound": ["kizuna_fucku.mp3", "kizuna_fucku2.mp3", "kizuna_omg.mp3"]},
|
||||
{"names": ["!zegawa"], "sound": ["moaning_leBuP3p.mp3","1caae40.mp3","ara_ara.mp3","bndarling.mp3","watashi_hotto_jaate.mp3"]},
|
||||
{"names": ["!screenshot"], "sound": "camera1.wav"},
|
||||
{"names": ["!badro"], "sound": "vip.mp3"},
|
||||
{"names": ["!omarlaser"], "sound": "omarlaser.mp3"},
|
||||
{"names": ["!omarlenny"], "sound": "omarlenny.mp3"},
|
||||
{"names": ["!silent"], "sound": ["silent_scream.mp3" , "silent_laught.mp3"]},
|
||||
{"names": ["!willy"], "sound": "rippr_lotr_plz.mp3"},
|
||||
{"names": ["!anone"], "sound": "anone.mp3"},
|
||||
{"names": ["!jayz"], "sound": "jayz.wav"},
|
||||
{"names": ["!succ"], "sound": "succ.mp3"},
|
||||
{"names": ["!uguu"], "sound": "uguu_1.mp3"},
|
||||
{"names": ["!doot"], "sound": "skullsound2.mp3"},
|
||||
{"names": ["!widow"], "sound": "widowmaker_-_americans-1.mp3"},
|
||||
{"names": ["!winston"], "sound": "i-wanna-be-winston.mp3"},
|
||||
{"names": ["!try"], "sound": "try.mp3"},
|
||||
{"names": ["!smug"], "sound": "smug.mp3"},
|
||||
{"names": ["!skullz_tw"], "sound": "tw_skullzrage.mp3"},
|
||||
{"names": ["!genesis"], "sound": "genesis.mp3"},
|
||||
{"names": ["!borealis"], "sound": ["borealis1.mp3", "borealis2.mp3"]},
|
||||
{"names": ["!clams"], "sound": ["clams1.mp3", "clams2.mp3", "clams3.mp3"]},
|
||||
{"names": ["!hams"], "sound": ["hams1.mp3", "hams2.mp3"]},
|
||||
{"names": ["!expression"], "sound": "expression.mp3"},
|
||||
{"names": ["!albany"], "sound": "expression.mp3"},
|
||||
{"names": ["!steamedyes"], "sound": "steamedyes.mp3"},
|
||||
{"names": ["!steamedno"], "sound": "steamedno.mp3"},
|
||||
{"names": ["!luncheon"], "sound": "luncheon.mp3"},
|
||||
{"names": ["!devilish"], "sound": "devilish.mp3"},
|
||||
{"names": ["!roast"], "sound": "roast.mp3"},
|
||||
{"names": ["!goodlord"], "sound": "lord_unloze.mp3"},
|
||||
{"names": ["!time"], "sound": "time.mp3"},
|
||||
{"names": ["!dialect"], "sound": "dialect.mp3"},
|
||||
{"names": ["!attention"], "sound": "attention.mp3"},
|
||||
{"names": ["!great"], "sound": "great.mp3"},
|
||||
{"names": ["!epicsolo"], "sound": "epicsolo.mp3"},
|
||||
{"names": ["!everyonertv"], "sound": "everyonertv.mp3"},
|
||||
{"names": ["!esk"], "sound": "esk.mp3"},
|
||||
{"names": ["!epc"], "sound": "epc.mp3"},
|
||||
{"names": ["!ges"], "sound": "ges.mp3"},
|
||||
{"names": ["!eze"], "sound": "eze.mp3"},
|
||||
{"names": ["!eod"], "sound": "eod.mp3"},
|
||||
{"names": ["!tce"], "sound": "tce.mp3"},
|
||||
{"names": ["!trouble"], "sound": "trouble.mp3"},
|
||||
{"names": ["!teamwin"], "sound": "teamwin.mp3"},
|
||||
{"names": ["!nide"], "sound": ["nide.mp3", "nide2.mp3"]},
|
||||
{"names": ["!heaven"], "sound": "heaven.mp3"},
|
||||
{"names": ["!unskilled"], "sound": "unskilled.mp3"},
|
||||
{"names": ["!russia"], "sound": "russia.mp3"},
|
||||
{"names": ["!gg"], "sound": "gg.mp3"},
|
||||
{"names": ["!notrigger"], "sound": "notrig.wav"},
|
||||
{"names": ["!enemy"], "sound": ["enemee1.wav", "enemee2.wav", "enemee3.wav", "enemee4.wav", "enemee5.wav"]},
|
||||
{"names": ["!glacius"], "sound": "Glacius.m4a"},
|
||||
{"names": ["!ayaya"], "sound": ["ayayakaren1.mp3", "ayayakaren2.mp3"]},
|
||||
{"names": ["!dogan"], "sound": "dogan_im_going_to_fucking_fuck_you.mp3"},
|
||||
{"names": ["!destroy"], "sound": "dickstodestroy.mp3"},
|
||||
{"names": ["!boi"], "sound": "ainsley_harriott_and_his_spicy_meatconverttoaudio.mp3"},
|
||||
{"names": ["!gunga", "!ginga"], "sound": "gunga_ginga.mp3"},
|
||||
{"names": ["!leaders"], "sound": "leaders.mp3"},
|
||||
{"names": ["!never"], "sound": "never.mp3"},
|
||||
{"names": ["!hl1", "!blackmesa"], "sound": ["hl1_whatyoudoing.wav", "hl1_otis_talkmuch.wav", "hl1_pain3.wav", "hl1_otis_virgin.wav", "hl1_pain4.wav", "hl1_otis_die.wav", "hl1_pain1.wav", "hl1_otis_mom.wav", "hl1_pain2.wav", "hl1_scream05.wav", "hl1_iwounded.wav", "hl1_fear12.wav", "hl1_fear14.wav", "hl1_fear15.wav", "hl1_fear6.wav", "hl1_fear7.wav", "hl1_fear8.wav", "hl1_fear11.wav", "hl1_scream07.wav", "hl1_scream01.wav"]},
|
||||
{"names": ["!lügen"], "sound": "lügen.mp3"},
|
||||
{"names": ["!lachen"], "sound": "lachen.mp3"},
|
||||
{"names": ["!incoming"], "sound": "phalanx_incoming.mp3"},
|
||||
{"names": ["!address"], "sound": "humanz_send_me_your_address.wav"},
|
||||
{"names": ["!abuse"], "sound": "humanz_abuse.wav"},
|
||||
{"names": ["!killme"], "sound": "doit_comeon_killme.mp3"},
|
||||
{"names": ["!sickening"], "sound": "sickening.mp3"},
|
||||
{"names": ["!disgusting"], "sound": "disgusting_scuffed.mp3"},
|
||||
{"names": ["!icq"], "sound": "icq_old_sound.wav"},
|
||||
{"names": ["!discord"], "sound": "discord-notification.mp3"},
|
||||
{"names": ["!law", "!order"], "sound": "Law_&_Order_Sound.mp3"},
|
||||
{"names": ["!deutschland"], "sound": "VorUnsLiegtDeutschland.mp3"},
|
||||
{"names": ["!qwerpi"], "sound": ["qwerpi_aotsu/answer_the_question1.mp3", "qwerpi_aotsu/answer_the_question2.mp3", "qwerpi_aotsu/answer_the_question3.mp3", "qwerpi_aotsu/answer_the_question4.mp3", "qwerpi_aotsu/answer_the_question5.mp3", "qwerpi_aotsu/answer_the_question6.mp3", "qwerpi_aotsu/answer_the_question7.mp3", "qwerpi_aotsu/aotsuki_autism.mp3", "qwerpi_aotsu/aotsuki_come_on_stop.mp3", "qwerpi_aotsu/aotsuki_kouya.mp3", "qwerpi_aotsu/aotsuki_pls.mp3", "qwerpi_aotsu/aotsukitsukitsuki1.mp3", "qwerpi_aotsu/aotsukitsukitsuki2.mp3", "qwerpi_aotsu/aotsukyaotsukyaotsuky1.mp3", "qwerpi_aotsu/aotsukyaotsukyaotsuky2.mp3", "qwerpi_aotsu/can_you_pls_tell_me_your_opinion_takeshima.mp3", "qwerpi_aotsu/come_on_aotsukyyy.mp3", "qwerpi_aotsu/faking_faking_faking.mp3", "qwerpi_aotsu/i_changed_my_name_he_can_hear_me.mp3", "qwerpi_aotsu/japan_or_america1.mp3", "qwerpi_aotsu/japan_or_america10.mp3", "qwerpi_aotsu/japan_or_america11.mp3", "qwerpi_aotsu/japan_or_america12.mp3", "qwerpi_aotsu/japan_or_america2.mp3", "qwerpi_aotsu/japan_or_america3.mp3", "qwerpi_aotsu/japan_or_america4.mp3", "qwerpi_aotsu/japan_or_america5.mp3", "qwerpi_aotsu/japan_or_america6.mp3", "qwerpi_aotsu/japan_or_america7.mp3", "qwerpi_aotsu/japan_or_america8.mp3", "qwerpi_aotsu/japan_or_america9.mp3", "qwerpi_aotsu/onegaishimasu.mp3", "qwerpi_aotsu/pls_win_round_aotsuki.mp3", "qwerpi_aotsu/takeshima1.mp3", "qwerpi_aotsu/takeshima2.mp3", "qwerpi_aotsu/takeshima3.mp3", "qwerpi_aotsu/takeshima4.mp3", "qwerpi_aotsu/takeshima5.mp3", "qwerpi_aotsu/takeshima6.mp3", "qwerpi_aotsu/takeshima7.mp3", "qwerpi_aotsu/unnamed.mp3", "qwerpi_aotsu/why_do_you_do_this_to_me.mp3", "qwerpi_aotsu/why_dont_you_just_answer1.mp3", "qwerpi_aotsu/why_dont_you_just_answer2.mp3", "qwerpi_aotsu/why_dont_you_just_answer3.mp3", "qwerpi_aotsu/why_dont_you_just_answer4.mp3", "qwerpi_aotsu/why_dont_you_just_answer5.mp3", "qwerpi_aotsu/why_dont_you_just_answer6.mp3", "qwerpi_aotsu/you_not_japanese1.mp3", "qwerpi_aotsu/you_not_japanese2.mp3", "qwerpi_aotsu/you_not_japanese3.mp3", "qwerpi_aotsu/you_will_never_get_rid_of_me_aotsuki.mp3"]},
|
||||
{"names": ["!panic"], "sound": "greta_panic.mp3"},
|
||||
{"names": ["!gachi"], "sound": ["gachi/300.mp3", "gachi/ah.mp3", "gachi/amazing.mp3", "gachi/anal.mp3", "gachi/artist.mp3", "gachi/asswecan.mp3", "gachi/attention.mp3", "gachi/bigger.mp3", "gachi/boss.mp3", "gachi/boynextdoor.mp3", "gachi/collegeboy.mp3", "gachi/comeon.mp3", "gachi/daddy.mp3", "gachi/deep.mp3", "gachi/door.mp3", "gachi/doyoulike.mp3", "gachi/dungeon.mp3", "gachi/embarrassing.mp3", "gachi/fantasies.mp3", "gachi/fuckyou.mp3", "gachi/fuckyou2.mp3", "gachi/fuckyou3.mp3", "gachi/gangingup.mp3", "gachi/happy.mp3", "gachi/interruption.mp3", "gachi/jabroni.mp3", "gachi/lash.mp3", "gachi/likethat.mp3", "gachi/loads.mp3", "gachi/lube.mp3", "gachi/mmh.mp3", "gachi/pants.mp3", "gachi/power.mp3", "gachi/rip.mp3", "gachi/slaves.mp3", "gachi/sorry.mp3", "gachi/sorry2.mp3", "gachi/spank.mp3", "gachi/suction.mp3", "gachi/takeit.mp3", "gachi/website.mp3", "gachi/woo.mp3", "gachi/wth.mp3"]},
|
||||
{"names": ["!ali", "!alia"], "sound": "AliA.mp3"},
|
||||
{"names": ["!dropit"], "sound": "dropit.mp3"},
|
||||
{"names": ["!murica"], "sound": "MURICA.mp3"},
|
||||
{"names": ["!ocean", "!oceanman"], "sound": "oceanman.mp3"},
|
||||
{"names": ["!thanos"], "sound": "thanos_snaps_fingers_sfx.mp3"},
|
||||
{"names": ["!greta"], "sound": ["greta_1.mp3", "greta_2.mp3", "greta_3.mp3"]},
|
||||
{"names": ["!playb"], "sound": ["PlayB_-_back_back_back.mp3", "PlayB_-_back_doorhug.mp3", "PlayB_-_dont_stay_fkng_nubs.mp3", "PlayB_-_not_FF"]},
|
||||
{"names": ["!alarm"], "sound": "iphone_alarm.mp3"},
|
||||
{"names": ["!iphone", "!ringtone"], "sound": "iphone_ringtone.mp3"},
|
||||
{"names": ["!gman"], "sound": ["gman_choose1.wav", "gman_mono34.wav", "Gman_wellseeaboutthat.ogg", "gman_choose1.wav", "gman_choose2.wav", "gman_wisely_done.wav", "gman_wisely_done_freeman.wav", "gman_well_shit.wav"]},
|
||||
{"names": ["!steamedjenz"], "sound": "jenz_yes.mp3"},
|
||||
{"names": ["!sinaknife"], "sound": "sinaknife.mp3"},
|
||||
{"names": ["!sans"], "sound": ["undertale_sans.mp3", "megalovania_1.mp3", "megalovania_2.mp3"]},
|
||||
{"names": ["!what", "!youwhat"], "sound": "you-what-spongebob.mp3"},
|
||||
{"names": ["!pufferfish", "!augh"], "sound": "pufferfish_augh.mp3"},
|
||||
{"names": ["!gotcha"], "sound": "Gotcha Bitch2.mp3"},
|
||||
{"names": ["!palpatine"], "sound": "Palp_doit.mp3"},
|
||||
{"names": ["!chosenone"], "sound": "ChosenOne.mp3"},
|
||||
{"names": ["!anakinfun"], "sound": "AnakinFun.mp3"},
|
||||
{"names": ["!anakinliar"], "sound": "AnakinLiar.mp3"},
|
||||
{"names": ["!hellothere"], "sound": "HelloThere.mp3"},
|
||||
{"names": ["!because"], "sound": "BecauseObiWan.mp3"},
|
||||
{"names": ["!outofhand"], "sound": "OutofHand.mp3"},
|
||||
{"names": ["!problem"], "sound": "problem.mp3"},
|
||||
{"names": ["!pizza"], "sound": ["pizza.mp3", "pizza2.mp3"]},
|
||||
{"names": ["!train"], "sound": ["train.mp3", "train2.mp3", "train4.mp3"]},
|
||||
{"names": ["!screech"], "sound": "screech.mp3"},
|
||||
{"names": ["!simp"], "sound": "simp.mp3"},
|
||||
{"names": ["!tadadada"], "sound": "tadadada.mp3"},
|
||||
{"names": ["!kroaat"], "sound": "kroaat_rage.mp3"},
|
||||
{"names": ["!passed"], "sound": "GTA San Andreas - Mission passed sound.mp3"},
|
||||
{"names": ["!ahshit"], "sound": "GTA San Andreas - Ah shit, here we go again.mp3"},
|
||||
{"names": ["!no"], "sound": "nononojojo.mp3"},
|
||||
|
||||
{"names": ["_amigos"], "sound": "10-60/amigos.mp3"},
|
||||
{"names": ["_sad"], "sound": "10-60/2sad4me2.mp3"},
|
||||
{"names": ["_minasplease"], "sound": "10-60/MINASPLEASESONG.mp3"},
|
||||
{"names": ["_jobs"], "sound": "10-60/Jobs.mp3"},
|
||||
{"names": ["_praise"], "sound": "10-60/Praise-The-Sun-420Yolo2.mp3"},
|
||||
{"names": ["_brainpower"], "sound": "10-60/Brain Power V2.mp3"},
|
||||
{"names": ["_cotton"], "sound": "10-60/Cotton Eye Akbar.mp3"},
|
||||
{"names": ["_suckmycock", "_stopshooting"], "sound": "10-60/meshlem_arrive.mp3"},
|
||||
{"names": ["_hacker"], "sound": "10-60/Meshlem_die_good.mp3"},
|
||||
{"names": ["_how", "_happen"], "sound": "10-60/how_could_this_happen_to_me.mp3"},
|
||||
{"names": ["_tuturemix"], "sound": "10-60/tuturemix.mp3"},
|
||||
{"names": ["_pirate"], "sound": "10-60/pirate.wav"},
|
||||
{"names": ["_legend"], "sound": "10-60/legend.mp3"},
|
||||
{"names": ["_pirates"], "sound": "10-60/The - best.mp3"},
|
||||
{"names": ["_pegboard"], "sound": "10-60/Pendulum - Witchcraft Pegboard.mp3"},
|
||||
{"names": ["_saxoo"], "sound": "10-60/saxo.mp3"},
|
||||
{"names": ["_no"], "sound": "10-60/NOOOOOOOOOOO.wav"},
|
||||
{"names": ["_crabrave"], "sound": "10-60/crabrave.mp3"},
|
||||
{"names": ["_takemeon"], "sound": "10-60/a-ha-take-on-me-cut-mp3.mp3"},
|
||||
{"names": ["_shake"], "sound": "10-60/ShakeRockstar.ogg"},
|
||||
{"names": ["_hitormiss"], "sound": "10-60/hom_sans.mp3"},
|
||||
{"names": ["_johnny"], "sound": "10-60/Johnny Daepp.mp3"},
|
||||
{"names": ["_theo"], "sound": "10-60/theobald_lower.mp3"},
|
||||
{"names": ["_chinamotor"], "sound": "10-60/chinesemotorcycle.mp3"},
|
||||
{"names": ["_ftarflute"], "sound": "10-60/pend_taran_flute.mp3"},
|
||||
{"names": ["_neon_remix"], "sound": "10-60/neon_remix.mp3"},
|
||||
{"names": ["_lickies"], "sound": "10-60/lickies.mp3"},
|
||||
{"names": ["_jenzlem"], "sound": "10-60/jenzlem.m4a"},
|
||||
{"names": ["_playb"], "sound": "10-60/DontfallbackdefendPlayB.mp3"},
|
||||
{"names": ["_awaken"], "sound": "10-60/awaken.mp3"},
|
||||
{"names": ["_dudu"], "sound": "10-60/dudu.mp3"},
|
||||
{"names": ["_ice"], "sound": "10-60/levshoot.mp3"},
|
||||
{"names": ["_gunga", "_ginga"], "sound": "10-60/gunga_ginga.mp3"},
|
||||
{"names": ["_makoplease"], "sound": "10-60/makoplease.mp3"},
|
||||
{"names": ["_willrip"], "sound": "10-60/fullwilly.mp3"},
|
||||
{"names": ["_rage"], "sound": "tense1983_rage.mp3"},
|
||||
{"names": ["_france"], "sound": "france.mp3"},
|
||||
{"names": ["_cne"], "sound": "Cne_goes_apocalypse_mode.mp3"},
|
||||
|
||||
{"names": ["#tempest"], "sound": "60+/tempest.wav"},
|
||||
{"names": ["#pendulum_knife"], "sound": "60+/Pendulum_KnifeParty.mp3"},
|
||||
{"names": ["#nostar"], "sound": "60+/minassong.mp3"},
|
||||
{"names": ["#pika"], "sound": "60+/pika.wav"},
|
||||
{"names": ["#tripoloski"], "sound": "60+/tripoloski.mp3"},
|
||||
{"names": ["#tunak"], "sound": "60+/tunak.mp3"},
|
||||
{"names": ["#crabman"], "sound": "60+/CrabMan.ogg"},
|
||||
{"names": ["#trump"], "sound": "Donald_Trump_-_Shooting_Stars.mp3"},
|
||||
{"names": ["#tri_remix"], "sound": "60+/tree_poloskee.mp3"}
|
||||
]
|
Loading…
Reference in New Issue
Block a user