]> jfr.im git - erebus.git/commitdiff
admin_config - add !getconfig, remove some unused functions main
authorJohn Runyon <redacted>
Thu, 9 May 2024 22:11:01 +0000 (16:11 -0600)
committerJohn Runyon <redacted>
Thu, 9 May 2024 22:11:01 +0000 (16:11 -0600)
25 files changed:
.gitignore
README.md
bot.py
erebus.py
modlib.py
modules/admin_channel.py
modules/admin_config.py
modules/admin_user.py
modules/channel_admin.py
modules/eval.py
modules/example_flags.py [moved from modules/test_flags.py with 92% similarity]
modules/exception_hook.py
modules/listenbrainz.py
modules/misc.py
modules/msg.py
modules/nitterize.py
modules/resources.py
modules/server.py
modules/sockets.py
modules/steam.py
modules/subtext.py
modules/urls.py
modules/weatherstack_weather.py
mysql.sql [moved from dump.sql with 78% similarity]
sqlite.sql [new file with mode: 0644]

index 620d76ea3550a88f620f4bee27430224fd2a1f2a..82461e4d10ec90673bb8bf910ceff5e5908a3c88 100644 (file)
@@ -9,3 +9,4 @@ nohup.out
 pidfile
 logfile
 oldlogs/
+erebus.db
index 2ec3dc8daca27732bd70bc7d2e5add576965a650..2e1a2d1e00b0dbd40d3ecd6e5b0e75cfba35b7d0 100644 (file)
--- a/README.md
+++ b/README.md
@@ -5,9 +5,17 @@ Getting started
 ---------------
 - `cp bot.config.example bot.config`
 - `vim bot.config`
-- Create a MySQL database, i.e. `CREATE DATABASE foo; GRANT ALL ON foo.* TO ...`
-- `mysql <dump.sql`
-- Add your auth (main NickServ nick/account name on other networks) in the database as owner: `INSERT INTO users VALUES ('YourAuth', 100);`
+- Pick a database:
+  - MySQL:
+    - Create a MySQL database, i.e. `CREATE DATABASE foo; GRANT ALL ON foo.* TO ...`
+    - `mysql <mysql.sql`
+    - dbtype=mysql, dbhost/dbuser/dbpass/dbname with connection info
+  - SQLite
+    - `sqlite3 erebus.db <sqlite.sql`
+    - dbtype=sqlite, dbhost=erebus.db (or other database path)
+- Populate at least the `bots` and `users` table:
+  - Add a bot: `INSERT INTO bots VALUES ('nick', 'user', NULL, NULL, NULL, 1, 0)`
+  - Add your auth (main NickServ nick/account name on other networks) in the database as owner: `INSERT INTO users VALUES ('YourAuth', 100);`
 - `./run`
 
 Install croncheck.sh in your crontab, if desired.  
diff --git a/bot.py b/bot.py
index fbb399991ddba703dc4ff821926de7bc2659d101..fd9292295aa3d2051d729d8e8e6a6e1b5b73b8a2 100644 (file)
--- a/bot.py
+++ b/bot.py
@@ -247,6 +247,10 @@ class Bot(object):
                        for u in chan.users:
                                if u.nick != self.nick:
                                        self._clientLeft(u.nick, chan)
+                       if chan.deleting:
+                               chan.bot.chans.remove(chan)
+                               del self.parent.chans[chan.name.lower()]
+                               del chan
                else:
                        user = self.parent.user(nick)
                        gone = user.part(chan)
index cd85e3e3b238ba345e3d8ff8b3300439ec0b5d5e..fb6fcc80fc703480887c2be351c368fb1a7e5669 100644 (file)
--- a/erebus.py
+++ b/erebus.py
@@ -6,8 +6,8 @@
 
 from __future__ import print_function
 
-import os, sys, select, MySQLdb, MySQLdb.cursors, time, traceback, random, gc
-import bot, config, ctlmod
+import os, sys, select, time, traceback, random, gc
+import bot, config, ctlmod, modlib
 
 class Erebus(object): #singleton to pass around
        APIVERSION = 0
@@ -124,6 +124,8 @@ class Erebus(object): #singleton to pass around
                        self.voices = []
                        self.ops = []
 
