diff --git a/actions/__init__.py b/actions/__init__.py index d735748..f3fa1a9 100644 --- a/actions/__init__.py +++ b/actions/__init__.py @@ -1,9 +1,8 @@ from actions.botlist import botlist -from actions.summon import summon +from actions.web import summon, whois from actions.access import banish, pardon from actions.control import puppet, nomad from actions.stupid import hmm, hmmscore, hmmscoreboard -from actions.web import whois actions = [ { diff --git a/actions/summon.py b/actions/summon.py deleted file mode 100644 index 647307e..0000000 --- a/actions/summon.py +++ /dev/null @@ -1,25 +0,0 @@ -from subprocess import Popen, PIPE -from email.mime.text import MIMEText - -def summon(self, name, source, response): - botnick = self.bot.botnick - author = self.bot.author - user, reason = response.split("!summon ")[1].split(" ", 1) - - email = "{}@tilde.team" - - message = MIMEText(" ".join([ - "My bot, {}, received a summoning request for you".format(botnick), - "from {} in channel {} for reason: {}".format(name, source, reason) - ])) - - message["From"] = email.format(botnick) - message["To"] = email.format(user) - message["Subject"] = "You have been summoned!" - - command = "/usr/sbin/sendmail -t -oi".split(" ") - p = Popen(command, stdin=PIPE, universal_newlines=True) - p.communicate(message.as_string()) - - confirmation = "{}: You have summoned {}".format(name, user) - self.bot.send_message(source, confirmation) \ No newline at end of file diff --git a/actions/web.py b/actions/web.py index a07d690..ddc2895 100644 --- a/actions/web.py +++ b/actions/web.py @@ -1,7 +1,79 @@ +from subprocess import Popen, PIPE +from email.mime.text import MIMEText + from urllib.request import Request, urlopen +from urllib.parse import urlencode from urllib.error import HTTPError from json import loads +def get_iden(devices, device_name): + for device in devices: + if device.get("nickname", "") == device_name: + return device.get("iden", "") + +def push_note(bot, title, body): + api_url = "https://api.pushbullet.com/v2" + extra_settings = bot.settings.get("extras", dict()) + pb_settings = extra_settings.get("pushbullet", dict()) + api_key = pb_settings.get("api", "") + device_name = pb_settings.get("device", "") + + list_devices = Request("{}/devices".format(api_url)) + list_devices.add_header("Access-Token", api_key) + + try: + data = loads(urlopen(list_devices).read()) + except HTTPError: + return + + devices = data.get("devices", list()) + iden = get_iden(devices, device_name) + + params = { + "device_iden": iden, + "type": "note", + "title": title, + "body": body + } + + post_params = urlencode(params).encode() + + pushes = Request("{}/pushes".format(api_url), post_params) + pushes.add_header("Access-Token", api_key) + + try: + response = loads(urlopen(pushes).read()) + except HTTPError as e: + return + +def summon(self, name, source, response): + botnick = self.bot.botnick + author = self.bot.author + user, reason = response.split("!summon ")[1].split(" ", 1) + + email = "{}@tilde.team" + subject = "You have been summoned!" + + text = " ".join([ + "My bot, {}, received a summoning request for you".format(botnick), + "from {} in channel {} for reason: {}".format(name, source, reason) + ]) + message = MIMEText(text) + + message["From"] = email.format(botnick) + message["To"] = email.format(user) + message["Subject"] = subject + + command = "/usr/sbin/sendmail -t -oi".split(" ") + p = Popen(command, stdin=PIPE, universal_newlines=True) + p.communicate(message.as_string()) + + if user == author: + push_note(self.bot, subject, text) + + confirmation = "{}: You have summoned {}".format(name, user) + self.bot.send_message(source, confirmation) + def whois(self, name, source, response): botnick = self.bot.botnick domain = response.split("!whois ")[1] diff --git a/app.py b/app.py index 27f4ffb..d0f6d54 100755 --- a/app.py +++ b/app.py @@ -4,16 +4,17 @@ from os.path import dirname, realpath from bot import Bot, Tasks, Responses from actions import actions +from coroutines import coroutines -kingme = [ - "#chaos" -] +debug = False +kingme = [] if debug else ["#chaos"] +channels = ["#bots", "#insane"] +# if not debug: +# channels.extend([]) -bot = Bot("127.0.0.1", 6667, "BabiliBot|py", [ - "#bots", - "#insane" -]) +bot = Bot("127.0.0.1", 6667, "BabiliBot|py", channels) responses = Responses(bot) +tasks = Tasks(bot) for action in actions: if "type" in action and "pattern" in action and "callback" in action: @@ -23,6 +24,14 @@ for action in actions: action["callback"] ) +# for coro in coroutines: +# worker = coro["worker"] +# interval = coro["interval"] +# state = coro.get("state", None) +# coro_state = state if state is not None else (bot,) +# tasks.add_coroutine(worker, interval, coro_state) +tasks.coroutines = coroutines + def try_to_king_me(channel): bot.send_message("ChanServ", "REGISTER {}", channel) bot.send_message("ChanServ", "SET Successor {} {}", channel, bot.botnick) @@ -61,6 +70,7 @@ def handle_message(name, source, response): print("::", bot.memories) if __name__ == "__main__": + bot.tasks = tasks bot.start(dirname(realpath(__file__)), { "pm": handle_pm, "mode": handle_mode, diff --git a/bot/core.py b/bot/core.py index 838f17f..f1511b0 100644 --- a/bot/core.py +++ b/bot/core.py @@ -14,6 +14,7 @@ class Bot: self.settings = dict() self.places = list() + self.tasks = None self.author = "" self.recv_size = 2048 @@ -195,6 +196,10 @@ class Bot: print("DEBUG: Joined") + if self.tasks is not None: + if getattr(self.tasks, "run", None) is not None: + self.tasks.run() + while self.running: message = self.ircsock.recv(self.recv_size).decode() message = message.strip(self.splitter) diff --git a/bot/tasks.py b/bot/tasks.py index 71c56e8..4718c7d 100644 --- a/bot/tasks.py +++ b/bot/tasks.py @@ -1,3 +1,37 @@ +import time +import sched +from threading import Thread + class Tasks: - def __init__(self): - pass \ No newline at end of file + def __init__(self, bot): + self.bot = bot + self.scheduler = sched.scheduler(time.time, time.sleep) + self.thread = Thread(target=self.worker, args=(self,)) + self.coroutines = list() + self.states = dict() + + def periodic(self, scheduler, interval, action, index, state=dict()): + self.states[index] = action(state) + scheduler.enter(interval, 1, self.periodic, ( + scheduler, interval, action, index, self.states[index] + )) + + def worker(self, tasks): + for c, coro in enumerate(tasks.coroutines): + interval = coro["interval"] + worker = coro["worker"] + state = coro.get("state", dict()) + state["bot"] = tasks.bot + tasks.periodic(tasks.scheduler, interval, worker, c, state) + tasks.scheduler.run() + + def add_coroutine(self, worker, interval, state=dict()): + self.coroutines.append({ + "worker": worker, + "interval": interval, + "state": state + }) + + def run(self): + self.thread.daemon = True + self.thread.start() \ No newline at end of file diff --git a/coroutines/__init__.py b/coroutines/__init__.py new file mode 100644 index 0000000..07549b1 --- /dev/null +++ b/coroutines/__init__.py @@ -0,0 +1,19 @@ +from coroutines.bbj import BBJ + +# { +# "worker": test, +# "interval": 3 +# } +# def test(bot): +# print("Testing {}".format(bot.botnick)) + +coroutines = [ + { + "worker": lambda state: BBJ(state).start(), + "interval": 5, + "state": { + "source": "http://localhost:7099/api", + "channels": ["#insane"], #team + } + } +] \ No newline at end of file diff --git a/coroutines/bbj.py b/coroutines/bbj.py new file mode 100644 index 0000000..159a283 --- /dev/null +++ b/coroutines/bbj.py @@ -0,0 +1,88 @@ +from urllib.request import Request, urlopen +from urllib.parse import urlencode +from urllib.error import HTTPError +from datetime import datetime +from json import loads, dumps +from re import sub + +class BBJ: + def __init__(self, state): + self.name = "bbj" + self.bot = state["bot"] + self.source = state["source"] + self.channels = state["channels"] + self.memory = state.get("memory", { + "initialized": False, + # "timestamp": datetime.now().timestamp(), + "known": dict() + }) + + def start(self): + if not self.memory["initialized"]: + self.memory["initialized"] = True + self.fetch(self.cache) + return self.run() + + def run(self): + self.fetch(self.mirror) + return { + "bot": self.bot, + "source": self.source, + "channels": self.channels, + "memory": self.memory + } + + def cache(self, item): + self.memory["known"][item["thread_id"]] = item["last_mod"] + + def process_thread(self, thread_id, thread): + data = thread.get("data", dict()) + title = data.get("title", "") + replies = data.get("reply_count", "") + messages = data.get("messages", "") + usermap = thread.get("usermap", dict()) + reply = messages[replies] + author = reply.get("author", "") + username = usermap[author].get("user_name", "") + body = reply.get("body", "") + body = sub(r">>\d\n\n", r"", body) + body = sub(r"\n", r" ", body) + php = "https://bbj.tilde.team/index.php" + link = "{}?thread_id={}".format(php, thread_id) + for channel in self.channels: + response = "'{}' ({}) : {} <{}>".format(title, username, body, link) + message = "[{}] {}".format(self.name, response) + self.bot.send_message(channel, message) + + def get_thread(self, thread_id, callback): + params = { + "thread_id": thread_id + } + post_params = str(dumps(params)).encode() + thread_load = Request("{}/thread_load".format(self.source), post_params) + thread_load.add_header("Content-Type", "application/json") + + try: + response = callback(thread_id, loads(urlopen(thread_load).read())) + except HTTPError: + return + + def mirror(self, item): + thread_id = item["thread_id"] + last_mod = self.memory["known"][thread_id] + if last_mod == item["last_mod"]: + return + + self.memory["known"][thread_id] = item["last_mod"] + self.get_thread(thread_id, self.process_thread) + + def fetch(self, callback): + thread_index = Request("{}/thread_index".format(self.source)) + + try: + response = loads(urlopen(thread_index).read()) + threads = response.get("data", dict()) + for thread in threads: + callback(thread) + except HTTPError: + return \ No newline at end of file diff --git a/coroutines/rss.py b/coroutines/rss.py new file mode 100644 index 0000000..e69de29