This commit is contained in:
BotoX 2019-12-30 17:43:48 +01:00
parent 1d18414148
commit d94fe1204f
19 changed files with 594 additions and 196 deletions

0
Torchlight/AccessManager.py Normal file → Executable file
View File

2
Torchlight/AsyncClient.py Normal file → Executable file
View File

@ -61,7 +61,6 @@ class AsyncClient():
def OnReceive(self, data): def OnReceive(self, data):
Obj = json.loads(data) Obj = json.loads(data)
print(Obj)
if "method" in Obj and Obj["method"] == "publish": if "method" in Obj and Obj["method"] == "publish":
self.Master.OnPublish(Obj) self.Master.OnPublish(Obj)
@ -80,7 +79,6 @@ class AsyncClient():
return None return None
Data = json.dumps(obj, ensure_ascii = False, separators = (',', ':')).encode("UTF-8") Data = json.dumps(obj, ensure_ascii = False, separators = (',', ':')).encode("UTF-8")
print(obj)
with (await self.SendLock): with (await self.SendLock):
if not self.Protocol: if not self.Protocol:

97
Torchlight/AudioManager.py Normal file → Executable file
View File

@ -23,6 +23,7 @@ class AudioPlayerFactory():
if _type == self.AUDIOPLAYER_FFMPEG: if _type == self.AUDIOPLAYER_FFMPEG:
return self.FFmpegAudioPlayerFactory.NewPlayer() return self.FFmpegAudioPlayerFactory.NewPlayer()
class AntiSpam(): class AntiSpam():
def __init__(self, master): def __init__(self, master):
self.Logger = logging.getLogger(__class__.__name__) self.Logger = logging.getLogger(__class__.__name__)
@ -31,6 +32,7 @@ class AntiSpam():
self.LastClips = dict() self.LastClips = dict()
self.DisabledTime = None self.DisabledTime = None
self.SaidHint = False
def CheckAntiSpam(self, player): def CheckAntiSpam(self, player):
if self.DisabledTime and self.DisabledTime > self.Torchlight().Master.Loop.time() and \ if self.DisabledTime and self.DisabledTime > self.Torchlight().Master.Loop.time() and \
@ -42,10 +44,7 @@ class AntiSpam():
return True return True
def RegisterClip(self, clip): def SpamCheck(self, Delta):
self.LastClips[hash(clip)] = dict({"timestamp": None, "duration": 0.0, "dominant": False, "active": True})
def SpamCheck(self):
Now = self.Torchlight().Master.Loop.time() Now = self.Torchlight().Master.Loop.time()
Duration = 0.0 Duration = 0.0
@ -53,7 +52,7 @@ class AntiSpam():
if not Clip["timestamp"]: if not Clip["timestamp"]:
continue continue
if Clip["timestamp"] + self.Torchlight().Config["AntiSpam"]["MaxUsageSpan"] < Now: if Clip["timestamp"] + Clip["duration"] + self.Torchlight().Config["AntiSpam"]["MaxUsageSpan"] < Now:
if not Clip["active"]: if not Clip["active"]:
del self.LastClips[Key] del self.LastClips[Key]
continue continue
@ -73,7 +72,8 @@ class AntiSpam():
self.LastClips.clear() self.LastClips.clear()
def OnPlay(self, clip): def OnPlay(self, clip):
self.LastClips[hash(clip)]["timestamp"] = self.Torchlight().Master.Loop.time() Now = self.Torchlight().Master.Loop.time()
self.LastClips[hash(clip)] = dict({"timestamp": Now, "duration": 0.0, "dominant": False, "active": True})
HasDominant = False HasDominant = False
for Key, Clip in self.LastClips.items(): for Key, Clip in self.LastClips.items():
@ -84,6 +84,9 @@ class AntiSpam():
self.LastClips[hash(clip)]["dominant"] = not HasDominant self.LastClips[hash(clip)]["dominant"] = not HasDominant
def OnStop(self, clip): def OnStop(self, clip):
if hash(clip) not in self.LastClips:
return
self.LastClips[hash(clip)]["active"] = False self.LastClips[hash(clip)]["active"] = False
if self.LastClips[hash(clip)]["dominant"]: if self.LastClips[hash(clip)]["dominant"]:
@ -102,7 +105,79 @@ class AntiSpam():
return return
Clip["duration"] += Delta Clip["duration"] += Delta
self.SpamCheck() 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\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(): class AudioManager():
@ -110,6 +185,7 @@ class AudioManager():
self.Logger = logging.getLogger(__class__.__name__) self.Logger = logging.getLogger(__class__.__name__)
self.Torchlight = torchlight self.Torchlight = torchlight
self.AntiSpam = AntiSpam(self) self.AntiSpam = AntiSpam(self)
self.Advertiser = Advertiser(self)
self.AudioPlayerFactory = AudioPlayerFactory(self) self.AudioPlayerFactory = AudioPlayerFactory(self)
self.AudioClips = [] self.AudioClips = []
@ -153,7 +229,7 @@ class AudioManager():
if extra and not extra.lower() in AudioClip.Player.Name.lower(): if extra and not extra.lower() in AudioClip.Player.Name.lower():
continue continue
if not Level or Level < AudioClip.Level: if not Level or (Level < AudioClip.Level and Level < self.Torchlight().Config["AntiSpam"]["StopLevel"]):
AudioClip.Stops.add(player.UserID) AudioClip.Stops.add(player.UserID)
if len(AudioClip.Stops) >= 3: if len(AudioClip.Stops) >= 3:
@ -188,11 +264,14 @@ class AudioManager():
self.AudioClips.append(Clip) self.AudioClips.append(Clip)
if not player.Access or player.Access["level"] < self.Torchlight().Config["AntiSpam"]["ImmunityLevel"]: if not player.Access or player.Access["level"] < self.Torchlight().Config["AntiSpam"]["ImmunityLevel"]:
self.AntiSpam.RegisterClip(Clip)
Clip.AudioPlayer.AddCallback("Play", lambda *args: self.AntiSpam.OnPlay(Clip, *args)) 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("Stop", lambda *args: self.AntiSpam.OnStop(Clip, *args))
Clip.AudioPlayer.AddCallback("Update", lambda *args: self.AntiSpam.OnUpdate(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 return Clip
def OnDisconnect(self, player): def OnDisconnect(self, player):

0
Torchlight/CommandHandler.py Normal file → Executable file
View File

454
Torchlight/Commands.py Normal file → Executable file
View File

@ -16,10 +16,26 @@ class BaseCommand():
self.Triggers = [] self.Triggers = []
self.Level = 0 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): async def _func(self, message, player):
self.Logger.debug(sys._getframe().f_code.co_name) self.Logger.debug(sys._getframe().f_code.co_name)
### FILTER COMMANDS ###
class URLFilter(BaseCommand): class URLFilter(BaseCommand):
Order = 1 Order = 1
import re import re
@ -49,11 +65,16 @@ class URLFilter(BaseCommand):
if TimeStr: if TimeStr:
Time = Utils.ParseTime(TimeStr) Time = Utils.ParseTime(TimeStr)
Proc = await asyncio.create_subprocess_exec("youtube-dl", "--dump-json", "-xg", url, Proc = await asyncio.create_subprocess_exec("youtube-dl", "--dump-json", "-g", url,
stdout = asyncio.subprocess.PIPE) stdout = asyncio.subprocess.PIPE)
Out, _ = await Proc.communicate() Out, _ = await Proc.communicate()
url, Info = Out.split(b'\n', maxsplit = 1) parts = Out.split(b'\n')
parts.pop() # trailing new line
Info = parts.pop()
url = parts.pop()
url = url.strip().decode("ascii") url = url.strip().decode("ascii")
Info = self.json.loads(Info) Info = self.json.loads(Info)
@ -119,49 +140,62 @@ class URLFilter(BaseCommand):
asyncio.ensure_future(self.URLInfo(Url)) asyncio.ensure_future(self.URLInfo(Url))
return -1 return -1
### FILTER COMMANDS ###
### LEVEL 0 COMMANDS ### 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): class Access(BaseCommand):
def __init__(self, torchlight): def __init__(self, torchlight):
super().__init__(torchlight) super().__init__(torchlight)
self.Triggers = ["!access"] #, "!who", "!whois"] self.Triggers = ["!access"]
self.Level = 0 self.Level = 0
def FormatAccess(self, 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 self.Torchlight().Config["AudioLimits"]:
Uses = self.Torchlight().Config["AudioLimits"][Level]["Uses"]
TotalTime = self.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
async def _func(self, message, player): async def _func(self, message, player):
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message)) self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
if self.check_chat_cooldown(player):
return -1
Count = 0 Count = 0
if message[0] == "!access": if message[0] == "!access":
if message[1]: if message[1]:
return -1 return -1
self.Torchlight().SayChat(self.FormatAccess(player)) self.Torchlight().SayChat(FormatAccess(self.Torchlight, player), player)
elif message[0] == "!who": 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: for Player in self.Torchlight().Players:
if Player.Name.lower().find(message[1].lower()) != -1: if Player.Name.lower().find(message[1].lower()) != -1:
self.Torchlight().SayChat(self.FormatAccess(Player)) self.Torchlight().SayChat(FormatAccess(self.Torchlight, Player))
Count += 1 Count += 1
if Count >= 3: if Count >= 3:
@ -172,7 +206,7 @@ class Access(BaseCommand):
if Access["name"].lower().find(message[1].lower()) != -1: if Access["name"].lower().find(message[1].lower()) != -1:
Player = self.Torchlight().Players.FindUniqueID(UniqueID) Player = self.Torchlight().Players.FindUniqueID(UniqueID)
if Player: if Player:
self.Torchlight().SayChat(self.FormatAccess(Player)) self.Torchlight().SayChat(FormatAccess(self.Torchlight, Player))
else: else:
self.Torchlight().SayChat("#? \"{0}\"({1}) is level {2!s} is currently offline.".format(Access["name"], UniqueID, Access["level"])) self.Torchlight().SayChat("#? \"{0}\"({1}) is level {2!s} is currently offline.".format(Access["name"], UniqueID, Access["level"]))
@ -181,34 +215,6 @@ class Access(BaseCommand):
break break
return 0 return 0
class Calculate(BaseCommand):
import urllib.parse
import aiohttp
import json
def __init__(self, torchlight):
super().__init__(torchlight)
self.Triggers = ["!c"]
self.Level = 0
async def Calculate(self, Params):
async with self.aiohttp.ClientSession() as session:
Response = await asyncio.wait_for(session.get("http://math.leftforliving.com/query", params=Params), 5)
if not Response:
return 1
Data = await asyncio.wait_for(Response.json(content_type = "text/json"), 5)
if not Data:
return 2
if not Data["error"]:
self.Torchlight().SayChat(Data["answer"])
return 0
async def _func(self, message, player):
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
Params = dict({"question": message[1]})
Ret = await self.Calculate(Params)
return Ret
class WolframAlpha(BaseCommand): class WolframAlpha(BaseCommand):
import urllib.parse import urllib.parse
@ -218,12 +224,12 @@ class WolframAlpha(BaseCommand):
def __init__(self, torchlight): def __init__(self, torchlight):
super().__init__(torchlight) super().__init__(torchlight)
self.Triggers = ["!cc"] self.Triggers = ["!cc"]
self.Level = 0 self.Level = 3
def Clean(self, Text): def Clean(self, Text):
return self.re.sub("[ ]{2,}", " ", Text.replace(' | ', ': ').replace('\n', ' | ').replace('~~', '')).strip() return self.re.sub("[ ]{2,}", " ", Text.replace(' | ', ': ').replace('\n', ' | ').replace('~~', '')).strip()
async def Calculate(self, Params): async def Calculate(self, Params, player):
async with self.aiohttp.ClientSession() as session: async with self.aiohttp.ClientSession() as session:
Response = await asyncio.wait_for(session.get("http://api.wolframalpha.com/v2/query", params=Params), 10) Response = await asyncio.wait_for(session.get("http://api.wolframalpha.com/v2/query", params=Params), 10)
if not Response: if not Response:
@ -246,7 +252,7 @@ class WolframAlpha(BaseCommand):
# no support for future stuff yet, TODO? # no support for future stuff yet, TODO?
if not Didyoumeans: if not Didyoumeans:
# If there's no pods, the question clearly wasn't understood # If there's no pods, the question clearly wasn't understood
self.Torchlight().SayChat("Sorry, couldn't understand the question.") self.Torchlight().SayChat("Sorry, couldn't understand the question.", player)
return 3 return 3
Options = [] Options = []
@ -254,39 +260,128 @@ class WolframAlpha(BaseCommand):
Options.append("\"{0}\"".format(Didyoumean.text)) Options.append("\"{0}\"".format(Didyoumean.text))
Line = " or ".join(Options) Line = " or ".join(Options)
Line = "Did you mean {0}?".format(Line) Line = "Did you mean {0}?".format(Line)
self.Torchlight().SayChat(Line) self.Torchlight().SayChat(Line, player)
return 0 return 0
# If there's only one pod with text, it's probably the answer # If there's only one pod with text, it's probably the answer
# example: "integral x²" # example: "integral x²"
if len(Pods) == 1: if len(Pods) == 1:
Answer = self.Clean(Pods[0]) Answer = self.Clean(Pods[0])
self.Torchlight().SayChat(Answer) self.Torchlight().SayChat(Answer, player)
return 0 return 0
# If there's multiple pods, first is the question interpretation # If there's multiple pods, first is the question interpretation
Question = self.Clean(Pods[0].replace(' | ', ' ').replace('\n', ' ')) Question = self.Clean(Pods[0].replace(' | ', ' ').replace('\n', ' '))
# and second is the best answer # and second is the best answer
Answer = self.Clean(Pods[1]) Answer = self.Clean(Pods[1])
self.Torchlight().SayChat("{0} = {1}".format(Question, Answer)) self.Torchlight().SayChat("{0} = {1}".format(Question, Answer), player)
return 0 return 0
async def _func(self, message, player): async def _func(self, message, player):
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message)) self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
Level = 0 if self.check_chat_cooldown(player):
if player.Access: return -1
Level = player.Access["level"]
Disabled = self.Torchlight().Disabled if self.check_disabled(player):
if Disabled and (Disabled > Level or Disabled == Level and Level < self.Torchlight().Config["AntiSpam"]["ImmunityLevel"]): return -1
self.Torchlight().SayPrivate(player, "Torchlight is currently disabled!")
return 1
Params = dict({"input": message[1], "appid": self.Torchlight().Config["WolframAPIKey"]}) Params = dict({"input": message[1], "appid": self.Torchlight().Config["WolframAPIKey"]})
Ret = await self.Calculate(Params) Ret = await self.Calculate(Params, player)
return Ret return Ret
class UrbanDictionary(BaseCommand):
import aiohttp
def __init__(self, torchlight):
super().__init__(torchlight)
self.Triggers = ["!define", "!ud"]
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
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 = 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
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): class WUnderground(BaseCommand):
import aiohttp import aiohttp
def __init__(self, torchlight): def __init__(self, torchlight):
@ -352,6 +447,7 @@ class WUnderground(BaseCommand):
Observation["relative_humidity"])) Observation["relative_humidity"]))
return 0 return 0
'''
class VoteDisable(BaseCommand): class VoteDisable(BaseCommand):
def __init__(self, torchlight): def __init__(self, torchlight):
@ -361,6 +457,7 @@ class VoteDisable(BaseCommand):
async def _func(self, message, player): async def _func(self, message, player):
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message)) self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
if self.Torchlight().Disabled: if self.Torchlight().Disabled:
self.Torchlight().SayPrivate(player, "Torchlight is already disabled for the duration of this map.") self.Torchlight().SayPrivate(player, "Torchlight is already disabled for the duration of this map.")
return return
@ -375,70 +472,118 @@ class VoteDisable(BaseCommand):
else: else:
self.Torchlight().SayPrivate(player, "Torchlight needs {0} more disable votes to be disabled.".format(needed - have)) self.Torchlight().SayPrivate(player, "Torchlight needs {0} more disable votes to be disabled.".format(needed - have))
### LEVEL 0 COMMANDS ###
### LIMITED LEVEL 0 COMMANDS ###
class VoiceCommands(BaseCommand): class VoiceCommands(BaseCommand):
import json import json
import random import random
def __init__(self, torchlight): def __init__(self, torchlight):
super().__init__(torchlight) super().__init__(torchlight)
self.Triggers = ["!random"] self.Triggers = ["!random", "!search"]
self.Level = 0 self.Level = 0
def LoadTriggers(self): def LoadTriggers(self):
try: try:
with open("triggers.json", "r") as fp: with open("triggers.json", "r") as fp:
self.VoiceTriggers = self.json.load(fp) Triggers = self.json.load(fp)
except ValueError as e: except ValueError as e:
self.Logger.error(sys._getframe().f_code.co_name + ' ' + str(e)) self.Logger.error(sys._getframe().f_code.co_name + ' ' + str(e))
self.Torchlight().SayChat(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): def _setup(self):
self.Logger.debug(sys._getframe().f_code.co_name) self.Logger.debug(sys._getframe().f_code.co_name)
self.LoadTriggers() self.LoadTriggers()
for Triggers in self.VoiceTriggers: for Trigger in self.VoiceTriggers.keys():
for Trigger in Triggers["names"]: self.Triggers.append(Trigger)
self.Triggers.append(Trigger)
async def _func(self, message, player): async def _func(self, message, player):
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message)) self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
if self.check_disabled(player):
return -1
Level = 0 Level = 0
if player.Access: if player.Access:
Level = player.Access["level"] Level = player.Access["level"]
Disabled = self.Torchlight().Disabled message[0] = message[0].lower()
if Disabled and (Disabled > Level or Disabled == Level and Level < self.Torchlight().Config["AntiSpam"]["ImmunityLevel"]): message[1] = message[1].lower()
self.Torchlight().SayPrivate(player, "Torchlight is currently disabled!") if message[0][0] != '!' and Level < 2:
return 1 return 1
if message[0][0] == '_' and Level < 2: if message[0] == "!search":
return 1 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].lower() == "!random": if message[0] == "!random":
Trigger = self.random.choice(self.VoiceTriggers) Trigger = self.random.choice(list(self.VoiceTriggers.values()))
if isinstance(Trigger["sound"], list): if isinstance(Trigger, list):
Sound = self.random.choice(Trigger["sound"]) Sound = self.random.choice(Trigger)
else: else:
Sound = Trigger["sound"] Sound = Trigger
else: else:
for Trigger in self.VoiceTriggers: Sounds = self.VoiceTriggers[message[0]]
for Name in Trigger["names"]:
if message[0].lower() == Name:
Num = Utils.GetNum(message[1])
if Num:
Num = int(Num)
if isinstance(Trigger["sound"], list): try:
if Num and Num > 0 and Num <= len(Trigger["sound"]): Num = int(message[1])
Sound = Trigger["sound"][Num - 1] except ValueError:
else: Num = None
Sound = self.random.choice(Trigger["sound"])
else:
Sound = Trigger["sound"]
break 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)) Path = os.path.abspath(os.path.join("sounds", Sound))
AudioClip = self.Torchlight().AudioManager.AudioClip(player, "file://" + Path) AudioClip = self.Torchlight().AudioManager.AudioClip(player, "file://" + Path)
@ -447,23 +592,21 @@ class VoiceCommands(BaseCommand):
return AudioClip.Play() return AudioClip.Play()
class YouTube(BaseCommand): class YouTube(BaseCommand):
def __init__(self, torchlight): def __init__(self, torchlight):
super().__init__(torchlight) super().__init__(torchlight)
self.Triggers = ["!yt"] self.Triggers = ["!yt"]
self.Level = 2 self.Level = 3
async def _func(self, message, player): async def _func(self, message, player):
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message)) self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
Level = 0 if self.check_disabled(player):
if player.Access: return -1
Level = player.Access["level"]
Disabled = self.Torchlight().Disabled if self.Torchlight().LastUrl:
if Disabled and (Disabled > Level or Disabled == Level and Level < self.Torchlight().Config["AntiSpam"]["ImmunityLevel"]): message[1] = message[1].replace("!last", self.Torchlight().LastUrl)
self.Torchlight().SayPrivate(player, "Torchlight is currently disabled!")
return 1
Temp = DataHolder() Temp = DataHolder()
Time = None Time = None
@ -485,19 +628,13 @@ class YouTubeSearch(BaseCommand):
def __init__(self, torchlight): def __init__(self, torchlight):
super().__init__(torchlight) super().__init__(torchlight)
self.Triggers = ["!yts"] self.Triggers = ["!yts"]
self.Level = 2 self.Level = 3
async def _func(self, message, player): async def _func(self, message, player):
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message)) self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
Level = 0 if self.check_disabled(player):
if player.Access: return -1
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 1
Temp = DataHolder() Temp = DataHolder()
Time = None Time = None
@ -518,7 +655,7 @@ class YouTubeSearch(BaseCommand):
if Info["extractor_key"] == "Youtube": if Info["extractor_key"] == "Youtube":
self.Torchlight().SayChat("\x07E52D27[YouTube]\x01 {0} | {1} | {2}/5.00 | {3:,}".format( 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"]))) 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) AudioClip = self.Torchlight().AudioManager.AudioClip(player, url)
if not AudioClip: if not AudioClip:
@ -528,6 +665,7 @@ class YouTubeSearch(BaseCommand):
return AudioClip.Play(Time) return AudioClip.Play(Time)
class Say(BaseCommand): class Say(BaseCommand):
import gtts import gtts
import tempfile import tempfile
@ -535,7 +673,7 @@ class Say(BaseCommand):
def __init__(self, torchlight): def __init__(self, torchlight):
super().__init__(torchlight) super().__init__(torchlight)
self.Triggers = [("!say", 4)] self.Triggers = [("!say", 4)]
self.Level = 0 self.Level = 2
async def Say(self, player, language, message): async def Say(self, player, language, message):
GTTS = self.gtts.gTTS(text = message, lang = language) GTTS = self.gtts.gTTS(text = message, lang = language)
@ -559,14 +697,8 @@ class Say(BaseCommand):
async def _func(self, message, player): async def _func(self, message, player):
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message)) self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
Level = 0 if self.check_disabled(player):
if player.Access: return -1
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 1
if not message[1]: if not message[1]:
return 1 return 1
@ -581,6 +713,7 @@ class Say(BaseCommand):
asyncio.ensure_future(self.Say(player, Language, message[1])) asyncio.ensure_future(self.Say(player, Language, message[1]))
return 0 return 0
'''
class DECTalk(BaseCommand): class DECTalk(BaseCommand):
import tempfile import tempfile
def __init__(self, torchlight): def __init__(self, torchlight):
@ -612,20 +745,15 @@ class DECTalk(BaseCommand):
async def _func(self, message, player): async def _func(self, message, player):
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message)) self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
Level = 0 if self.check_disabled(player):
if player.Access: return -1
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 1
if not message[1]: if not message[1]:
return 1 return 1
asyncio.ensure_future(self.Say(player, message[1])) asyncio.ensure_future(self.Say(player, message[1]))
return 0 return 0
'''
class Stop(BaseCommand): class Stop(BaseCommand):
def __init__(self, torchlight): def __init__(self, torchlight):
@ -639,18 +767,7 @@ class Stop(BaseCommand):
self.Torchlight().AudioManager.Stop(player, message[1]) self.Torchlight().AudioManager.Stop(player, message[1])
return True return True
### LIMITED LEVEL 0 COMMANDS ###
### LEVEL 1 COMMANDS ###
### LEVEL 1 COMMANDS ###
### LEVEL 2 COMMANDS ###
### LEVEL 2 COMMANDS ###
### LEVEL 3 COMMANDS ###
class EnableDisable(BaseCommand): class EnableDisable(BaseCommand):
def __init__(self, torchlight): def __init__(self, torchlight):
super().__init__(torchlight) super().__init__(torchlight)
@ -659,24 +776,24 @@ class EnableDisable(BaseCommand):
async def _func(self, message, player): async def _func(self, message, player):
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message)) self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
if message[0] == "!enable": if message[0] == "!enable":
if self.Torchlight().Disabled: if self.Torchlight().Disabled:
if self.Torchlight().Disabled > player.Access["level"]: 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.") self.Torchlight().SayPrivate(player, "You don't have access to enable torchlight, since it was disabled by a higher level user.")
return 1 return 1
self.Torchlight().SayChat("Torchlight has been enabled for the duration of this map - Type !disable to disable it again.") self.Torchlight().SayChat("Torchlight has been enabled for the duration of this map - Type !disable to disable it again.")
self.Torchlight().Disabled = False self.Torchlight().Disabled = False
elif message[0] == "!disable": elif message[0] == "!disable":
if not self.Torchlight().Disabled: if self.Torchlight().Disabled > player.Access["level"]:
self.Torchlight().SayChat("Torchlight has been disabled for the duration of this map - Type !enable to enable it again.") 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"] self.Torchlight().Disabled = player.Access["level"]
### LEVEL 3 COMMANDS ###
### LEVEL 4 COMMANDS ###
class AdminAccess(BaseCommand): class AdminAccess(BaseCommand):
from collections import OrderedDict from collections import OrderedDict
def __init__(self, torchlight): def __init__(self, torchlight):
@ -782,15 +899,24 @@ class AdminAccess(BaseCommand):
del self.Torchlight().Access[Player.UniqueID] del self.Torchlight().Access[Player.UniqueID]
Player.Access = None Player.Access = None
return 0 return 0
### LEVEL 4 COMMANDS ###
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
### LEVEL X COMMANDS ###
class Exec(BaseCommand): class Exec(BaseCommand):
def __init__(self, torchlight): def __init__(self, torchlight):
super().__init__(torchlight) super().__init__(torchlight)
self.Triggers = ["!exec"] self.Triggers = ["!exec"]
self.Level = 9 self.Level = 100
async def _func(self, message, player): async def _func(self, message, player):
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message)) self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
@ -801,15 +927,3 @@ class Exec(BaseCommand):
return 1 return 1
self.Torchlight().SayChat(str(Response)) self.Torchlight().SayChat(str(Response))
return 0 return 0
class Reload(BaseCommand):
def __init__(self, torchlight):
super().__init__(torchlight)
self.Triggers = ["!reload"]
self.Level = 6
async def _func(self, message, player):
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + str(message))
self.Torchlight().Reload()
return 0
### LEVEL X COMMANDS ###

7
Torchlight/Config.py Normal file → Executable file
View File

@ -2,16 +2,21 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging import logging
import json import json
import sys
class Config(): class Config():
def __init__(self): def __init__(self):
self.Logger = logging.getLogger(__class__.__name__) self.Logger = logging.getLogger(__class__.__name__)
self.Config = dict() self.Config = dict()
if len(sys.argv) >= 2:
self.ConfigPath = sys.argv[1]
else:
self.ConfigPath = "config.json"
self.Load() self.Load()
def Load(self): def Load(self):
try: try:
with open("config.json", "r") as fp: with open(self.ConfigPath, "r") as fp:
self.Config = json.load(fp) self.Config = json.load(fp)
except ValueError as e: except ValueError as e:
self.Logger.error(sys._getframe().f_code.co_name + ' ' + str(e)) self.Logger.error(sys._getframe().f_code.co_name + ' ' + str(e))

0
Torchlight/Constants.py Normal file → Executable file
View File

4
Torchlight/FFmpegAudioPlayer.py Normal file → Executable file
View File

@ -60,9 +60,9 @@ class FFmpegAudioPlayer():
def PlayURI(self, uri, position, *args): def PlayURI(self, uri, position, *args):
if position: if position:
PosStr = str(datetime.timedelta(seconds = 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", *args, "-"] Command = ["/usr/bin/ffmpeg", "-ss", PosStr, "-i", uri, "-acodec", "pcm_s16le", "-ac", "1", "-ar", str(int(self.SampleRate)), "-f", "s16le", "-vn", *args, "-"]
else: else:
Command = ["/usr/bin/ffmpeg", "-i", uri, "-acodec", "pcm_s16le", "-ac", "1", "-ar", str(int(self.SampleRate)), "-f", "s16le", *args, "-"] Command = ["/usr/bin/ffmpeg", "-i", uri, "-acodec", "pcm_s16le", "-ac", "1", "-ar", str(int(self.SampleRate)), "-f", "s16le", "-vn", *args, "-"]
print(Command) print(Command)

View File

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import asyncio import asyncio
import logging import logging
import traceback
class GameEvents(): class GameEvents():
def __init__(self, master): def __init__(self, master):

21
Torchlight/PlayerManager.py Normal file → Executable file
View File

@ -15,6 +15,7 @@ class PlayerManager():
self.Torchlight().GameEvents.HookEx("player_connect", self.Event_PlayerConnect) self.Torchlight().GameEvents.HookEx("player_connect", self.Event_PlayerConnect)
self.Torchlight().GameEvents.HookEx("player_activate", self.Event_PlayerActivate) 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_info", self.Event_PlayerInfo)
self.Torchlight().GameEvents.HookEx("player_disconnect", self.Event_PlayerDisconnect) self.Torchlight().GameEvents.HookEx("player_disconnect", self.Event_PlayerDisconnect)
self.Torchlight().GameEvents.HookEx("server_spawn", self.Event_ServerSpawn) self.Torchlight().GameEvents.HookEx("server_spawn", self.Event_ServerSpawn)
@ -23,7 +24,8 @@ class PlayerManager():
index += 1 index += 1
self.Logger.info("OnConnect(name={0}, index={1}, userid={2}, networkid={3}, address={4}, bot={5})" self.Logger.info("OnConnect(name={0}, index={1}, userid={2}, networkid={3}, address={4}, bot={5})"
.format(name, index, userid, networkid, address, bot)) .format(name, index, userid, networkid, address, bot))
assert self.Players[index] == None 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] = self.Player(self, index, userid, networkid, address, name)
self.Players[index].OnConnect() self.Players[index].OnConnect()
@ -35,6 +37,11 @@ class PlayerManager():
self.Players[index].OnActivate() 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): def Event_PlayerInfo(self, name, index, userid, networkid, bot):
index += 1 index += 1
self.Logger.info("OnInfo(name={0}, index={1}, userid={2}, networkid={3}, bot={4})" self.Logger.info("OnInfo(name={0}, index={1}, userid={2}, networkid={3}, bot={4})"
@ -157,6 +164,7 @@ class PlayerManager():
self.Admin = self.PlayerManager.Admin() self.Admin = self.PlayerManager.Admin()
self.Storage = None self.Storage = None
self.Active = False self.Active = False
self.ChatCooldown = 0
def OnConnect(self): def OnConnect(self):
self.Storage = self.PlayerManager.Storage[self.UniqueID] self.Storage = self.PlayerManager.Storage[self.UniqueID]
@ -168,17 +176,22 @@ class PlayerManager():
def OnActivate(self): def OnActivate(self):
self.Active = True self.Active = True
asyncio.ensure_future(self.OnPostActivate())
async def OnPostActivate(self): async def OnClientPostAdminCheck(self):
self.Admin._FlagBits = (await self.Torchlight().API.GetUserFlagBits(self.Index))["result"] 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)) self.PlayerManager.Logger.info("#{0} \"{1}\"({2}) FlagBits: {3}".format(self.UserID, self.Name, self.UniqueID, self.Admin._FlagBits))
if not self.Access: if not self.Access:
if self.Admin.Generic(): if self.Admin.RCON():
self.Access = dict({"level": 6, "name": "SAdmin"})
elif self.Admin.Generic():
self.Access = dict({"level": 3, "name": "Admin"}) self.Access = dict({"level": 3, "name": "Admin"})
elif self.Admin.Custom1(): elif self.Admin.Custom1():
self.Access = dict({"level": 1, "name": "VIP"}) 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): def OnInfo(self, name):
self.Name = name self.Name = name

0
Torchlight/SourceModAPI.py Normal file → Executable file
View File

0
Torchlight/SourceRCONServer.py Normal file → Executable file
View File

159
Torchlight/Subscribe.py Executable file
View 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")

28
Torchlight/Torchlight.py Normal file → Executable file
View File

@ -12,7 +12,7 @@ import textwrap
from .AsyncClient import AsyncClient from .AsyncClient import AsyncClient
from .SourceModAPI import SourceModAPI from .SourceModAPI import SourceModAPI
from .GameEvents import GameEvents from .Subscribe import GameEvents, Forwards
from .Utils import Utils from .Utils import Utils
from .Config import Config from .Config import Config
@ -30,6 +30,7 @@ class Torchlight():
self.API = SourceModAPI(self.WeakSelf) self.API = SourceModAPI(self.WeakSelf)
self.GameEvents = GameEvents(self.WeakSelf) self.GameEvents = GameEvents(self.WeakSelf)
self.Forwards = Forwards(self.WeakSelf)
self.DisableVotes = set() self.DisableVotes = set()
self.Disabled = 0 self.Disabled = 0
@ -49,7 +50,7 @@ class Torchlight():
self.GameEvents.HookEx("server_spawn", self.Event_ServerSpawn) self.GameEvents.HookEx("server_spawn", self.Event_ServerSpawn)
self.GameEvents.HookEx("player_say", self.Event_PlayerSay) self.GameEvents.HookEx("player_say", self.Event_PlayerSay)
def SayChat(self, message): def SayChat(self, message, player=None):
message = "\x0700FFFA[Torchlight]: \x01{0}".format(message) message = "\x0700FFFA[Torchlight]: \x01{0}".format(message)
if len(message) > 976: if len(message) > 976:
message = message[:973] + "..." message = message[:973] + "..."
@ -57,8 +58,25 @@ class Torchlight():
for line in lines: for line in lines:
asyncio.ensure_future(self.API.PrintToChatAll(line)) 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): def SayPrivate(self, player, message):
asyncio.ensure_future(self.API.PrintToChat(player.Index, "\x0700FFFA[Torchlight]: \x01{0}".format(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): def Reload(self):
self.Config.Load() self.Config.Load()
@ -70,6 +88,8 @@ class Torchlight():
def OnPublish(self, obj): def OnPublish(self, obj):
if obj["module"] == "gameevents": if obj["module"] == "gameevents":
self.GameEvents.OnPublish(obj) 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): def Event_ServerSpawn(self, hostname, address, ip, port, game, mapname, maxplayers, os, dedicated, password):
self.DisableVotes = set() self.DisableVotes = set()
@ -105,11 +125,13 @@ class TorchlightHandler():
# Pre Hook for late load # Pre Hook for late load
await self.Torchlight.GameEvents._Register(["player_connect", "player_activate"]) await self.Torchlight.GameEvents._Register(["player_connect", "player_activate"])
await self.Torchlight.Forwards._Register(["OnClientPostAdminCheck"])
self.Torchlight.InitModules() self.Torchlight.InitModules()
# Late load # Late load
await self.Torchlight.GameEvents.Replay(["player_connect", "player_activate"]) await self.Torchlight.GameEvents.Replay(["player_connect", "player_activate"])
await self.Torchlight.Forwards.Replay(["OnClientPostAdminCheck"])
async def Send(self, data): async def Send(self, data):
return await self._Client.Send(data) return await self._Client.Send(data)

0
Torchlight/Utils.py Normal file → Executable file
View File

0
Torchlight/__init__.py Normal file → Executable file
View File

View File

@ -1,6 +1,6 @@
{ {
"[U:1:51174697]": { "[U:1:51174697]": {
"name": "BotoX", "name": "BotoX",
"level": 10 "level": 100
} }
} }

View File

@ -3,7 +3,7 @@
{ {
"Host": "10.0.0.101", "Host": "10.0.0.101",
"Port": 27020, "Port": 27020,
"SampleRate": 48000 "SampleRate": 22050
}, },
"SMAPIServer": "SMAPIServer":
{ {
@ -30,10 +30,12 @@
}, },
"AntiSpam": "AntiSpam":
{ {
"ImmunityLevel": 5,
"MaxUsageSpan": 60, "MaxUsageSpan": 60,
"MaxUsageTime": 10, "MaxUsageTime": 10,
"PunishDelay": 60, "PunishDelay": 60,
"ImmunityLevel": 4 "StopLevel": 3,
"ChatCooldown": 15
}, },
"TorchRCON": "TorchRCON":
@ -44,5 +46,6 @@
}, },
"WolframAPIKey": "***", "WolframAPIKey": "***",
"WundergroundAPIKey": "***" "WundergroundAPIKey": "***",
"OpenWeatherAPIKey": "***"
} }

View File

@ -15,7 +15,11 @@ import Torchlight.Torchlight
from Torchlight.SourceRCONServer import SourceRCONServer from Torchlight.SourceRCONServer import SourceRCONServer
if __name__ == '__main__': if __name__ == '__main__':
logging.basicConfig(level = logging.DEBUG) 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() Loop = asyncio.get_event_loop()