+                       self.deleting = False # if true, the bot will remove cached records of this channel when the bot sees that it has left the channel
+
                        c = main.query("SELECT user, level FROM chusers WHERE chan = %s", (self.name,))
                        if c:
                                row = c.fetchone()
@@ -194,33 +196,41 @@ class Erebus(object): #singleton to pass around
                        self.potype = "select"
                        self.fdlist = []
 
-       def query(self, *args, **kwargs):
-               if 'noretry' in kwargs:
-                       noretry = kwargs['noretry']
-                       del kwargs['noretry']
-               else:
-                       noretry = False
+       def query(self, sql, parameters=[], noretry=False):
+               # Callers use %s-style (paramstyle='format') placeholders in queries.
+               # There's no provision for a literal '%s' present inside the query; stuff it in a parameter instead.
+               if db_api.paramstyle == 'format' or db_api.paramstyle == 'pyformat': # mysql, postgresql
+                       # psycopg actually asks for a mapping with %(name)s style (pyformat) but it will accept %s style.
+                       pass
+               elif db_api.paramstyle == 'qmark': # sqlite doesn't like %s style.
+                       parameters = [str(p) for p in parameters]
+                       sql = sql.replace('%s', '?') # hope that wasn't literal, oopsie
+
+               log_noretry = ''
+               if noretry:
+                       log_noretry = ', noretry=True'
+               self.log("[SQL]", "?", "query(%r, %r%s)" % (sql, parameters, log_noretry))
 
-               self.log("[SQL]", "?", "query(%s, %s)" % (', '.join([repr(i) for i in args]), ', '.join([str(key)+"="+repr(kwargs[key]) for key in kwargs])))
                try:
                        curs = self.db.cursor()
-                       res = curs.execute(*args, **kwargs)
+                       res = curs.execute(sql, parameters)
                        if res:
                                return curs
                        else:
                                return res
-               except MySQLdb.DataError as e:
-                       self.log("[SQL]", ".", "MySQL DataError: %r" % (e))
+               except db_api.DataError as e:
+                       self.log("[SQL]", ".", "DB DataError: %r" % (e))
                        return False
-               except MySQLdb.MySQLError as e:
-                       self.log("[SQL]", "!", "MySQL error! %r" % (e))
+               except db_api.Error as e:
+                       self.log("[SQL]", "!", "DB error! %r" % (e))
                        if not noretry:
                                dbsetup()
-                               return self.query(*args, noretry=True, **kwargs)
+                               return self.query(sql, parameters, noretry=True)
                        else:
                                raise e
 
        def querycb(self, cb, *args, **kwargs):
+               # TODO this should either get thrown out with getdb()/returndb(), or else be adjusted to make use of it.
                def run_query():
                        cb(self.query(*args, **kwargs))
                threading.Thread(target=run_query).start()
@@ -231,6 +241,8 @@ class Erebus(object): #singleton to pass around
                self.bots[nick.lower()] = obj
 
        def newfd(self, obj, fileno):
+               if not isinstance(obj, modlib.Socketlike):
+                       raise Exception('Attempted to hook a socket without a class to process data')
                self.fds[fileno] = obj
                if self.potype == "poll":
                        self.po.register(fileno, select.POLLIN)
@@ -298,7 +310,9 @@ class Erebus(object): #singleton to pass around
                return [u for u in self.users.values() if u.auth == auth.lower()]
 
        def getdb(self):
-               """Get a DB object. The object must be returned to the pool after us, using returndb()."""
+               """Get a DB object. The object must be returned to the pool after us, using returndb(). This is intended for use from child threads.
+                       It should probably be treated as deprecated though. Where possible new modules should avoid using threads.
+                       In the future, timers will be provided (manipulating the timeout_seconds of the poll() method), and that should mostly be used in place of threading."""
                return self.dbs.pop()
 
        def returndb(self, db):
@@ -357,9 +371,29 @@ class Erebus(object): #singleton to pass around
 def dbsetup():
        main.db = None
        main.dbs = []
+       dbtype = cfg.get('erebus', 'dbtype', 'mysql')
+       if dbtype == 'mysql':
+               _dbsetup_mysql()
+       elif dbtype == 'sqlite':
+               _dbsetup_sqlite()
+       else:
+               main.log('*', '!', 'Unknown dbtype in config: %s' % (dbtype))
+
+def _dbsetup_mysql():
+       global db_api
+       import MySQLdb as db_api, MySQLdb.cursors
        for i in range(cfg.get('erebus', 'num_db_connections', 2)-1):
