diff --git a/config.py b/config.py new file mode 100644 index 0000000..db6ceab --- /dev/null +++ b/config.py @@ -0,0 +1,14 @@ +# Server and channel settings +server = ("localhost", 6667) +channels = ["#rktest"] +bot_nick = "ramenkan" + +# Admin user and code for admin actions +admin_user = "mio" +admin_code = "ramen" + +# Request prefix, e.g. "!" +req_prefix = "!" + +# Print messages to stdout +debug = True diff --git a/irc.py b/irc.py index 3dc9d80..f48debc 100644 --- a/irc.py +++ b/irc.py @@ -1,105 +1,76 @@ -#!/usr/bin/python3 - import socket -from os import _exit -from random import randint + class IRC: """A set of methods for basic IRC communication.""" def __init__(self): - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.debug = False + self.sock = "" - def set_prefs(self, prefs): - """Set environment variables from a dictionary of values.""" - self.server = prefs["server"] - self.channels = prefs["channels"] - self.bot_nick = prefs["bot_nick"] - self.admin_user = prefs["admin_user"] - self.admin_code = prefs["admin_code"] - self.req_prefix = prefs["req_prefix"] - self.is_debug = prefs["is_debug"] - - def msg(self, command, text, *args, **kwargs): + def is_debug(self, boolean): + self.debug = boolean + + def send(self, command, text, *args, **kwargs): """Send messages given the IRC command and text. Optionally specify a message recipient with `recvr=user`.""" recvr = kwargs.get("recvr", "") if recvr != "": recvr += " :" - self.sock.sendall(bytes(command + " " + recvr + text + "\n", "utf-8")) + if self.debug: + print("[debug][send] " + command + " " + recvr + text) + bs = bytes(command + " " + recvr + text + "\r\n", "utf-8") + try: + if kwargs.get("sock", "") != "": + public_ss = kwargs.get("sock", "") + public_ss.sendall(bs) + else: + self.sock.sendall(bs) + except BrokenPipeError: + print("[debug][broken_pipe_err] " + command + " " + recvr + text) + pass - def pm(self, text, recipient): - """Alias of msg() with the PRIVMSG IRC command and a recipient.""" - self.msg("PRIVMSG", text, recvr=recipient) - - def connect(self): + def connect(self, server, bot_nick): """Connect to the server and sends user/nick information.""" - self.sock.connect(self.server) - self.msg("USER", self.bot_nick + " " + self.bot_nick + " " + \ - self.bot_nick + " ehlo") - self.msg("NICK", self.bot_nick) + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect(server) + self.send("USER", bot_nick + " " + bot_nick + " " + \ + bot_nick + " " + bot_nick) + self.send("NICK", bot_nick) + return self.sock - def parse_line(self, line): + def disconnect(self, resp_msg, quit_msg, admin_user): + """Notify the admin user and disconnect from the server.""" + self.send("PRIVMSG", resp_msg, recvr=admin_user) + self.send("QUIT", ":" + quit_msg) + + def receive(self, *args, **kwargs): + data = self.sock.recv(2040).decode("utf-8").strip("\n\r") + if self.debug: + print("[debug][recv] " + data) + return data + + def keep_alive(self, line): + """Stay connected to a server by responding to server pings.""" + if "PING" in line: + self.send("PONG", ":" + line.split(":", 2)[1].split(" ", 1)[0]) + + def join_channels(self, channels): + """Join channels from a list in the config.""" + for c in channels: + if c.strip() != "" or c.strip() != "#": + self.send("JOIN", c) + + def parse_msg(self, line, req_prefix): """Extract the request, the nick and username of the requester, the channel where the request originated and returns a dictionary of values.""" data = {"req": "", "req_chan": "", "nick": "", "user": ""} - if (":" + self.req_prefix) in self.line: + if (":" + req_prefix) in line: data["req"] = line.split("PRIVMSG", 1)[1].split(":" + \ - self.req_prefix, 1)[1].strip() - data["req_chan"] = line.split("PRIVMSG ", 1)[1].split(" :", 1)[0] + req_prefix, 1)[1].strip() + data["req_chan"] = line.split("PRIVMSG ", \ + 1)[1].split(" :", 1)[0] data["nick"] = line.split("!~", 1)[0][1:] data["user"] = line.split("!~", 2)[1][0:].split("@", 1)[0] return data - - def keep_alive(self): - """Stay connected to a server by responding to server pings.""" - self.line = self.sock.recv(2040).decode("utf-8").strip("\n\r") - if "PING" in self.line: - self.msg("PONG", ":" + self.server[0]) - if self.is_debug: - print(self.line) - - def join_chans(self): - """Join channels from a list provided in environment settings.""" - for c in self.channels: - if c.strip() != "" or c.strip() != "#": - self.msg("JOIN", c) - - def map_actions(self, channel): - """Listen for bot requests in a channel and match responses.""" - data = self.parse_line(self.line) - - # Respond only in the channel the request was made - if channel == data["req_chan"]: - # General commands - if data["req"] == "rollcall" or data["req"] == "help": - rollcall = ( - "一、二、三、らーめん缶! " - "Hello, I am a ramen vending machine. " - "Please type a code to request service: " - "!help " - "Support: +81 012-700-1MIO " - "どうぞめしあがれ。" - ) - self.pm(rollcall, data["req_chan"]) - - elif data["req"] == ("water " + self.bot_nick): - resp = [ - ("\x01ACTION happily pours the hot liquid into a bowl of " - "noodles and offers it to ") + data["nick"] + "\x01", - "( ^_^)o自自o(^_^ )Cheers!", - "Water Level [/////////] 200% - Thanks! (^▽^)" - ] - self.pm(resp[randint(0, len(resp)-1)], data["req_chan"]) - - elif data["req"] == ("botsnack " + self.bot_nick): - self.pm("Ramen time anytime! ヽ(´▽`)/", data["req_chan"]) - - # Respond to some commands only in private message - if data["req_chan"] == self.bot_nick: - if data["req"] == ("exit " + self.admin_code) and \ - data["user"].lower() == self.admin_user.lower(): - self.pm("Okay, okay, I'll leave. (´・ω・`)", self.admin_user) - self.msg("QUIT", ":noodling off") - _exit(0) diff --git a/ramen.py b/ramen.py new file mode 100644 index 0000000..f0946b4 --- /dev/null +++ b/ramen.py @@ -0,0 +1,71 @@ +from os import _exit +from random import randint + +import config as cfg +from irc import IRC + + +class Ramen: + + def __init__(self): + self.irc = IRC() + self.irc.is_debug = cfg.debug + self.bot_nick = cfg.bot_nick + self.admin_user = cfg.admin_user + self.admin_code = cfg.admin_code + self.req_prefix = cfg.req_prefix + + def attach(self, socket): + self.sock = socket + + def handle(self, channel, data): + """Listen for requests in a channel and match responses.""" + self.msg = self.irc.parse_msg(data, self.req_prefix) + + if self.msg["req_chan"] == self.bot_nick: + # Respond to some commands only from admin user + if self.msg["user"].lower() == self.admin_user.lower() and \ + self.admin_code in self.msg["req"]: + self.handle_admin_req(self.msg["req"], self.admin_user) + + # Respond only in the channel the request was made + if channel == self.msg["req_chan"]: + # General commands + if self.msg["req"] == "rollcall" or self.msg["req"] == "help": + self.handle_rollcall() + elif self.msg["req"] == ("water " + self.bot_nick): + self.handle_water() + elif self.msg["req"] == ("botsnack " + self.bot_nick): + self.handle_botsnack() + + def handle_admin_req(self, req, admin_user): + if "exit" in req: + self.irc.send("PRIVMSG", "Okay, okay, I'll leave. (´・ω・`)", \ + recvr=admin_user, sock=self.sock) + self.irc.send("QUIT", ":noodling off", sock=self.sock) + _exit(0) + + def handle_rollcall(self): + resp = ( + "一、二、三、らーめん缶! " + "Hello, I am a ramen vending machine. " + "Please type a code for service: " + "!help " + "Support: +81 012-700-1MIO どうぞめしあがれ。" + ) + self.irc.send("PRIVMSG", resp, recvr=self.msg["req_chan"], \ + sock=self.sock) + + def handle_water(self): + resp = [ + ("\x01ACTION happily pours the hot liquid into a bowl of noodles " + "and offers it to ") + self.msg["nick"] + "\x01", + "( ^_^)o自自o(^_^ )Cheers!", + "Water Level [/////////] 200% - Thanks! (^▽^)" + ] + self.irc.send("PRIVMSG", resp[randint(0, len(resp)-1)], \ + recvr=self.msg["req_chan"], sock=self.sock) + + def handle_botsnack(self): + self.irc.send("PRIVMSG", "Ramen time anytime! ヽ(´▽`)/", \ + recvr=self.msg["req_chan"], sock=self.sock) diff --git a/run.py b/run.py index 7bb5d5f..73b2de0 100644 --- a/run.py +++ b/run.py @@ -1,23 +1,22 @@ -#!/usr/bin/python3 +from time import sleep -from irc import * +import config as cfg +from irc import IRC +from ramen import Ramen -config = { - "server": ("localhost", 6667), - "channels": ["#rktest"], - "bot_nick": "ramenkan", - "admin_user": "mio", - "admin_code": "ramen", - "req_prefix": "!", - "is_debug": True - } irc = IRC() -irc.set_prefs(config) -irc.connect() -irc.join_chans() +irc.is_debug(cfg.debug) +socket = irc.connect(cfg.server, cfg.bot_nick) + +irc.join_channels(cfg.channels) +ramen = Ramen() +# Have run.py and ramen.py share the same socket connection +ramen.attach(socket) while 1: - irc.keep_alive() - for c in config["channels"]: - irc.map_actions(c) + sleep(2) + data = irc.receive(debug=cfg.debug) + irc.keep_alive(data) + for chan in cfg.channels: + ramen.handle(chan, data)