Convoluted but works, needs better abstraction

trunk
mio 2018-09-13 06:10:50 +00:00
parent cef65b6a94
commit 02aff49ea0
4 changed files with 154 additions and 99 deletions

14
config.py 100644
View File

@ -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. "!<request>"
req_prefix = "!"
# Print messages to stdout
debug = True

135
irc.py
View File

@ -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)

71
ramen.py 100644
View File

@ -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)

33
run.py
View File

@ -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)