From 57b9006a9943a16ab7100241fce6faabe14a1faf Mon Sep 17 00:00:00 2001 From: Niles Rogoff Date: Sun, 2 Apr 2017 19:14:31 -0400 Subject: [PATCH] Initial commit --- readme.md | 5 +++ stream.py | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++ stream2.py | 41 +++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 readme.md create mode 100644 stream.py create mode 100644 stream2.py diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..cef5fdf --- /dev/null +++ b/readme.md @@ -0,0 +1,5 @@ +## ctlbot + +Simple bot that scrapes for new messages in the nntpchan's ctl (moderation) channel, and sends them to irc if it finds any. + +It needs to be split across two scripts because twisted is garbage and you can't have multiple reactors at a time diff --git a/stream.py b/stream.py new file mode 100644 index 0000000..91d2355 --- /dev/null +++ b/stream.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +import subprocess +import random +import time +import string +import sys +import BTEdb +import base64 +import json +user = "CHANGEME" +pw = "CHANGEME" +from twisted.internet import reactor, protocol, ssl +import twisted.protocols.basic + +#debug = True +debug = True +messages = [] + +db = BTEdb.Database("db.json") +if not db.TableExists("main"): + db.CreateTable("main") + db.Insert("main", oldmax=0) +oldmax = db.Dump("main")[0]["oldmax"] + + +def handle_text(s, lines): # handle a text/plain encoded section + messages.append(s + " /// " + " ".join(lines[lines.index(""):])) # subject // line1 line2 line3 + + +def handle_part(s, lines): # handle one part of a multipart/mixed encoding + types = [k.split()[1][:-1] for k in lines if "Content-Type:" in k] + if types[0] == "text/plain": + messages.append( + s + " /// " + base64.b64decode(" ".join(lines[lines.index(""):])).decode("utf-8").replace("\n", " ")) + else: + print(types[0] + " is not text/plain, skipping") + + +def handle(lines): + if len(lines) == 0: + return + types = [k.split()[1][:-1] for k in lines if "Content-Type:" in k] + subj = [" ".join(k.split()[1:]) for k in lines if "Subject:" in k][0] # grab subject + if types[0] == "text/plain": # if it's plain, delegate that + handle_text(subj, lines) + elif types[0] == "message/rfc822": # if it's rfc822, extract the plain section and delegate that + handle_text(subj, lines[lines.index("") + 1:]) + else: # otherwise split at the boundry and handle each part + bound = [k.split()[2].replace("boundary=", "") + for k in lines if "multipart/mixed" in k][0] + r = [[]] + for l in lines: + if l == "--" + bound: + r.append([]) + else: + r[-1].append(l) + for s in r: + handle_part(subj, s) + + +class client(twisted.protocols.basic.LineReceiver): + def sl(self, line): + if debug: + print("Send: " + line) + self.sendLine(line.encode("utf-8")) + def __init__(self): + self.max = 0 + self.min = 0 + self.in_message = False + self.this_message = [] + def lineReceived(self, data): + data = data.decode("utf-8") + if debug: + print("Recv: " + data) + if len(data) == 0: + self.this_message.append("") + return + elif self.in_message: + if data == "." or data.split()[0] == "430": + self.in_message = False + handle(self.this_message) + self.cur += 1 + if self.cur < self.max: + self.sl("ARTICLE " + str(self.cur)) + self.in_message = True + self.this_message = [] + else: + db.Truncate("main") + db.Insert("main", oldmax=self.max) + self.sl("QUIT") + else: + self.this_message.append(data) + data = data.split() + if data[0] == "200": # posting allowed + # self.sl("AUTHINFO USER " + user) + # if data[0] == "381": # password required + # self.sl("AUTHINFO PASS " + pw) + # if data[0] == "281": # authentication success + self.sl("GROUP ctl") + if data[0] == "211": # group stats + self.min = int(data[2]) + self.max = int(data[3]) + if self.max > oldmax: + self.cur = oldmax + 1 + self.sl("ARTICLE " + str(oldmax + 1)) + self.in_message = True + else: + self.sl("QUIT") + if data[0] == "205": # bai + reactor.stop() + # sys.exit(0) + + +class fac(protocol.ClientFactory): + protocol = client + + +# this connects the protocol to a server running on port 8000 +reactor.connectTCP("10.8.0.1", 1199, fac()) +reactor.run() + +if len(messages) == 0: + x = "no messages" + open("/dev/shm/stream", "w").write(x) # just to make sure if we invoke stream2.py it will die before trying to connect + sys.exit(0) + +x = json.dumps(messages, indent=4) +open("/dev/shm/stream", "w").write(x) +subprocess.call(["python3", "stream2.py"]) + diff --git a/stream2.py b/stream2.py new file mode 100644 index 0000000..31603f4 --- /dev/null +++ b/stream2.py @@ -0,0 +1,41 @@ +from twisted.words.protocols import irc +import sys +import threading +import time +import json +from twisted.internet import reactor, protocol, ssl +import twisted.protocols.basic + +messages = json.load(open("/dev/shm/stream", "r")) +#messages = ["TEST!!"] +channel = "#nntpchan" + +class ircproto(irc.IRCClient): + nickname = "ctlbot" + password = "ctlbot/freenode:CHANGEME" + + def sleepAndDie(self): # We need the thread because for some reason calling quit right after we send the message results in the message not getting sent + print("Sleeping thread started") + time.sleep(4) + # self.quit() + # reactor.stop() + reactor.callFromThread(reactor.stop) + + def signedOn(self): + self.join(channel) + + def joined(self, channel_p): + if channel == channel_p: # forgetting this check got me in a lot of trouble on freenode + for m in messages: + print("PRIVMSG " + m) + self.msg(channel, "New message to ctl: " + m) + t = threading.Thread(target=self.sleepAndDie) + t.start() + + +class ircfac(protocol.ReconnectingClientFactory): + protocol = ircproto + +reactor.connectSSL("10.8.0.1", 9030, ircfac(), ssl.ClientContextFactory()) +reactor.run() +