Compare commits

...

17 Commits

25 changed files with 252 additions and 179 deletions

1
.gitignore vendored
View File

@ -2,4 +2,5 @@
*.db
*.pyc
*.patch
auth.py

View File

@ -1,12 +1,5 @@
# oirc-bot
general purpose bot lol it does cool things
REWRITE TIME
## how to use it
1. git clone it
2. choose which modules you want to enable in `modules/`
3. set admins and nickname and stuff in the bottom of `bot.py`
## other bots that use the oirc-bot framework
- [kiedtl/ircbot](//github.com/kiedtl/ircbot/)

196
bot.py
View File

@ -1,106 +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 Balun(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
self.acceptInvites=True
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):
if not self.acceptInvites:
return
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
async def on_join(self, channel, person):
await super().on_join(channel, person)
await self.modules['usrinfo'].on_join(self,channel,person)
async def on_ctcp(self, by, chan, what, contents):
await self.on_message(chan,by,"{} {}".format(what,contents)) # treat ctcp as normal messages
async def on_message(self, chan, source, msg):
if chan == self.nickname: # dont try to message yourself when people dm you lmfao
chan = source
if source != self.nickname:
def rawm(rname):
def decorator(func):
shared.rawm[rname] = func
return func
return decorator
if time.time() > self.t:
if msg == '!botlist':
await self.message(chan, 'helo im owen\'s nice bot https://xfnw.ttm.sh/git/oirc')
await self.parseCommand(chan, source, msg)
for i in self.rawm:
await self.rawm[i](self, chan, source, msg)
async def message(self,modname,channel,msg):
await self.send(build("PRIVMSG",[channel,f'[\x036{modname}\x0f] {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):
# 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 on_001(self, line):
asyncio.create_task(self.load_modules())
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
# 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(
"balun",
host = "irc.tilde.chat",
port = 6697,
tls = True,
sasl = sasl_params)
await bot.add_server("tilde", params)
await bot.run()
if __name__ == "__main__":
client = Balun('balun', realname='owens bot')
client.admins = ['lickthecheese', 'ben', 'coffeeowl', 'gbmor', 'tomasino', 'ubergeek', 'deepend', 'calamitous', 'khuxkm']
client.prefix = '.'
client.run('team.tilde.chat', tls=True, tls_verify=False)
asyncio.run(main())

View File

@ -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
@ -151,8 +151,7 @@ commands = {
'quit': quit,
'reload': reloadmods,
'commit': commit,
'part': part,
'join': join,
'raw': rawcmd,
'eval': ev,
'send': send,
'joins': joins,
@ -163,20 +162,21 @@ commands = {
'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]))
asyncio.create_task(commands[msg.pop(0)](self, chan, source, ' '.join(msg)))
else:
await self.message(chan, '[\x036admin\x0f] You do not have permission to do this')
async def init(self):
self.chandb = self.db['chan']
self.chandb = shared.db['chan']
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 <command>: quit reload commit part join joins eval send']

12
modules/botlist.py Normal file
View File

@ -0,0 +1,12 @@
from bot import *
modulename='botlist'
@rawm('botlist')
async def botlist(s,c,n,m):
if m == '!botlist':
await message(s,modulename,c,'hi im balun ; prefix . ; owner xfnw')
async def init(self):
await self.send(build("MODE",[self.nickname,'+B']))

View File

@ -1,5 +1,6 @@
import modules.identify as ident
import asyncio
from bot import *
async def bal(self):
bals = {}
@ -32,12 +33,15 @@ async def send(self,c,n,m):
await self.message(c, '[\x036coin\x0f] invalid syntax')
return
try:
to = await ident.user(self, m.pop(0))
to = self.users[m.pop(0).lower()].account
except:
await self.message(c, '[\x036coin\x0f] that user is not logged in. refusing so coins are not lost')
if to == '':
await self.message(c, '[\x036coin\x0f] they must authenticate with nickserv.')
return
amount = round(float(m.pop(0)),2)
message = ' '.join(m)
sender = await ident.user(self, n)
sender = self.users[n.lower()].account
self.ledger.insert(dict(to=to,sender=sender,amount=amount,message=message))
@ -48,9 +52,11 @@ async def balance(self,c,n,m):
if len(m) < 1:
m = n
try:
m = await ident.user(self, m)
m = self.users[m.lower()].account
except:
m = m
if m == '':
m = m
bals = await bal(self)
if m in bals:
latest = self.ledger.find_one(to=m,order_by='-id')
@ -76,16 +82,16 @@ async def richest(self,c,n,m):
))
async def init(self):
self.ledger = self.db['ledger']
self.ledger = shared.db['ledger']
self.initfund = 1
self.cmd['tipcoins'] = send
self.cmd['sendcoins'] = send
self.cmd['balance'] = balance
self.cmd['richest'] = richest
shared.commands['tipcoins'] = send
shared.commands['sendcoins'] = send
shared.commands['balance'] = balance
shared.commands['richest'] = richest
return
self.help['sendcoins'] = ['sendcoins <recipient> <amount> [message] - send someone coins. note (more)','this does NOT verify transactions went through!! check your balance after']
self.help['balance'] = ['balance [person] - check someone\'s balance','coins owo']
self.help['richest'] = ['richest - who has the most coins','coins owo']

9
modules/core.py Normal file
View File

@ -0,0 +1,9 @@
from irctokens import build, Line
import bot
async def init(self):
self.admins = ['xfnw','lickthecheese']

View File

@ -1,8 +1,10 @@
import random
from bot import *
async def coffeeup(self,c,n,m):
if c in ['#coffee','#tea','#water','#CAPS']:
if c in ['#coffee','#tea','#water','#CAPS','#sodawater']:
if (c[1:]+"!" in m and c+'!' not in m) or c=='#coffee' and ('latte!' in m or 'espresso!' in m or 'cappucino!' in m) or c=='#tea' and ('chai!' in m):
cc = self.coffee.find_one(name=c)
if cc:
@ -22,8 +24,8 @@ async def coffeeup(self,c,n,m):
async def init(self):
self.rawm['coffeeup'] = coffeeup
self.coffee = self.db['coffee']
shared.rawm['coffeeup'] = coffeeup
self.coffee = shared.db['coffee']
self.coffeetypes = [
"kum\u200cquat's aeropressed ",

View File

@ -1,7 +1,7 @@
from bot import *
import json, requests
@command('lookup')
async def lookup(self,c,n,m):
if len(m) < 1:
await self.message(c, '[\x036ham\x0f] you need the callsign lol')
@ -18,7 +18,7 @@ async def lookup(self,c,n,m):
async def init(self):
self.cmd['lookup'] = lookup
self.help['lookup'] = ['lookup <callsign> - lookup a ham callsign','ROBERT']
pass
#self.help['lookup'] = ['lookup <callsign> - lookup a ham callsign','ROBERT']

View File

@ -1,12 +0,0 @@
async def user(self,nick):
u = await self.whois(nick)
if u and u['account']:
return u['account']
else:
raise Exception('NotLoggedIn')
async def init(self):
pass

9
modules/invite.py Normal file
View File

@ -0,0 +1,9 @@
from bot import *
@listener('INVITE')
async def on_invite(self,line):
self.send(build("JOIN",[line.params[1]]))
async def init(self):
pass

View File

@ -1,4 +1,4 @@
from bot import *
import random
async def getkeep(self,c,n,m):
@ -72,18 +72,18 @@ async def rmkeep(self,c,n,m):
await self.message(c,'[\x036keep\x0f] you must have +o in #balun')
async def init(self):
self.keepdb = self.db['keep']
self.keepdb = shared.db['keep']
self.cmd['keep'] = getkeep
self.help['keep'] = ['keep - get keeps about keep','lets learn about keep!']
shared.commands['keep'] = getkeep
#self.help['keep'] = ['keep - get keeps about keep','lets learn about keep!']
self.cmd['addkeep'] = addkeep
self.help['addkeep'] = ['addkeep <keep> - add a new keep (more)','if you find something offensive contact lickthecheese, he can remove it and/or tell you who added it so watch out!']
shared.commands['addkeep'] = addkeep
#self.help['addkeep'] = ['addkeep <keep> - add a new keep (more)','if you find something offensive contact lickthecheese, he can remove it and/or tell you who added it so watch out!']
self.cmd['grabkeep'] = grabkeep
self.help['grabkeep'] = ['grabkeep [back] - grab something to keep','tooootally did not steal this from bitbot']
shared.commands['grabkeep'] = grabkeep
#self.help['grabkeep'] = ['grabkeep [back] - grab something to keep','tooootally did not steal this from bitbot']
self.rmkeepchan = "#balun"
self.cmd['rmkeep'] = rmkeep
self.help['rmkeep'] = ['rmkeep <criteria> <pattern> - remove some keep(s). criteria types in (more)','types of criteria: n|nick q|quote eg "rmkeep nick spammer" to get rid of all keeps created by nick spammer']
shared.commands['rmkeep'] = rmkeep
#self.help['rmkeep'] = ['rmkeep <criteria> <pattern> - remove some keep(s). criteria types in (more)','types of criteria: n|nick q|quote eg "rmkeep nick spammer" to get rid of all keeps created by nick spammer']

View File

@ -1,9 +1,9 @@
from bot import *
import random
async def owologger(self,c,n,m):
print("<{} {}> {}".format(c,n,m))
if m[:len(self.prefix)] == self.prefix:
if m[:len(shared.prefix)] == shared.prefix:
return
if c not in self.owolog:
self.owolog[c] = []
@ -32,7 +32,7 @@ async def owotext(self, back, chan):
async def init(self):
self.owolog = {}
self.rawm['owolog'] = owologger
self.cmd['owo'] = owoify
self.help['owo'] = ['owo [num] - owoify the text', 'owo owo uwu']
shared.rawm['owolog'] = owologger
shared.commands['owo'] = owoify
#self.help['owo'] = ['owo [num] - owoify the text', 'owo owo uwu']

View File

@ -1,6 +1,6 @@
import subprocess
from bot import *
def isfloat(value):
try:
@ -16,7 +16,7 @@ async def rpninp(self, chan, nick, msg):
try:
msg = msg.replace('+',' + ')
msg = msg.replace('a',' a ')
msg = msg.replace('-',' - ')
#msg = msg.replace('-',' - ')
msg = msg.replace('s',' s ')
msg = msg.replace('\\',' \\ ')
msg = msg.replace('*',' * ')
@ -81,12 +81,12 @@ async def rpntoggle(self, chan, nick, msg):
await self.message(chan, '[\x036rpn\x0f] rpn outputting has been enabled')
async def init(self):
self.help['rpn'] = ['rpn <inp> - simple reverse polish notation calculator (more)', 'it has an alias of . so you can just do {}. <inp>, and if enabled it will also parse floats and functions as input. there are 4 functions, add (+|a), subtract (-|s), multiply (*|x|m), and devide (/|d), and p to print register 0'.format(self.prefix)]
self.cmd['rpn'] = rpntinp
self.cmd['.'] = rpntinp
self.rawm['rpn'] = rpninp
self.cmd['rt'] = rpntoggle
self.help['rt'] = ['rt - toggle the output of rpn calculatons into the channel', 'rpn is cool']
#self.help['rpn'] = ['rpn <inp> - simple reverse polish notation calculator (more)', 'it has an alias of . so you can just do {}. <inp>, and if enabled it will also parse floats and functions as input. there are 4 functions, add (+|a), subtract (-|s), multiply (*|x|m), and devide (/|d), and p to print register 0'.format(self.prefix)]
shared.commands['rpn'] = rpntinp
shared.commands['.'] = rpntinp
shared.rawm['rpn'] = rpninp
shared.commands['rt'] = rpntoggle
#self.help['rt'] = ['rt - toggle the output of rpn calculatons into the channel', 'rpn is cool']
self.rpnhist = {}

14
modules/test.py Normal file
View File

@ -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")

0
requirements.txt Normal file
View File

11
shared.py Normal file
View File

@ -0,0 +1,11 @@
import dataset
prefix = '.'
modules = {}
listeners = []
commands = {}
rawm = {}
db = dataset.connect('sqlite:///database.db')