Commit 1734bcc5 authored by Benjamin Graillot's avatar Benjamin Graillot

Initial commit

parents
src/secrets.py
src/config.py
operators = {'yournick': float('+inf')}
import asyncio
import aioimaplib
import email
import irc.bot
import config
from subs import subs
import threading
import re
import time
import quopri
def write_subs(subs):
with open('subs.py', 'w') as s:
s.write('subs = { ' + ',\n'.join([repr(nick) + ':{' + ',\n'.join([repr(channel) + ': [' + ','.join([repr(patt) for patt in subs[nick][channel]]) + ']' for channel in subs[nick]]) + '}' for nick in subs]) + ' }\n')
class Bot(irc.bot.SingleServerIRCBot):
def __init__(self, nickname, channel="#bot", server="irc.crans.org", port=6667):
irc.bot.SingleServerIRCBot.__init__(self, [(server, port)], nickname, nickname)
self.nick = nickname
self.channel = channel
self.operators = config.operators
self.started = False
self.active = []
#self.on_command_ext = lambda *args, **kwargs: None
#self.on_pubmsg_ext = lambda conn, e: None
#self.source_file = ""
def on_nicknameinuse(self, conn, e):
conn.nick(conn.get_nickname() + "_")
self.nick += "_"
def on_welcome(self, conn, e):
conn.join(self.channel)
self.on_welcome_ext(conn, e)
def on_privmsg(self, conn, e):
if e.source.nick in self.operators:
return self.do_command(conn, e.arguments[0].split(' '), self.operators[e.source.nick], e.source.nick)
return self.do_command(conn, e.arguments[0].split(' '), 0, e.source.nick)
def do_command(self, conn, command, level, source):
if command[0].casefold() == "op":
if len(command) == 2:
dst_level = 100
else:
try:
dst_level = int(command[2])
except:
dst_level = 0
if 0 <= dst_level < level:
self.operators[command[1]] = dst_level
elif command[0].casefold() == "upgrade" and level >= float("inf"):
pass
#if self.source_file:
# self.do_command_ext, self.on_pubmsg_ext = updgrade(self.source_file)
elif command[0].casefold() == "join" and level >= 100:
for channel in command[1:]:
conn.join(channel)
elif command[0].casefold() == "leave" and level >= 100:
for channel in command[1:]:
conn.part(channel, "Ce n'est qu'un au revoir")
elif command[0].casefold() == "on" and level >= 100:
for channel in command[1:]:
self.active.append(channel)
elif command[0].casefold() == "off" and level >= 100:
for channel in command[1:]:
if channel in self.active:
self.active.remove(channel)
else:
self.do_command_ext(conn, command, level, source)
class Ninja(Bot):
def __init__(self):
Bot.__init__(self, "NinjaBot")
def on_welcome_ext(self, conn, e):
conn.join('#wikistalk')
l = [ list(subs[nick].keys()) for nick in subs ]
channels = []
for k in l: channels += k
channels = set(channels)
for channel in channels:
if channel.startswith('#'):
conn.join(channel)
def do_command_ext(self, conn, command, level, source):
global subs
if source not in subs:
subs[source] = {}
if command[0].casefold() == "help":
conn.privmsg(source, "Commandes :")
conn.privmsg(source, " - sub (#channel|yournick) regex : abonner #channel ou soi-même aux modifications sur les pages matchant regex (au format python).")
conn.privmsg(source, " - list : lister ses abonnements.")
conn.privmsg(source, " - del (#channel|yournick) n : supprimer le nième abonnement donné par list.")
if level >= 100:
conn.privmsg(source, " - listall : lister tous les abonnements.")
if level >= float('inf'):
conn.privmsg(source, " - sudo nick command : exécuter la commande en tant que nick (attention : les éventuels messages privés de réponse seront envoyés à cette personne).")
elif command[0].casefold() == "sub":
if len(command) >= 3:
patt = ' '.join(command[2:])
channel = command[1] if command[1].startswith('#') else source
if channel not in subs[source]:
subs[source][channel] = []
if channel.startswith('#'): conn.join(channel)
subs[source][channel].append(patt)
elif command[0].casefold() == "del":
if len(command) < 2:
return
channel = command[1] if command[1].startswith('#') else source
if channel not in subs[source]: return
if len(command) == 2:
if channel in subs[source]: del subs[source][channel]
if channel.startswith('#'):
if any(channel in subs[nick] for nick in subs):
conn.part(channel, "Plus d'abonnement sur {channel}".format(channel=channel))
elif len(command) == 3:
if command[2].isnumeric():
n = int(command[2])
if n < len(subs[source][channel]):
del subs[source][channel][n]
if len(subs[source][channel]) == 0:
del subs[source][channel]
if channel.startswith('#'):
if any(channel in subs[nick] for nick in subs):
conn.part(channel, "Plus d'abonnement sur {channel}".format(channel=channel))
elif command[0].casefold() == "list":
for channel in subs[source]:
conn.privmsg(source, channel)
for i, patt in enumerate(subs[source][channel]):
conn.privmsg(source, '+ {i}. {patt}'.format(i=i, patt=patt))
elif command[0].casefold() == "sudo":
if len(command) >= 3 and level == float('inf'):
self.do_command_ext(conn, command[2:], 0, command[1])
elif command[0].casefold() == "listall" and level >= 100:
for nick in subs:
conn.privmsg(source, nick + ':')
for channel in subs[nick]:
conn.privmsg(source, ' - '+channel)
for i, patt in enumerate(subs[nick][channel]):
conn.privmsg(source, ' + {i}. {patt}'.format(i=i, patt=patt))
write_subs(subs)
def push_update(self, page, user, revision, comment, attachment=False):
global subs
patts = {}
for nick in subs:
for channel in subs[nick]:
if channel not in patts: patts[channel] = []
patts[channel] += subs[nick][channel]
for channel in patts:
if any(re.fullmatch(patt, page) for patt in patts[channel]):
t = time.time()
if not attachment:
if comment:
self.connection.privmsg(channel, '\x0310,99{page}\x0f \x033,99{user}\x0f \x0399,99{comment}\x0f \x0315,99(r{revision})'.format(page=page, user=user, revision=revision, comment=comment))
else:
self.connection.privmsg(channel, '\x0310,99{page}\x0f \x033,99{user}\x0f \x0315,99(r{revision})\x0f'.format(page=page, user=user, revision=revision))
else:
self.connection.privmsg(channel, '\x0310,99{page}\x0f \x033,99{user}\x0f \x0399,99{comment}\x0f : \x0310,99{revision}\x0f'.format(page=page, user=user, comment=comment, revision=revision))
while t + 5 > time.time():
time.sleep(1)
@asyncio.coroutine
def idle_loop(host, user, password, ninja):
imap_client = aioimaplib.IMAP4_SSL(host=host, timeout=30)
yield from imap_client.wait_hello_from_server()
yield from imap_client.login(user, password)
yield from imap_client.select()
while True:
idle = yield from imap_client.idle_start(timeout=60)
msgs = yield from imap_client.wait_server_push()
imap_client.idle_done()
yield from asyncio.wait_for(idle, 30)
for m in msgs:
if m.endswith(' EXISTS'):
mail = yield from imap_client.uid('fetch', m[:-7], '(RFC822)')
mail = email.message_from_bytes(mail.lines[1])
body = quopri.decodestring(mail.get_payload()).decode('utf-8').replace('\r', '')
page_user = re.search(r'La page « (?P<page>.*) » a été modifiée par (?P<user>.*) :', body)
revision = re.search(r'\?action=diff&rev1=[0-9]+&rev2=(?P<rev>[0-9]+)\n', body)
comment = re.search(r'\s*Commentaire :\s*\n\s*(?P<comment>[^\n]*)\n', body)
if comment: comment = comment.group('comment')
if page_user:
ninja.push_update(page_user.group('page'), page_user.group('user'), int(revision.group('rev')), comment)
else:
page_user = re.search(r'vous vous êtes abonné aux notifications de changements pour la page "(?P<page>.*)"\.Une pièce jointe (?P<add>(?:vient d\'y être ajouté)|(?:de cette page vient d\'être supprimée)) par (?P<user>.*)\. Quelques détails sur la pièce jointe :', body)
piece = re.search(r'Nom\s*:\s*(?P<name>[^\n]*)\n', body)
if page_user:
ninja.push_update(page_user.group('page'), page_user.group('user'), piece.group('name'), "ajout d'une pièce jointe" if page_user.group('add').startswith('v') else 'suppression de la pièce jointe', attachment=True)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
ninja = Ninja()
t = threading.Thread(target=ninja.start)
t.start()
while True:
try:
loop.run_until_complete(idle_loop(secrets.host, secrets.user, secrets.password, ninja))
except:
pass
host = ''
user = ''
password = ''
subs = {}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment