diff --git a/.gitignore b/.gitignore index a86389a..728b360 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,7 @@ docs/_build/ # PyBuilder target/ +tildeclub.json tildeteam.json tildeverse.json config.json diff --git a/config.json.example b/config.json.example index a980886..4eeeff2 100644 --- a/config.json.example +++ b/config.json.example @@ -2,6 +2,7 @@ "channels": [], "address": "127.0.0.1", "port": 6667, + "tls": false, "botnick", "tooter" } diff --git a/requirements.txt b/requirements.txt index 74ea545..a3e9998 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ Mastodon.py==1.5.0 emoji==0.5.4 -irctokens==1.0.0 +ircrobots==0.3.7 diff --git a/tooter.py b/tooter.py index 117185f..33b57f8 100644 --- a/tooter.py +++ b/tooter.py @@ -1,16 +1,21 @@ #!/usr/bin/env python3 from mastodon import Mastodon +from irctokens import build, Line +from ircrobots import Bot as BaseBot +from ircrobots import Server as BaseServer +from ircrobots import ConnectionParams, SASLUserPass +import asyncio import emoji -import irctokens +import glob import json import os import re -import socket import sys HELPTEXT = "helo i can send toots from irc: @tildeverse@tilde.zone - from @tildeteam from the #team channel)" + def masto_from_json(conf): conf = json.load(conf) return Mastodon( @@ -21,39 +26,12 @@ def masto_from_json(conf): ) -def _send(line): - print(f"> {line.format()}") - e.push(line) - while e.pending(): - e.pop(s.send(e.pending())) - - -def send(chan, msg): - _send(irctokens.build("PRIVMSG", [chan, msg])) - - -def think(line): - chan = line.params.pop(0) - words = line.params[0].split(" ") - - if len(words) > 0 and line.hostmask.nickname != config["botnick"]: - cmd = words[0].lower() - if cmd == "!toot": - if len(words) >= 2: - status = emoji.emojize(" ".join(words[1:]), use_aliases=True) - if chan == "#team": - res = tildeteam.toot(status) - else: - res = tildeverse.toot(status) - print(res) - send(chan, "tooted! {}".format(res["url"])) - else: - send(chan, HELPTEXT) - elif cmd == "!source": - send(chan, "https://tildegit.org/ben/tooter") - elif cmd in ["!botlist", "!toothelp"]: - send(chan, HELPTEXT) - +masto = {} +# load masto creds +for cred in glob.glob("tilde*.json"): + shortname, _ = os.path.splitext(cred) + with open(cred, "r") as f: + masto[shortname] = masto_from_json(f) # do setup path = os.path.dirname(os.path.abspath(__file__)) @@ -70,53 +48,79 @@ if len(sys.argv) > 1: for c in sys.argv[1:]: channels.append("#" + c) -# read masto creds -with open(os.path.join(path, "tildeverse.json"), "r") as f: - tildeverse = masto_from_json(f) -with open(os.path.join(path, "tildeteam.json"), "r") as f: - tildeteam = masto_from_json(f) +def think(line): + chan = line.params[0] + words = line.params[1].split(" ") + + if len(words) > 0: + cmd = words[0].lower() + if cmd == "!toot": + if len(words) >= 2: + status = emoji.emojize(" ".join(words[1:]), use_aliases=True) + if chan == "#team": + res = masto["tildeteam"].toot(status) + elif chan == "#club": + res = masto["tildeclub"].toot(status) + else: + res = masto["tildeverse"].toot(status) + print(res) + return "tooted! {}".format(res["url"]) + else: + return HELPTEXT + elif cmd == "!source": + return "https://tildegit.org/ben/tooter" + elif cmd in ["!botlist", "!toothelp"]: + return HELPTEXT + + +class Server(BaseServer): + async def line_send(self, line: Line): + print(f"{self.name} > {line.format()}") + + async def line_read(self, line: Line): + print(f"{self.name} < {line.format()}") + if line.command == "001": + print(f"connected to {self.isupport.network}") + await self.send(build("JOIN", [",".join(channels)])) + await self.send(build("MODE", [self.nickname, "+B"])) + + if line.command == "INVITE": + print(f"received invite to {line.params[1:]}") + await self.send(build("JOIN", line.params[1:])) + + if ( + line.command == "PRIVMSG" + and self.has_channel(line.params[0]) + and not line.hostmask is None + and not self.casefold(line.hostmask.nickname) == self.nickname_lower + and not ("batch" in line.tags and line.tags["batch"] == "1") + and self.has_user(line.hostmask.nickname) + ): + response = think(line) + if response is not None: + await self.send(build("PRIVMSG", [line.params[0], response])) + + +class Bot(BaseBot): + def create_server(self, name: str): + return Server(self, name) + + +async def main(): + bot = Bot() + + params = ConnectionParams( + config["botnick"], + host=config["address"], + port=config["port"], + tls=config["tls"], + sasl=SASLUserPass(account["username"], account["password"]), + ) + + await bot.add_server("tilde", params) + await bot.run() + if __name__ == "__main__": - d = irctokens.StatefulDecoder() - e = irctokens.StatefulEncoder() - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect((config["address"], config["port"])) - - _send(irctokens.build("USER", [config["botnick"], "0", "*", "mastodon tooter"])) - _send(irctokens.build("NICK", [config["botnick"]])) - - while True: - lines = d.push(s.recv(1024)) - - if lines == None: - print("! disconnected") - break - - for line in lines: - print(f"< {line.format()}") - - if line.command == "PING": - _send(irctokens.build("PONG", line.params)) - - elif line.command == "001": - _send(irctokens.build("MODE", [config["botnick"], "+B"])) - if account is not None: - _send( - irctokens.build( - "SQUERY", - [ - "NickServ", - "IDENTIFY", - account["username"], - account["password"], - ], - ) - ) - _send(irctokens.build("JOIN", [",".join(channels)])) - - elif line.command == "INVITE": - _send(irctokens.build("JOIN", line.params)) - - elif line.command == "PRIVMSG": - think(line) + asyncio.run(main())