-               main.dbs.append(MySQLdb.connect(host=cfg.dbhost, user=cfg.dbuser, passwd=cfg.dbpass, db=cfg.dbname, cursorclass=MySQLdb.cursors.DictCursor))
-       main.db = MySQLdb.connect(host=cfg.dbhost, user=cfg.dbuser, passwd=cfg.dbpass, db=cfg.dbname, cursorclass=MySQLdb.cursors.DictCursor)
+               main.dbs.append(db_api.connect(host=cfg.dbhost, user=cfg.dbuser, passwd=cfg.dbpass, db=cfg.dbname, cursorclass=MySQLdb.cursors.DictCursor))
+       main.db = db_api.connect(host=cfg.dbhost, user=cfg.dbuser, passwd=cfg.dbpass, db=cfg.dbname, cursorclass=MySQLdb.cursors.DictCursor)
+
+def _dbsetup_sqlite():
+       global db_api
+       import sqlite3 as db_api
+       for i in range(cfg.get('erebus', 'num_db_connections', 2)):
+               main.db = db_api.connect(cfg.dbhost)
+               main.db.row_factory = db_api.Row
+               main.db.isolation_level = None
+               main.dbs.append(main.db)
 
 def setup():
        global cfg, main
@@ -394,7 +428,7 @@ def loop():
                try:
                        data = main.fd(fileno).getdata()
                except:
-                       main.log('*', '!', 'Super-mega-emergency: getdata raised exception for socket %d' % (fileno))
+                       main.log('*', '!', 'Error receiving data: getdata raised exception for socket %d, closing' % (fileno))
                        traceback.print_exc()
                        data = None
                if data is None:
@@ -406,7 +440,7 @@ def loop():
                                try:
                                        main.fd(fileno).parse(line)
                                except:
-                                       main.log('*', '!', 'Super-mega-emergency: parse raised exception for socket %d data %r' % (fileno, line))
+                                       main.log('*', '!', 'Error receiving data: parse raised exception for socket %d data %r, ignoring' % (fileno, line))
                                        traceback.print_exc()
        if main.mustquit is not None:
                main.log('*', '!', 'Core exiting due to: %s' % (main.mustquit))
index b36c153287f4979c107a8768c60b91536949ec15..11ca1fb7a4acc454cab79b2254583fe8d1d885d8 100644 (file)
--- a/modlib.py
+++ b/modlib.py
@@ -3,6 +3,7 @@
 # module helper functions, see modules/modtest.py for usage
 # This file is released into the public domain; see http://unlicense.org/
 
+import abc
 import sys
 import socket
 from functools import wraps
@@ -60,6 +61,7 @@ class modlib(object):
        WRONGARGS = "Wrong number of arguments."
 
        def __init__(self, name):
+               self.Socketlike = Socketlike
                self.hooks = {} # {command:handler}
                self.chanhooks = {} # {channel:handler}
                self.exceptionhooks = [] # [(exception,handler)]
@@ -206,9 +208,7 @@ class modlib(object):
                return self._hooksocket(socket.AF_UNIX, socket.SOCK_STREAM, path, data)
        def _hooksocket(self, af, ty, address, data):
                def realhook(cls):
-                       if not (hasattr(cls, 'getdata') and callable(cls.getdata)):
-                               # Check early that the object implements getdata.
-                               # If getdata ever returns a non-empty list, then a parse method must also exist, but we don't check that.
+                       if not issubclass(cls, Socketlike):
                                raise Exception('Attempted to hook a socket without a class to process data')
                        self.sockhooks.append((af, ty, address, cls, data))
                        if self.parent is not None:
@@ -322,7 +322,52 @@ class modlib(object):
                        return func
                return realhook
 
