summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatt Arnold <matt@thegnuguru.org>2022-02-20 18:33:55 -0500
committerMatt Arnold <matt@thegnuguru.org>2022-02-20 18:33:55 -0500
commit13381bc12504eb0c2afcf193de6754666213739c (patch)
treebce8e9941a9a357f2a83a3989f5e859be85e4f78
parent6938bd4862d8a934044b27277eab1773c0805819 (diff)
Major rewrite to use a proper irc tokenizer json for configuration, and other such QOL improvements
-rw-r--r--.gitignore3
-rw-r--r--IRC.py77
-rw-r--r--client.py31
-rw-r--r--config.json.example7
4 files changed, 84 insertions, 34 deletions
diff --git a/.gitignore b/.gitignore
index de2d5e0..6d979ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -150,3 +150,6 @@ cython_debug/
 #  and can be added to the global gitignore or merged into this file.  For a more nuclear
 #  option (not recommended) you can uncomment the following to ignore the entire idea folder.
 #.idea/
+
+# ignore config file
+config.json
diff --git a/IRC.py b/IRC.py
index 018756b..a67e65d 100644
--- a/IRC.py
+++ b/IRC.py
@@ -2,9 +2,16 @@
 # Copyright (C) 2022 Matt Arnold
 import socket
 import sys
+import irctokens
 import time
 class IRCBadMessage(Exception):
     pass
