diff --git a/.gitignore b/.gitignore index 55fb785..d9d163f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ sizelog *journal +auth.py + diff --git a/bot.py b/bot.py index 3d1514d..66794e8 100755 --- a/bot.py +++ b/bot.py @@ -1,102 +1,134 @@ #!/usr/bin/env python3 +import asyncio, os, importlib -import pydle, asyncio, dataset, sys, os, time +from irctokens import build, Line +from ircrobots import Bot as BaseBot +from ircrobots import Server as BaseServer +from ircrobots import ConnectionParams, SASLUserPass, SASLSCRAM -class Kim(pydle.Client): - async def on_connect(self): - print('Connected!') +from auth import username, password +import shared - self.modules = {} - self.cmd = {} - self.rawm = {} - self.help = {} - self.db = dataset.connect('sqlite:///database.db') - self.t=0 +def is_admin(func): + async def decorator(self,channel,nick,msg): + if nick.lower() in self.users and self.users[nick.lower()].account in self.admins: + await func(self,channel,nick,msg) + else: + await message(self,'core',channel,'you do not have permission to do that') + return decorator - print('loading modules...') - await self.loadMods() - print('joining channels') - for i in self.chansjoin: - await self.join(i) - print('Done!') - - # tilde +B bot - await self.set_mode(self.nickname, '+B') +#def is_chanop(func): - async def loadMods(self): - for i in [s for s in os.listdir('modules') if ".py" in s and '.swp' not in s]: - i = i[:-3] - print('loading', i) - m = __import__("modules."+i) - m = eval('m.'+i) - await m.init(self) - self.modules[i] = m +def command(commandname): + def decorator(func): + shared.commands[commandname] = func + return func + return decorator - async def on_invite(self, channel, by): - await self.modules['invite'].invite(self, channel, by) - # print('{} invited me to {}!'.format(by, channel)) - # self.t = time.time()+1 - # await self.join(channel) +def listener(listenername): + def decorator(func): + shared.listeners.append((listenername, func)) + return func + return decorator + +def rawm(rname): + def decorator(func): + shared.rawm[rname] = func + return func + return decorator - async def on_message(self, chan, source, msg): - if chan == self.nickname: - chan = source - if source != self.nickname: + +async def message(self,modname,channel,msg): + await self.send(build("PRIVMSG",[channel,f'[\x036{modname}\x0f] {msg}'])) - if time.time() > self.t: - - if msg == '!botlist': - await self.message(chan, 'helo im kim, a learning chatbot https://tildegit.org/xfnw/kim/') - await self.parseCommand(chan, source, msg) - for i in self.rawm: - await self.rawm[i](self, chan, source, msg) - async def parseCommand(self, chan, source, msg): - if msg[:len(self.prefix)] == self.prefix: - - msg = msg[len(self.prefix):] - cmd = msg.split(' ')[0] - msg = msg[len(cmd)+1:] - if len(cmd) < 1: - return +class Server(BaseServer): + async def line_read(self, line: Line): + print(f"{self.name} < {line.format()}") + if 'on_'+line.command.lower() in dir(self): + asyncio.create_task(self.__getattribute__('on_'+line.command.lower())(line)) + for listener in shared.listeners: + if listener[0] == line.command: + asyncio.create_task(listener[1](self,line)) - if cmd in self.cmd: - await self.cmd[cmd](self, chan, source, msg) - return - - # fuzzy search for commands - results = [i for i in self.cmd if i.startswith(cmd)] - if len(results) == 1: - await self.cmd[results[0]](self, chan, source, msg) + async def line_send(self, line: Line): + print(f"{self.name} > {line.format()}") - async def is_admin(self, nickname): + async def on_001(self, line): + asyncio.create_task(self.load_modules()) - # Check the WHOIS info to see if the source has identified with NickServ. - # This is a blocking operation, so use yield. - info = await self.whois(nickname) - if 'account' in info: - account = info['account'] - else: - # they are not nickserv registered - return False - return account in self.admins + async def load_modules(self): + for i in [s for s in os.listdir('modules') if '.py' in s and '.swp' not in s]: + i = i[:-3] + m = importlib.import_module('modules.' + i) + asyncio.create_task(m.init(self)) + shared.modules[i] = m - #async def on_private_message(self, trash, source, msg): - # if source != self.nickname: - # for i in self.rawm: - # await self.rawm[i](self, source, source, msg) + # depricated, to support old modules + async def message(self,channel,msg): + await self.send(build("PRIVMSG",[channel,msg])) + async def on_privmsg(self, line): + if line.tags and "batch" in line.tags and line.tags["batch"] == '1': + return + + + channel = line.params[0] + nick = line.source.split('!')[0] + msg = line.params[1] + + if channel == self.nickname: + channel = nick + + await self.handle_rawm(channel,nick,msg) + await self.handle_command(channel,nick,msg) + async def handle_rawm(self,channel,nick,msg): + for i in shared.rawm: + await shared.rawm[i](self,channel,nick,msg) + async def handle_command(self,channel,nick,msg): + if msg[:len(shared.prefix)] == shared.prefix: + msg = msg[len(shared.prefix):] + cmd = msg.split(' ')[0] + msg = msg[len(cmd)+1:] + if len(cmd) < 1: + return + + if cmd in shared.commands: + await shared.commands[cmd](self,channel,nick,msg) + return + + results = [i for i in shared.commands if i.startswith(cmd)] + if len(results) == 1: + await shared.commands[results[0]](self,channel,nick,msg) + + +class Bot(BaseBot): + def create_server(self, name: str): + return Server(self, name) + + +async def main(): + bot = Bot() + + sasl_params = SASLUserPass(username, password) + params = ConnectionParams( + "kim", + host = "irc.tilde.chat", + port = 6697, + tls = True, + sasl = sasl_params) + + await bot.add_server("tilde", params) + await bot.run() + if __name__ == "__main__": - client = Kim('kim', realname='owens bot') - client.admins = ['lickthecheese', 'ben', 'coffeeowl', 'gbmor', 'tomasino', 'ubergeek', 'deepend', 'calamitous', 'khuxkm'] - client.prefix = 'kim: ' - client.run('team.tilde.chat', tls=True, tls_verify=False) - + asyncio.run(main()) + diff --git a/modules/admin.py b/modules/admin.py index f0c6d21..157477a 100644 --- a/modules/admin.py +++ b/modules/admin.py @@ -1,52 +1,52 @@ -import importlib, time, asyncio, pydle +import importlib, time, asyncio, random +from bot import * + +quitmessages = [ + "time to die", + 'you can hide, but you can not run!', + "you're next", + 'bye', + 'the balun has been popped.', + ] + async def commit(self, chan, source, msg): await self.quit('{} told me to commit {}'.format(source,msg)) async def quit(self, chan, source, msg): - await self.quit('{} told me to {}'.format(source,msg)) + await self.send(build("QUIT",[random.choice(quitmessages)])) async def reloadmods(self, chan, source, msg): await self.message(chan, '[\x036admin\x0f] reloading modules...') - self.oldcmd = self.cmd - self.cmd = {} - self.rawm = {} - self.help = {} + shared.oldcmd = shared.commands + shared.commands = {} + shared.rawm = {} + shared.listeners = [] + shared.help = {} try: - for i in self.modules: - importlib.reload(self.modules[i]) - await self.modules[i].init(self) + for i in shared.modules: + importlib.reload(shared.modules[i]) + await shared.modules[i].init(self) #await self.message(chan, '[\x036admin\x0f] load {} sucess!'.format(i)) - await self.message(chan, '[\x036admin\x0f] done! {} modules reloaded!'.format(len(self.modules))) + await self.message(chan, '[\x036admin\x0f] done! {} modules reloaded!'.format(len(shared.modules))) except: await self.message(chan, '[\x036admin\x0f] reload failed... attempting to recover...') - self.cmd = self.oldcmd + shared.commands = shared.oldcmd -async def part(self, chan, source, msg): - await self.message(chan, '[\x036admin\x0f] bye {}'.format(msg)) - await self.part(msg) - -async def join(self, chan, source, msg): - self.t = time.time()+1 - await self.message(chan, '[\x036admin\x0f] joined {}'.format(msg)) - await self.join(msg) +async def rawcmd(self, chan, source, msg): + await self.send_raw(msg) async def joins(self, chan, source, msg): - await self.message(chan, '[\x036admin\x0f] I will drop commands for some seconds to ignore chanhistory...') + await self.message(chan, '[\x036admin\x0f] joining slowly as to not flood...') for i in self.chandb.all(): - self.t = time.time() + 5 - try: - await self.join(i['name']) - await asyncio.sleep(3) - print('joined {}'.format(i['name'])) - except pydle.client.AlreadyInChannel: - print('I am already in {}'.format(i['name'])) - await asyncio.sleep(3) - await self.message(chan, '[\x036admin\x0f] Sucess!') + await self.send(build("JOIN",[i['name']])) + await asyncio.sleep(1) + print('joined {}'.format(i['name'])) + await self.message(chan, '[\x036admin\x0f] Sucess! i may be laggy for a bit while i sort through all these channels...') async def aexec(self, code): # Make an async function with the code and `exec` it @@ -74,7 +74,7 @@ async def send(self, c, n, m): await self.message(c, '[\x036admin\x0f] sent') async def shut(self, c, n, m): - self.qtime[c] = time.time()+(60*10) + shared.qtime[c] = time.time()+(60*10) await self.message(c, '[\x036admin\x0f] Ok, il be back in 10 minutes') async def schans(self, c, n, m): @@ -94,11 +94,56 @@ async def addalias(self,c,n,m): await self.message(c,'[\x036admin\x0f] added "{}" alias for "{}"'.format(al,m)) +async def addot(self,c,n,m): + al = m.split(' ')[0] + m = m[len(al)+1:] # dont use the list since i want trailing spaces + if al in self.rawm: + await self.message(c,'[\x036admin\x0f] no dont overwrite a command dummy') + return + self.rawm[al]=Ot(m,al).ot + + await self.message(c,'[\x036admin\x0f] added "{}" trigger for "{}"'.format(al,m)) + + + +async def addtrigger(self,c,n,m): + al = m.split(' ')[0] + m = m[len(al)+1:] # dont use the list since i want trailing spaces + if al in self.rawm: + await self.message(c,'[\x036admin\x0f] no dont overwrite a command dummy') + return + self.rawm[al]=Trigger(m,al).trigger + + await self.message(c,'[\x036admin\x0f] added "{}" trigger for "{}"'.format(al,m)) + + + +class Ot(): + def __init__(self, ms, al): + self.ms = str(ms) + self.al = str(al) + async def ot(alself,self,c,n,m): + if alself.al in m and n != self.nickname: + asyncio.create_task(self.on_message(c,n,alself.ms.format(m))) + self.rawm.pop(alself.al) + + + + +class Trigger(): + def __init__(self, ms, al): + self.ms = str(ms) + self.al = str(al) + async def trigger(alself,self,c,n,m): + if alself.al in m: + asyncio.create_task(self.on_message(c,n,alself.ms.format(m))) + + class Alias(): def __init__(self, ms): self.ms = str(ms) async def alias(alself,self,c,n,m): - asyncio.create_task(self.parseCommand(c,n,alself.ms.format(m))) + asyncio.create_task(self.on_message(c,n,alself.ms.format(m))) @@ -106,30 +151,33 @@ commands = { 'quit': quit, 'reload': reloadmods, 'commit': commit, - 'part': part, - 'join': join, + 'raw': rawcmd, 'eval': ev, 'send': send, 'joins': joins, 'shut': shut, 'schans': schans, - 'addalias': addalias + 'addalias': addalias, + 'addtrigger': addtrigger, + 'addot': addot, } +@command('admin') +@is_admin async def adminHandle(self, chan, source, msg): - if await self.is_admin(source): msg = msg.split(' ') if len(msg) < 1 or not msg[0] in commands: await self.message(chan, '[\x036admin\x0f] Invalid command') return print('[ADMIN MODULE] {} told me to {}!!!'.format(source,msg[0])) - await commands[msg.pop(0)](self, chan, source, ' '.join(msg)) - else: - await self.message(chan, '[\x036admin\x0f] You do not have permission to do this') + asyncio.create_task(commands[msg.pop(0)](self, chan, source, ' '.join(msg))) async def init(self): - self.chandb = self.db['chan'] + self.chandb = shared.db['chan'] + + self.admins = ['lickthecheese'] + return self.cmd['admin'] = adminHandle self.help['admin'] = ['admin - various bot owner commands (more for subcommands)', 'sub-commands of admin, for more info do help admin : quit reload commit part join joins eval send'] diff --git a/modules/channels.py b/modules/channels.py deleted file mode 100644 index c78da3d..0000000 --- a/modules/channels.py +++ /dev/null @@ -1,16 +0,0 @@ - -async def action(self,c,n,m): - await self.message(c,'\x01ACTION {}\x01'.format(m[:400])) - - -async def echo(self,c,n,m): - await self.message(c,'[\x036channels\x0f] {}'.format(m[:400])) - -async def init(self): - self.chansjoin = ['#bots'] - - self.cmd['echo']=echo - self.cmd['action']=action - - - diff --git a/modules/invite.py b/modules/invite.py index 467ea34..4a7d716 100644 --- a/modules/invite.py +++ b/modules/invite.py @@ -1,26 +1,9 @@ -import time -async def invite(self, channel, by): - - if self.db['invite'].find_one(blacklist=channel): - print('{} invited me to {}, a blacklisted channel'.format(by,channel)) - return - - if self.db['invite'].find_one(enabled='true'): - - print('{} invited me to {}!'.format(by, channel)) - self.t = time.time()+1 - await self.join(channel) - else: - if self.chandb.find_one(name=channel): - self.t = time.time()+1 - await self.join(channel) - print('whee invited to {} by {}'.format(channel,by)) - return - - print('ive been invited but invites are disabled') +from bot import * +@listener('INVITE') +async def on_invite(self,line): + self.send(build("JOIN",[line.params[1]])) async def init(self): pass - diff --git a/modules/nlp.py b/modules/nlp.py index 4810de6..6ef1aa0 100644 --- a/modules/nlp.py +++ b/modules/nlp.py @@ -1,13 +1,14 @@ +from bot import * import dataset import random import time async def rec(self, m): - prew = self.db['prew'] - noch = self.db['noun'] - beg = self.db['beg'] - end = self.db['end'] + prew = shared.db['prew'] + noch = shared.db['noun'] + beg = shared.db['beg'] + end = shared.db['end'] pre = '' words = m.split(' ') if words[0] == 'admin': @@ -22,20 +23,20 @@ async def rec(self, m): end.insert(dict(word=pre)) async def getNoun(self, words, c): - if c in self.cstate: - oldnoun = self.cstate[c] + if c in shared.cstate: + oldnoun = shared.cstate[c] else: oldnoun = None - self.db['remsg'].insert_ignore(dict(noun=oldnoun,msg=' '.join(words)),['id']) + shared.db['remsg'].insert_ignore(dict(noun=oldnoun,msg=' '.join(words)),['id']) - nouns = [i['word'] for i in self.db['noun'].find()] + nouns = [i['word'] for i in shared.db['noun'].find()] out = {} for i in words: out[i] = nouns.count(i) noun = min(out, key=out.get) - conversation = self.db['conver'] + conversation = shared.db['conver'] if oldnoun != None: print("adding", [oldnoun,noun]) conversation.insert_ignore(dict(pre=oldnoun,pro=noun),['id']) @@ -44,27 +45,27 @@ async def getNoun(self, words, c): print("nextnoun:",nextnoun) if len(nextnoun) > 0: noun = random.choice(nextnoun) - self.cstate[c] = noun + shared.cstate[c] = noun return noun async def genOut(self, noun): - oldresponses = [i['msg'] for i in self.db['remsg'].find(noun=noun)] + oldresponses = [i['msg'] for i in shared.db['remsg'].find(noun=noun)] if len(oldresponses) > 0: return random.choice(oldresponses).split(' ') - prew = self.db['prew'] - beg = [ i['word'] for i in self.db['beg'].find() ] - end = [ i['word'] for i in self.db['end'].find() ] - nouns = [i['word'] for i in self.db['noun'].find()] + prew = shared.db['prew'] + beg = [ i['word'] for i in shared.db['beg'].find() ] + end = [ i['word'] for i in shared.db['end'].find() ] + nouns = [i['word'] for i in shared.db['noun'].find()] iter=0 out = [noun] - while (out[0] not in beg or nouns.count(out[0])-1 > iter * self.enmul) and iter < 7: + while (out[0] not in beg or nouns.count(out[0])-1 > iter * shared.enmul) and iter < 7: try: out = [ random.choice(list(prew.find(pro=out[0])))['pre'] ] + out except IndexError: iter += 69 iter += 1 iter = 0 - while (out[-1] not in end or nouns.count(out[-1])-1 > iter * self.enmul) and iter < 7: + while (out[-1] not in end or nouns.count(out[-1])-1 > iter * shared.enmul) and iter < 7: try: out.append(random.choice(list(prew.find(pre=out[-1])))['pro']) @@ -75,19 +76,19 @@ async def genOut(self, noun): async def filter(self, c, n, m): - if self.t > time.time() or c in self.qtime and self.qtime[c] > time.time(): + if c in shared.qtime and shared.qtime[c] > time.time(): return - if m[:len(self.prefix)] == self.prefix: - m = m[len(self.prefix):] + if m[:len(shared.prefix)] == shared.prefix: + m = m[len(shared.prefix):] await go(self, c, n, m) elif m[:4] == 'kim ': m = m[4:] await go(self, c, n, m) else: if len(m.split(' ')) > 1: - if self.learntime + self.learndelay < time.time(): + if shared.learntime + shared.learndelay < time.time(): await rec(self, m) - self.learntime = time.time() + shared.learntime = time.time() async def go(self, c, n, m): await rec(self, m) @@ -98,11 +99,11 @@ async def go(self, c, n, m): async def init(self): - self.qtime = {} + shared.qtime = {} - self.learntime = 0 - self.learndelay = 4 - self.enmul = 40 - self.rawm['nlp'] = filter + shared.learntime = 0 + shared.learndelay = 4 + shared.enmul = 40 + shared.rawm['nlp'] = filter - self.cstate = {} + shared.cstate = {} diff --git a/modules/test.py b/modules/test.py new file mode 100644 index 0000000..c385223 --- /dev/null +++ b/modules/test.py @@ -0,0 +1,14 @@ +import asyncio +import bot + + +@bot.command('test') +@bot.is_admin +async def testy(self,channel,nick,msg): + await bot.message(self,'test',channel,'hi there') + +async def init(self): + + await self.send_raw("join #bots") + + diff --git a/shared.py b/shared.py new file mode 100644 index 0000000..a918907 --- /dev/null +++ b/shared.py @@ -0,0 +1,12 @@ + +import dataset + +prefix = 'kim: ' +modules = {} +listeners = [] +commands = {} +rawm = {} +db = dataset.connect('sqlite:///database.db') + +qtime = {} +