-class _ListenSocket(object):
+class Socketlike(abc.ABC):
+       def __init__(self, sock, data):
+               """This default method saves the socket in self.sock and creates self.buffer for getdata(). The data is discarded."""
+               self.sock = sock
+               self.buffer = b''
+
+       def getdata(self):
+               """This default method gets LF or CRLF separated lines from the socket and returns an array of completely-seen lines to the core.
+               This should work well for most line-based protocols (like IRC)."""
+               recvd = self.sock.recv(8192)
+               if recvd == b"": # EOF
+                       if len(self.buffer) != 0:
+                               # Process what's left in the buffer. We'll get called again after.
+                               remaining_buf = self.buffer.decode('utf-8', 'backslashreplace')
+                               self.buffer = b""
+                               return [remaining_buf]
+                       else:
+                               # Nothing left in the buffer. Return None to signal the core to close this socket.
+                               return None
+               self.buffer += recvd
+               lines = []
+
+               while b"\n" in self.buffer:
+                       pieces = self.buffer.split(b"\n", 1)
+                       s = pieces[0].decode('utf-8', 'backslashreplace').rstrip("\r")
+                       lines.append(pieces[0].decode('utf-8', 'backslashreplace'))
+                       self.buffer = pieces[1]
+
+               return lines
+
+       def __str__(self):
+               return '%s#%d' % (self.__class__.__name__, self.sock.fileno())
+       def __repr__(self):
+               return '<%s.%s #%d %s:%d>' % ((self.__class__.__module__, self.__class__.__name__, self.sock.fileno())+self.sock.getpeername())
+
+       @abc.abstractmethod
+       def parse(self, chunk): pass
+
+       @classmethod
+       def __subclasshook__(cls, C):
+               if cls is Socketlike:
+                       if any('parse' in B.__dict__ and 'getdata' in B.__dict__ for B in C.__mro__):
+                               return True
+               return NotImplemented
+
+class _ListenSocket(Socketlike):
        def __init__(self, lib, sock, cls, data):
                self.clients = []
                self.lib = lib
@@ -343,6 +388,10 @@ class _ListenSocket(object):
                        self.clients.remove((client,obj))
                return close
 
+       def parse(self):
+               # getdata will never return a non-empty array, so parse will never be called; but Socketlike requires this method
+               pass
+
        def getdata(self):
                client, addr = self.sock.accept()
                obj = self.cls(client, self.data)
index d34e0ac514db224841f5a59ce005b9f69e803ace..88a6c5af6efc8e59da8303f35f2606d015f14772 100644 (file)
@@ -1,6 +1,6 @@
 # Erebus IRC bot - Author: Erebus Team
 # vim: fileencoding=utf-8
-# simple module example
+# Channel list management (add, remove channels)
 # This file is released into the public domain; see http://unlicense.org/
 
 # module info
@@ -62,8 +62,7 @@ def join(bot, user, chan, realtarget, *args):
 
 
 def _part(user, chan):
-       chan.bot.chans.remove(chan)
-       del lib.parent.chans[chan.name.lower()]
+       chan.deleting = True
        lib.parent.query("DELETE FROM chusers WHERE chan = %s", (chan,))
        lib.parent.query("DELETE FROM chans WHERE chname = %s", (chan,))
        chan.bot.part(chan)
index 7caf6d06c6250150154af120f5fdc97b4f619926..4f7ef9015533572b7f8726b38244bb2a7e0dab33 100644 (file)
@@ -1,6 +1,6 @@
 # Erebus IRC bot - Author: Erebus Team
 # vim: fileencoding=utf-8
-# simple module example
+# Commands to change config file settings
 # This file is released into the public domain; see http://unlicense.org/
 
 # module info
@@ -25,23 +25,16 @@ modstop = lib.modstop
 
 # module code
 
-def _resolve_user(s):
-       if s.startswith("#"):
-               return lib.parent.User(s, s[1:])
+@lib.hook(needchan=False, glevel=lib.OWNER)
+@lib.help('<section> <key>', 'gets a current config file setting')
+@lib.argsEQ(2)
+def getconfig(bot, user, chan, realtarget, *args):
+       section, key = args[0], args[1]
+       value = bot.parent.cfg.get(section, key)
+       if value is not None:
+               user.msg('[%s] %s: %s' % (section, key, value))
        else:
-               return lib.parent.user(s, create=False)
-
-def _resolve_level(s):
-       try:
-               i = int(s)
-               return i
-       except ValueError:
-               su = s.upper()
-               if su == "ANYONE":
-                       su = "AUTHED" # resolve to 0 instead of -1
-               if su in lib.glevs:
-                       return lib.glevs[su]
-       return None
+               user.msg('That option is not set ([%s] %s)' % (section, key))
 
 @lib.hook(needchan=False, glevel=lib.OWNER)
 @lib.help('<section> <key> <value>', 'sets a config file setting to a new value')
