diff --git a/.gitignore b/.gitignore index 7d07c3d..9cc0fc5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +Database.json +Dump.txt Config.py -*.pyc \ No newline at end of file +*.pyc diff --git a/Locale/en.json b/Locale/en.json index da3fea2..0e79028 100644 --- a/Locale/en.json +++ b/Locale/en.json @@ -1,13 +1,13 @@ { "start": [ - "Hi {user.mention_markdown_v2()}!" + "*Hi* {user.mention_markdown_v2()}*!*" ], "help": [ - "There's no one around to help (yet)." + "*There's no one around to help (yet).*" ], "echo": { "empty": [ - "Echo what? Give me something to repeat." + "*Echo what? Give me something to repeat.*" ] }, "wish": { @@ -15,7 +15,7 @@ "*You wished for nothing! ✨*\n\n_Nothing happens..._" ], "done": [ - "*Your wish has been cast! ✨*\n\n_Chance of success: {0}%_" + "*Your wish has been cast! ✨*\n\n_Chance of success: *{Percent}%*._" ] }, "hug": { @@ -27,5 +27,22 @@ "others": [ "*{0} hugs {1} tightly... :3*" ] - } -} \ No newline at end of file + }, + "level": { + "empty": [ + "Check what's your level of something.\n\n*Usage*: {Cmd} ." + ], + "done": [ + "_Your level of *{Thing}* is... *{Percent}%*._" + ] + }, + "hash": [ + "*Usage*: {0} .\n\n*Available algorithms*: {1}." + ], + "eval": [ + "This feature is not implemented [Security Issue]." + ], + "time": [ + "Local time: *{0}*." + ] +} diff --git a/Locale/it.json b/Locale/it.json index 005626d..b103947 100644 --- a/Locale/it.json +++ b/Locale/it.json @@ -1,6 +1,6 @@ { "start": [ - "Ciao {user.mention_markdown_v2()}!" + "*Ciao* {0}*!*" ], "help": [ "*Non c'è nessuno qui ad aiutarti (per ora).*" @@ -15,7 +15,15 @@ "*Non hai desiderato nulla! ✨*\n\n_Non succede niente..._" ], "done": [ - "*Il tuo desiderio è stato espresso! ✨*\n\n_Probabilità che si avveri: {0}%_" + "*Il tuo desiderio è stato espresso! ✨*\n\n_Probabilità che si avveri: *{Percent}%*._" + ] + }, + "level": { + "empty": [ + "Controlla il tuo livello di qualcosa.\n\n*Uso*: {Cmd} ." + ], + "done": [ + "_Il tuo livello di *{Thing}* è... *{Percent}%*._" ] }, "hug": { diff --git a/StartWinDog b/StartWinDog new file mode 100755 index 0000000..28c1e09 --- /dev/null +++ b/StartWinDog @@ -0,0 +1,7 @@ +#!/bin/bash + +ScriptPath=$(realpath $0) +ScriptDir=$(dirname $ScriptPath) +cd $ScriptDir + +python3 WinDog.py diff --git a/WinDog.py b/WinDog.py index b9b49a4..a967fcd 100755 --- a/WinDog.py +++ b/WinDog.py @@ -1,36 +1,82 @@ #!/usr/bin/env python3 -# =================================== # -# WinDog multi-purpose chatbot -# Licensed under AGPLv3 by OctoSpacc -# =================================== # +# ================================== # +# WinDog multi-purpose chatbot # +# Licensed under AGPLv3 by OctoSpacc # +# ================================== # -import json +import json, hashlib, re, time, subprocess +from os import listdir from random import choice, randint +from types import SimpleNamespace from telegram import Update, ForceReply, Bot from telegram.utils.helpers import escape_markdown from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackContext from Config import * -Private = {} -Locale = {} +Db = {"Chats": {}} +Locale = {"Fallback": {}} -def CharEscape(String, Escape=''): +def SetupDb() -> None: + global Db + try: + with open('Database.json', 'r') as File: + Db = json.load(File) + except Exception: + pass + +def SetupLocale() -> None: + global Locale + for File in listdir('./Locale'): + Lang = File.split('.')[0] + try: + with open(f'./Locale/{File}') as File: + Locale[Lang] = json.load(File) + except Exception: + print(f'Cannot load {Lang} locale, exiting.') + raise + exit(1) + for Key in Locale[DefaultLang]: + Locale['Fallback'][Key] = Locale[DefaultLang][Key] + for Lang in Locale: + for Key in Locale[Lang]: + if not Key in Locale['Fallback']: + Locale['Fallback'][Key] = Locale[Lang][Key] + def __(Key:str, Lang:str=DefaultLang): + Set = None + Key = Key.split('.') + try: + Set = Locale.Locale[Lang] + for El in Key: + Set = Set[El] + except Exception: + Set = Locale.Locale['Fallback'] + for El in Key: + Set = Set[El] + return Set + Locale['__'] = __ + Locale['Locale'] = Locale + Locale = SimpleNamespace(**Locale) + +def CharEscape(String, Escape='') -> str: if Escape == 'MARKDOWN': return escape_markdown(String, version=2) - elif Escape == 'MARKDOWN_SPEECH': - for c in '.!_()[]<>': - String = String.replace(c, '\\'+c) - return String else: + if Escape == 'MARKDOWN_SPEECH': + Escape = '+-_.!()[]{}<>' + elif Escape == 'MARKDOWN_SPEECH_FORMAT': + Escape = '+-_.!()[]<>' for c in Escape: String = String.replace(c, '\\'+c) - return String + return String -def CommandFilter(Message): - return Message.lower().split(' ')[0][1:].split('@')[0] +def GetRawTokens(Text:str) -> list: + return Text.strip().replace('\t', ' ').replace(' ', ' ').replace(' ', ' ').split(' ') -def RandPercent(): +def CmdFilter(Msg) -> str: + return Msg.lower().split(' ')[0][1:].split('@')[0] + +def RandPercent() -> int: Num = randint(0,100) if Num == 100: Num = str(Num) + '\.00' @@ -38,77 +84,85 @@ def RandPercent(): Num = str(Num) + '\.' + str(randint(0,9)) + str(randint(0,9)) return Num -def start(update:Update, context:CallbackContext) -> None: +def cStart(update:Update, context:CallbackContext) -> None: if CmdRestrict(update): user = update.effective_user update.message.reply_markdown_v2( - fr'Hi {user.mention_markdown_v2()}\!', - #reply_markup=ForceReply(selective=True), - ) - -def help(update:Update, context:CallbackContext) -> None: - if CmdRestrict(update): - update.message.reply_markdown_v2( - CharEscape(choice(Locale[Lang]['help']), '.!()'), + CharEscape(choice(Locale.__('start')), 'MARKDOWN_SPEECH').format(user.mention_markdown_v2()), reply_to_message_id=update.message.message_id) -def echo(update:Update, context:CallbackContext) -> None: +def cHelp(update:Update, context:CallbackContext) -> None: if CmdRestrict(update): - Message = update.message.text - if len(Message.split(' ')) < 2: - Text = CharEscape(choice(Locale[Lang]['echo']['empty']), '.!') - update.message.reply_markdown_v2( - Text, - reply_to_message_id=update.message.message_id) - else: - Text = Message[len(Message.split(' ')[0])+1:] + update.message.reply_markdown_v2( + CharEscape(choice(Locale.__('help')), 'MARKDOWN_SPEECH'), + reply_to_message_id=update.message.message_id) + +def cConfig(update:Update, context:CallbackContext) -> None: + pass + +def cEcho(update:Update, context:CallbackContext) -> None: + if CmdRestrict(update): + Msg = update.message.text + if len(Msg.split(' ')) >= 2: + Text = Msg[len(Msg.split(' ')[0])+1:] update.message.reply_text( Text, reply_to_message_id=update.message.message_id) + else: + Text = CharEscape(choice(Locale.__('echo.empty')), '.!') + update.message.reply_markdown_v2( + Text, + reply_to_message_id=update.message.message_id) -def ping(update:Update, context:CallbackContext) -> None: +def cPing(update:Update, context:CallbackContext) -> None: if CmdRestrict(update): update.message.reply_markdown_v2( '*Pong\!*', reply_to_message_id=update.message.message_id) -def wish(update:Update, context:CallbackContext) -> None: +def percenter(update:Update, context:CallbackContext) -> None: if CmdRestrict(update): - if len(update.message.text.split(' ')) < 2: - Text = choice(Locale[Lang]['wish']['empty']) + Msg = update.message.text + Key = CmdFilter(Msg) + Toks = GetRawTokens(Msg) + Thing = Key.join(Msg.split(Key)[1:]).strip() + if len(Toks) >= 2: + Text = choice(Locale.__(f'{Key}.done')) else: - Text = choice(Locale[Lang]['wish']['done']) + Text = choice(Locale.__(f'{Key}.empty')) update.message.reply_markdown_v2( - CharEscape(Text, '.!').format(RandPercent()), + CharEscape(Text, '.!').format(Cmd=Toks[0], Percent=RandPercent(), Thing=Thing), reply_to_message_id=update.message.message_id) def multifun(update:Update, context:CallbackContext) -> None: if CmdRestrict(update): - Key = CommandFilter(update.message.text) - ReplyToMessage = update.message.message_id + Key = CmdFilter(update.message.text) + ReplyToMsg = update.message.message_id if update.message.reply_to_message: ReplyFromUID = update.message.reply_to_message.from_user.id - if ReplyFromUID == TGID and 'bot' in Locale[Lang][Key]: - Text = CharEscape(choice(Locale[Lang][Key]['bot']), 'MARKDOWN_SPEECH') - elif ReplyFromUID == update.message.from_user.id and 'self' in Locale[Lang][Key]: + if ReplyFromUID == TGID and 'bot' in Locale.__(Key): + Text = CharEscape(choice(Locale.__(f'{Key}.bot')), 'MARKDOWN_SPEECH') + elif ReplyFromUID == update.message.from_user.id and 'self' in Locale.__(Key): FromUName = CharEscape(update.message.from_user.first_name, 'MARKDOWN') - Text = CharEscape(choice(Locale[Lang][Key]['self']), 'MARKDOWN_SPEECH').format(FromUName) + Text = CharEscape(choice(Locale.__(f'{Key}.self')), 'MARKDOWN_SPEECH').format(FromUName) else: - if 'others' in Locale[Lang][Key]: + if 'others' in Locale.__(Key): FromUName = CharEscape(update.message.from_user.first_name, 'MARKDOWN') ToUName = CharEscape(update.message.reply_to_message.from_user.first_name, 'MARKDOWN') - Text = CharEscape(choice(Locale[Lang][Key]['others']), 'MARKDOWN_SPEECH').format(FromUName,ToUName) - ReplyToMessage = update.message.reply_to_message.message_id + Text = CharEscape(choice(Locale.__(f'{Key}.others')), 'MARKDOWN_SPEECH').format(FromUName,ToUName) + ReplyToMsg = update.message.reply_to_message.message_id else: - if 'empty' in Locale[Lang][Key]: - Text = CharEscape(choice(Locale[Lang][Key]['empty']), 'MARKDOWN_SPEECH') - update.message.reply_markdown_v2( - Text, - reply_to_message_id=ReplyToMessage) + if 'empty' in Locale.__(Key): + Text = CharEscape(choice(Locale.__(f'{Key}.empty')), 'MARKDOWN_SPEECH') + update.message.reply_markdown_v2(Text, reply_to_message_id=ReplyToMsg) +def cUnsplash(update:Update, context:CallbackContext) -> None: + pass def filters(update:Update, context:CallbackContext) -> None: - pass + if Debug and Dumper: + with open('Dump.txt', 'a') as File: + File.write(f'[{time.ctime()}] [{int(time.time())}] [{update.message.chat.id}] [{update.message.message_id}] [{update.message.from_user.id}] {update.message.text}\n') ''' if CmdRestrict(update): ChatID = update.message.chat.id @@ -130,51 +184,90 @@ def setfilter(update:Update, context:CallbackContext) -> None: Private['Chats'][ChatID]['Filters'][update.message.text] = {'Text':0} ''' -def CmdRestrict(update): +def cTime(update:Update, context:CallbackContext) -> None: + update.message.reply_markdown_v2( + CharEscape(choice(Locale.__('time')).format(time.ctime().replace(' ', ' ')), 'MARKDOWN_SPEECH'), + reply_to_message_id=update.message.message_id) + +def cHash(update:Update, context:CallbackContext) -> None: + if CmdRestrict(update): + Msg = update.message.text + Toks = GetRawTokens(Msg) + if len(Toks) >= 3 and Toks[1] in hashlib.algorithms_available: + Alg = Toks[1] + Caption = hashlib.new(Alg, Alg.join(Msg.split(Alg)[1:]).strip().encode()).hexdigest() + else: + Caption = CharEscape(choice(Locale.__('hash')).format(Toks[0], hashlib.algorithms_available), 'MARKDOWN_SPEECH') + update.message.reply_markdown_v2(Caption, reply_to_message_id=update.message.message_id) + +def cEval(update:Update, context:CallbackContext) -> None: + if CmdRestrict(update): + update.message.reply_markdown_v2( + CharEscape(choice(Locale.__('eval')), 'MARKDOWN_SPEECH'), + reply_to_message_id=update.message.message_id) + +def cExec(update:Update, context:CallbackContext) -> None: + if CmdRestrict(update): + Toks = GetRawTokens(update.message.text) + if len(Toks) >= 2 and Toks[1].lower() in ('date', 'neofetch', 'uptime'): + Caption = '```' + CharEscape(re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])').sub('', subprocess.run(('sh', '-c', Toks[1].lower()), stdout=subprocess.PIPE).stdout.decode()) , 'MARKDOWN') + '```', + update.message.reply_markdown_v2( + # + '```' + CharEscape( re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])').sub('', subprocess.run(('sh', '-c', Toks[1].lower()), + stdout=subprocess.PIPE).stdout.decode()) , 'MARKDOWN') + '```', + reply_to_message_id=update.message.message_id) + else: + update.message.reply_markdown_v2( + CharEscape(choice(Locale.__('eval')), 'MARKDOWN_SPEECH'), + reply_to_message_id=update.message.message_id) + +#def CmdArgs(Msg:str, Cfg:tuple=None): +# Args = [] +# Msg = Msg.strip().replace('\t', ' ') +# if Cfg: +# for i in Cfg: +# Args += [Msg.replace(' ', ' ').replace(' ', ' ').split(' ')[:i]] +# Msg = Msg +# else: +# return Msg.replace(' ', ' ').replace(' ', ' ').split(' ') + +def CmdRestrict(update) -> bool: if not TGRestrict: return True else: - if TGRestrict == 'Whitelist': + if TGRestrict.lower() == 'whitelist': if update.message.chat.id in TGWhitelist: return True return False -def main() -> None: - global Private, Locale +#def SendMsg(Data, context): +# pass - try: - with open('Private.json', 'r') as File: - Private = json.load(File) - except Exception: - Private = {} - if 'Chats' not in Private: - Private['Chats'] = {} - - try: - with open('Locale/{0}.json'.format(Lang)) as File: - Locale[Lang] = json.load(File) - except Exception: - print('Cannot load {0} locale, exiting'.format(Lang)) - raise - exit(1) +def Main() -> None: + SetupDb() + SetupLocale() #Private['Chats'].update({update.message.chat.id:{}}) updater = Updater(TGToken) dispatcher = updater.dispatcher - dispatcher.add_handler(CommandHandler('start', start)) - dispatcher.add_handler(CommandHandler('help', help)) - dispatcher.add_handler(CommandHandler('echo', echo)) - dispatcher.add_handler(CommandHandler('ping', ping)) - dispatcher.add_handler(CommandHandler('wish', wish)) - dispatcher.add_handler(CommandHandler('hug', multifun)) - dispatcher.add_handler(CommandHandler('pat', multifun)) - dispatcher.add_handler(CommandHandler('poke', multifun)) - dispatcher.add_handler(CommandHandler('cuddle', multifun)) - dispatcher.add_handler(CommandHandler('floor', multifun)) - dispatcher.add_handler(CommandHandler('hands', multifun)) - dispatcher.add_handler(CommandHandler('sessocto', multifun)) + dispatcher.add_handler(CommandHandler('start', cStart)) + dispatcher.add_handler(CommandHandler('config', cConfig)) + dispatcher.add_handler(CommandHandler('help', cHelp)) + dispatcher.add_handler(CommandHandler('echo', cEcho)) + dispatcher.add_handler(CommandHandler('ping', cPing)) + dispatcher.add_handler(CommandHandler('time', cTime)) + dispatcher.add_handler(CommandHandler('hash', cHash)) + dispatcher.add_handler(CommandHandler('eval', cEval)) + dispatcher.add_handler(CommandHandler('exec', cExec)) + dispatcher.add_handler(CommandHandler('unsplash', cUnsplash)) + + for Cmd in ('wish', 'level'): + dispatcher.add_handler(CommandHandler(Cmd, percenter)) + for Cmd in ('hug', 'pat', 'poke', 'cuddle', 'floor', 'hands', 'sessocto'): + dispatcher.add_handler(CommandHandler(Cmd, multifun)) + #dispatcher.add_handler(CommandHandler('setfilter', setfilter)) dispatcher.add_handler(MessageHandler(Filters.text & ~Filters.command, filters)) @@ -183,5 +276,5 @@ def main() -> None: updater.idle() if __name__ == '__main__': - main() + Main() print('Closing WinDog...')