+class IRCError(Exception):
+    pass
+
+def printred(s):
+    t = f"\033[1;31m {s} \033[0m\n"
+    print(t)
 
 def parsemsg(s):
     """Breaks a message from an IRC server into its prefix, command, and arguments.
@@ -24,48 +31,76 @@ def parsemsg(s):
     command = args.pop(0)
     return prefix, command, args
 
+
 LINEEND = '\r\n'
 
 class IRCBot:
 
     irc = None
 
-    def __init__(self, sock):
+    def __init__(self, sock, config=None):
         self.irc = sock
+        self.connected = False
+        self.config = config
+
+    def send_cmd(self, line):
+        """Send an IRC Command, takes an IRC command string without the CRLF
+        Returns encoded msg on success raises IRCError on failure """
+        if not self.connected:
+            raise IRCError("Not Connected")
+        encmsg = bytes(line.format() + LINEEND, 'UTF-8' )
+        expected = len(encmsg)
+        if self.irc.send(encmsg) == expected:
+            return str(encmsg)
+        else:
+            raise IRCError("Unexpected Send Length")
 
+    def on_welcome(self, *args, **kwargs):
+        authmsg = irctokens.build("NICKSERV", ['IDENTIFY', self.config['nickpass']])
+        self.send_cmd(authmsg)
+        joinmsg = irctokens.build("JOIN", [self.config['channel']])
+        self.send_cmd(joinmsg)
+
+        
     def send_privmsg(self, dst, msg):
-        self.irc.send(bytes("PRIVMSG " + dst + " :"  + msg + LINEEND, "UTF-8"))
+        msg = irctokens.build("PRIVMSG",[dst, msg])
+        self.send_cmd(msg)
     
     def send_quit(self, quitmsg):
-        msg = f'QUIT :{quitmsg}' + LINEEND
+        msg = irctokens.build("QUIT", [quitmsg])
         print(msg)
-        self.irc.send(msg.encode('utf-8'))
+        self.send_cmd(msg)
 
-    def send_action(self, action_msg):
+    def send_action(self, action_msg, dst):
         pass
 
-    def connect(self, server, port, channel, botnick, botpass, botnickpass):
+    def connect(self, server, port, channel, botnick, botnickpass):
+        if self.config is None:
+            self.config = {}
+            self.config["hostname"] = server
+            self.config["port"] = port
+            self.config["nick"] = botnick
+            self.config["channel"] = channel
+            self.config["nickpass"] = botnickpass
         print("Connecting to: " + server)
-        self.irc.connect((server, port))
-
-        # Perform user authentication
-        self.irc.send(bytes("USER " + botnick + " " + botnick +
-                      " " + botnick + " :python" + LINEEND, "UTF-8"))
-        self.irc.send(bytes("NICK " + botnick + LINEEND, "UTF-8"))
-        self.irc.send(bytes("NICKSERV IDENTIFY " +
-                      botnickpass + " " +  LINEEND, "UTF-8"))
-        time.sleep(5)
-
-        # join the channel
-        self.irc.send(bytes("JOIN " + channel + LINEEND, "UTF-8"))
+        self.irc.connect((self.config["hostname"], self.config["port"]))
+        self.connected = True
 
+        # Perform user registration
+        usermsg = irctokens.build("USER", [botnick, "0","*", "RabbitEars Bot"]).format()
+        print(usermsg)
+        self.send_cmd(usermsg)
+        nickmsg = irctokens.build("NICK", [botnick])
+        self.send_cmd(nickmsg)
     def get_response(self):
-        time.sleep(1)
         # Get the response
         resp = self.irc.recv(4096).decode("UTF-8")
         msg = parsemsg(resp)
-
-        if msg[1] == 'PING':
+        nwmsg = irctokens.tokenise(resp)
+        
+        if nwmsg.command == "001":
+            self.on_welcome(nwmsg.params)
+        if nwmsg.command == 'PING':
             print('Sending pong')
             self.irc.send(
                 bytes('PONG ' + LINEEND, "UTF-8"))
diff --git a/client.py b/client.py
index 112707c..bbd0649 100644
--- a/client.py
+++ b/client.py
@@ -1,19 +1,22 @@
-# 
+# Part of rabbitears See LICENSE for permissions
+# Copyright (C) 2022 Matt Arnold
 from IRC import *
 import os
 import random
 import ssl
 import socket
 import sys
+import irctokens
+import json
 
 LINEEND = '\r\n'
 # IRC Config
-hostname = "irc.spartalinux.xyz"  # Provide a valid server IP/Hostname
-port = 6697
-channel = "#botdev"
-botnick = "botley"
-botnickpass = "a.password"
-botpass = "unused"
+config = None
+with open('config.json') as f:
+    jld = f.read()
+    config = json.loads(jld)
+
+botpass = "unused.factor.this.out"
 
 # Need to pass the IRCBot class a socket the reason it doesn't do this itself is 
 # so you can set up TLS or not as you need it
@@ -22,14 +25,16 @@ oursock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 context = ssl.SSLContext()
 context.check_hostname = False
 context.verify_mode = ssl.CERT_NONE
-oursock = context.wrap_socket(oursock, server_hostname=hostname)
+oursock = context.wrap_socket(oursock, server_hostname=config['hostname'])
 irc = IRCBot(oursock)
-irc.connect(hostname, port, channel, botnick, botpass, botnickpass)
+irc.connect(config['hostname'],
+    config['port'],
+    config['channel'],
+    config['nick'],
+    config['nickpass'])
 
 def generate_response(person, message):
-    print(person, message)
     msg = message.strip(LINEEND)
-    irc.send_privmsg(channel, str(type(person)) + ' ' + str(type(message)))
     if 'cool.person' in person and msg.lower() == "hello botley":
         return "Greetings Master"
     elif msg.lower() == "hello":
@@ -42,10 +47,10 @@ while True:
 
         text = irc.get_response()
         print(text[0],text[1],text[2])
-        if text[1] == 'PRIVMSG' and text[2][0] == channel:
+        if text[1] == 'PRIVMSG' and text[2][0] == config['channel']:
             r = generate_response(text[0],text[2][1])
             if r is not None:
-                irc.send_privmsg(channel,r)
+                irc.send_privmsg(config['channel'],r)
     except KeyboardInterrupt:
         irc.send_quit("Ctrl-C Pressed")
         msg = oursock.recv(4096)
diff --git a/config.json.example b/config.json.example
new file mode 100644
index 0000000..42d81a3
--- /dev/null
+++ b/config.json.example
@@ -0,0 +1,7 @@
+{
+    "hostname": "irc.spartalinux.xyz",
+    "port": 6697,
+    "nick": "bad_bot",
+    "channel": "#botdev",
+    "nickpass": "a.password"
+}