index 3e4a02c3c0b2601d304f5239c40cbd208415f0f2..6d215f219a117120119ef1019f6b74b4938a4e2e 100644 (file)
@@ -1,6 +1,6 @@
 # Erebus IRC bot - Author: Erebus Team
 # vim: fileencoding=utf-8
-# simple module example
+# Global-user management (glevel)
 # This file is released into the public domain; see http://unlicense.org/
 
 # module info
index b8bebc6e9922bee5136ee2809c78c6c4588cd9aa..1e18d0e417b1c70a60ae211dd81114d5fd7b0f96 100644 (file)
@@ -1,6 +1,6 @@
 # Erebus IRC bot - Author: Erebus Team
 # vim: fileencoding=utf-8
-# simple module example
+# Channel-access management
 # This file is released into the public domain; see http://unlicense.org/
 
 # module info
index c32797aab768b624ce10a54435e6843aca1cc41f..e5e4399c1cbf65034569432768f59dd976497899 100644 (file)
@@ -1,6 +1,6 @@
 # Erebus IRC bot - Author: Erebus Team
 # vim: fileencoding=utf-8
-# !EVAL and !EXEC commands
+# !EVAL and !EXEC commands, dangerous! DO NOT USE without understanding the risks!
 # This file is released into the public domain; see http://unlicense.org/
 
 # module info
similarity index 92%
rename from modules/test_flags.py
rename to modules/example_flags.py
index d3268c70d4fbc34cd8b24adca5d43ee86ba22f86..b22ae576fdaf25579b0db0495507af7eab61aede 100644 (file)
@@ -1,6 +1,6 @@
 # Erebus IRC bot - Author: Erebus Team
 # vim: fileencoding=utf-8
-# Various highly recommended "control" commands.
+# An example of the "flags" capability
 # This file is released into the public domain; see http://unlicense.org/
 
 # module info
index 28c4c545ca97f9e2118239243d09a877d56af713..78b51bab37d4f79edcc85f40c76b77b012a8ca2d 100644 (file)
@@ -1,5 +1,6 @@
 # Erebus IRC bot - Author: Erebus Team
 # vim: fileencoding=utf-8
+# A basic exception hook. Sends exceptions to `[exception_hook] destination` in the config.
 # This file is released into the public domain; see http://unlicense.org/
 
 # module info
index 297f4491de73a8acbfe853698fc2d31a94b55d7c..81445d1ba85ab36e7d8a984a42ab0ab28b59b291 100644 (file)
@@ -1,5 +1,6 @@
 # Erebus IRC bot - Author: Erebus Team
 # vim: fileencoding=utf-8
+# ListenBrainz now-playing
 
 # module info
 modinfo = {
index d9aa7abfde0a988e329d7931008db484bdf535d0..4590d8d82823e0d93557b56d99ffe4fafec75aea 100644 (file)
@@ -1,6 +1,6 @@
 # Erebus IRC bot - Author: Erebus Team
 # vim: fileencoding=utf-8
-# simple module example
+# assorted simple functions
 # This file is released into the public domain; see http://unlicense.org/
 
 # module info
index 68ca951d93e3b00dd0a297144280922a2312c581..5261f4cb555b037832f87fb94cd3945531b85b84 100644 (file)
@@ -1,5 +1,6 @@
 # Erebus IRC bot - Author: Erebus Team
 # vim: fileencoding=utf-8
+# Commands to message various destinations
 # This file is released into the public domain; see http://unlicense.org/
 
 # module info
index 07695a3a220e217efbc457939f0861ae43c4014a..8a8b09596d41f3250b9302cbb4f89ebd5a737a29 100644 (file)
@@ -1,8 +1,6 @@
 # Erebus IRC bot - Author: Erebus Team
 # vim: fileencoding=utf-8
-# module for 's/regex/replacement/' style correction
-# warning: arbitrary regex's are generally capable of various DoS attacks on CPU/memory usage. use with caution.
-# see for usage examples: https://github.com/zonidjan/erebus/commit/d7e9802778477f1faa26a03078cb1b3c018a5e5c
+# nitterize twitter links
 # This file is released into the public domain; see http://unlicense.org/
 
 # module info
index 35136a810f031afdd67e55c811254f26a245e788..5fc4309a9c135b5c90d0b97daa9c00a8129ec390 100644 (file)
@@ -1,6 +1,6 @@
 # Erebus IRC bot - Author: Erebus Team
 # vim: fileencoding=utf-8
-# resource-usage module
+# resource-usage reporting module
 # This file is released into the public domain; see http://unlicense.org/
 
 # module info
index 691436a4a22e1ee6b09973ad9204d831b7c6cbc2..ade5935b6077336e14dc83b0fb14482ca445f6aa 100644 (file)
@@ -1,5 +1,6 @@
 # Erebus IRC bot - Author: Erebus Team
 # vim: fileencoding=utf-8
+# Utility commands to show info like uptime and current server
 # This file is released into the public domain; see http://unlicense.org/
 
 import time
index cf79e70d36a912fc8e100e6936062cef8ec9e1a0..af650ed5f3a5a68e9e7dc7dc4ac748a5e77d7cda 100644 (file)
@@ -1,9 +1,34 @@
 # Erebus IRC bot - Author: Erebus Team
 # vim: fileencoding=utf-8
+# Configurable sockets module. DO NOT USE without understanding the risks
 # This file is released into the public domain; see http://unlicense.org/
 
 # Note: this module doesn't do any kind of authentication, so anyone who can connect to the bound port can spam you.
 
+"""
+To use - add in bot.config something like:
+
+[sockets]
+127.0.0.1:1337 = #example
+
+The left side is the address to listen on and the right side is the channel to send to.
+The exmaple will send incoming lines/packets on localhost, port 1337 to channel #example
+
+The full syntax for the address is:
+[unix:]</path/to/socket>
+[udp|tcp:][<ip>:]<port>
+
+
+Address examples:
+
+Unix domain socket:          /path
+Unix domain socket:          unix:/path
+TCP socket (all interfaces): 1337
+TCP socket (one interface):  127.0.0.1:1337
+UDP socket (all interfaces): udp:1337
+UDP socket (one interface):  udp:127.0.0.1:1337
+"""
+
 # module info
 modinfo = {
        'author': 'Erebus Team',
@@ -22,66 +47,32 @@ def modstart(parent, *args, **kwargs):
 modstop = lib.modstop
 
 # module code
+class BasicServer(lib.Socketlike):
+       def __init__(self, sock, data):
+               super(BasicServer, self).__init__(sock, data)
+               # NB neither directly referencing `channel`, nor trying to pass it through a default-arg-to-a-lambda like the python docs suggest, works here.
+               # Yay python. At least passing it via bind works.
+               self.chan = data
 
-def gotParent(parent):
-       for bindto, channel in parent.cfg.items('sockets'):
-               @lib.bind(bindto, data=channel)
-               class BasicServer(object):
-                       def __init__(self, sock, data):
-                               # NB neither directly referencing `channel`, nor trying to pass it through a default-arg-to-a-lambda like the python docs suggest, works here.
-                               # Yay python. At least passing it via bind works.
-                               self.chan = data
-                               self.buffer = b''
-                               self.sock = sock
-
-                       def getdata(self):
-                               recvd = self.sock.recv(8192)
-                               if recvd == b"": # EOF
-                                       if len(self.buffer) != 0:
-                                               # Process what's left in the buffer. We'll get called again after.
-                                               remaining_buf = self.buffer.decode('utf-8', 'backslashreplace')
-                                               self.buffer = b""
-                                               return [remaining_buf]
-                                       else:
-                                               # Nothing left in the buffer. Return None to signal the core to close this socket.
-                                               return None
-                               self.buffer += recvd
-                               lines = []
-
-                               while b"\n" in self.buffer:
-                                       pieces = self.buffer.split(b"\n", 1)
-                                       s = pieces[0].decode('utf-8', 'backslashreplace').rstrip("\r")
-                                       lines.append(pieces[0].decode('utf-8', 'backslashreplace'))
-                                       self.buffer = pieces[1]
-
-                               return lines
+       # default getdata() and send() methods are defined by lib.Socketlike
+       # suitable for line-based protocols like IRC
 
-                       def parse(self, line):
-                               try:
-                                       bot = lib.parent.channel(self.chan).bot
-                               except AttributeError: # <class 'AttributeError'> 'NoneType' object has no attribute 'bot'
-                                       bot = lib.parent.randbot()
-                               maxlen = bot.maxmsglen() - len("PRIVMSG  :") - len(self.chan)
-                               while len(line) > maxlen:
-                                       cutat = line.rfind(' ', 0, maxlen)
-                                       if cutat == -1:
-                                               cutat = maxlen
-                                       bot.msg(self.chan, line[0:cutat])
-                                       line = line[cutat:].strip()
-                               bot.msg(self.chan, line)
+       def parse(self, line):
+               try:
+                       bot = lib.parent.channel(self.chan).bot
+               except AttributeError: # <class 'AttributeError'> 'NoneType' object has no attribute 'bot'
+                       bot = lib.parent.randbot()
+               maxlen = bot.maxmsglen() - len("PRIVMSG  :") - len(self.chan)
+               while len(line) > maxlen:
+                       cutat = line.rfind(' ', 0, maxlen)
+                       if cutat == -1:
+                               cutat = maxlen
+                       bot.msg(self.chan, line[0:cutat])
+                       line = line[cutat:].strip()
+               bot.msg(self.chan, line)
 
-                       def send(self, line):
-                               if lib.parent.parent.cfg.getboolean('debug', 'io'):
-                                       lib.parent.log(str(self), 'O', line)
-                               self.sock.sendall(line.encode('utf-8', 'backslashreplace')+b"\r\n")
+       # default __str__() and __repr__() methods are defined by lib.Socketlike
 
-                       def _getsockerr(self):
-                               try: # SO_ERROR might not exist on all platforms
-                                       return self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
-                               except:
-                                       return None
-
-                       def __str__(self):
-                               return '%s#%d' % (__name__, self.sock.fileno())
-                       def __repr__(self):
-                               return '<%s #%d %s:%d>' % ((__name__, self.sock.fileno())+self.sock.getpeername())
+def gotParent(parent):
+       for bindto, channel in parent.cfg.items('sockets'):
+               lib.bind(bindto, data=channel)(BasicServer)
index a72f5bf9d47eab65a1365fb668fd172f9ad5b829..170b7d485502719535caadd29667f4f2cccb139b 100644 (file)
@@ -1,5 +1,6 @@
 # Erebus IRC bot - Author: Erebus Team
 # vim: fileencoding=utf-8
+# Steam API commands (!steamnp)
 
 # module info
 modinfo = {
index c0c0fd1ec2c768a812aefd2aa86a0b74961f0e03..ca23ec7dda620f4d1aedb75a0d436e8e0d4b3b21 100644 (file)
@@ -2,7 +2,6 @@
 # vim: fileencoding=utf-8
 # module for 's/regex/replacement/' style correction
 # warning: arbitrary regex's are generally capable of various DoS attacks on CPU/memory usage. use with caution.
-# see for usage examples: https://github.com/zonidjan/erebus/commit/d7e9802778477f1faa26a03078cb1b3c018a5e5c
 # This file is released into the public domain; see http://unlicense.org/
 
 # module info
index fd21676e8f10be99e662f283a70bb91745368ccd..158bc9c946441be8ca78b9e490176f96ca1e6352 100644 (file)
@@ -249,17 +249,18 @@ def _do_request(url, try_aia=False):
        """
        try:
                request = urllib2.Request(url, headers={
-                       'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
-                       'Sec-Ch-Ua': '"Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"',
-                       'Sec-Ch-Ua-Mobile': '?0',
-                       'Sec-Ch-Ua-Platform': '"Linux"',
-                       'Sec-Fetch-Dest': 'document',
-                       'Sec-Fetch-Mode': 'navigate',
-                       'Sec-Fetch-Site': 'same-origin',
-                       'Sec-Fetch-User': '?1',
-                       'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
-                       'Accept-Language': 'en-US,en;q=0.9',
-                       'Upgrade-Insecure-Requests': '1'
+                       'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
+                       'accept-language': 'en-US,en;q=0.9',
+                       'cache-control': 'max-age=0',
+                       'sec-ch-ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
+                       'sec-ch-ua-mobile': '?0',
+                       'sec-ch-ua-platform': '"Linux"',
+                       'sec-fetch-dest': 'document',
+                       'sec-fetch-mode': 'navigate',
+                       'sec-fetch-site': 'none',
+                       'sec-fetch-user': '?1',
+                       'upgrade-insecure-requests': '1',
+                       'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'
                })
        except ValueError:
                return '', False
index 937f1dc19b905a7e85799cd63c43a9a59c18f3dc..d6140d131367dfd3cdfb29d72c550f950268a77b 100644 (file)
@@ -1,6 +1,6 @@
 # Erebus IRC bot - Author: Erebus Team
 # vim: fileencoding=utf-8
-# weather module
+# weather module (from WeatherStack, boo)
 # This file is released into the public domain; see http://unlicense.org/
 
 # module info
similarity index 78%
rename from dump.sql
rename to mysql.sql
index 29cfe0c877b536c12c1d13f3376f80e72fbc51d8..ab17b72e06d0d60f5ed57307ef76a4d0502775d9 100644 (file)
--- a/dump.sql
+++ b/mysql.sql
@@ -34,17 +34,6 @@ CREATE TABLE `bots` (
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
---
--- Dumping data for table `bots`
---
-
-LOCK TABLES `bots` WRITE;
-/*!40000 ALTER TABLE `bots` DISABLE KEYS */;
-INSERT INTO `bots` VALUES
-('Erebus','Erebus',NULL,'Erebus','',1,0);
-/*!40000 ALTER TABLE `bots` ENABLE KEYS */;
-UNLOCK TABLES;
-
 --
 -- Table structure for table `chans`
 --
@@ -61,17 +50,6 @@ CREATE TABLE `chans` (
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
---
--- Dumping data for table `chans`
---
-
-LOCK TABLES `chans` WRITE;
-/*!40000 ALTER TABLE `chans` DISABLE KEYS */;
-INSERT INTO `chans` VALUES
-('#','Erebus',1);
-/*!40000 ALTER TABLE `chans` ENABLE KEYS */;
-UNLOCK TABLES;
-
 --
 -- Table structure for table `chusers`
 --
@@ -87,17 +65,6 @@ CREATE TABLE `chusers` (
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
---
--- Dumping data for table `chusers`
---
-
-LOCK TABLES `chusers` WRITE;
-/*!40000 ALTER TABLE `chusers` DISABLE KEYS */;
-INSERT INTO `chusers` VALUES
-('#','dimecadmium',5);
-/*!40000 ALTER TABLE `chusers` ENABLE KEYS */;
-UNLOCK TABLES;
-
 --
 -- Table structure for table `users`
 --
@@ -112,16 +79,6 @@ CREATE TABLE `users` (
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
---
--- Dumping data for table `users`
---
-
-LOCK TABLES `users` WRITE;
-/*!40000 ALTER TABLE `users` DISABLE KEYS */;
-INSERT INTO `users` VALUES
-('dimecadmium',100);
-/*!40000 ALTER TABLE `users` ENABLE KEYS */;
-UNLOCK TABLES;
 /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
 
 /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
diff --git a/sqlite.sql b/sqlite.sql
new file mode 100644 (file)
index 0000000..df3a4d4
--- /dev/null
@@ -0,0 +1,28 @@
+CREATE TABLE `bots` (
+  `nick` varchar(30) NOT NULL,
+  `user` varchar(10) NOT NULL DEFAULT 'erebus',
+  `bind` varchar(15) DEFAULT NULL,
+  `authname` varchar(30) DEFAULT NULL,
+  `authpass` varchar(20) DEFAULT NULL,
+  `active` tinyint(1) NOT NULL DEFAULT 1,
+  `connected` tinyint(1) NOT NULL DEFAULT 0,
+  PRIMARY KEY (`nick`)
+);
+CREATE TABLE `chans` (
+  `chname` varchar(100) NOT NULL,
+  `bot` varchar(30) NOT NULL,
+  `active` tinyint(1) NOT NULL DEFAULT 1,
+  PRIMARY KEY (`chname`)
+);
+CREATE INDEX chans_bot ON chans (bot);
+CREATE TABLE `chusers` (
+  `chan` varchar(100) NOT NULL,
+  `user` varchar(30) NOT NULL,
+  `level` int(11) NOT NULL,
+  PRIMARY KEY (`chan`,`user`)
+);
+CREATE TABLE `users` (
+  `auth` varchar(30) NOT NULL,
+  `level` tinyint(3) NOT NULL,
+  PRIMARY KEY (`auth`)
+);