Major improvements, bot V3

This commit is contained in:
Firepup Sixfifty 2024-03-06 12:33:04 -06:00
parent f9183d0a06
commit ae2c1cb59a
Signed by: Firepup650
GPG key ID: 7C92E2ABBBFAB9BA
9 changed files with 290 additions and 42 deletions

View file

@ -2,6 +2,7 @@
from socket import socket from socket import socket
from overrides import bytes, bbytes from overrides import bytes, bbytes
from typing import NoReturn, Union from typing import NoReturn, Union
from pylast import LastFMNetwork
logs = ... logs = ...
re = ... re = ...
@ -34,6 +35,8 @@ class bot:
current: str current: str
tmpHost: str tmpHost: str
ignores: list[str] ignores: list[str]
threads: list[str]
lastfmLink: LastFMNetwork
def __init__(self, server: str): def __init__(self, server: str):
... ...

14
bot.py
View file

@ -8,9 +8,11 @@ import commands as cmds
import config as conf import config as conf
from time import sleep from time import sleep
from importlib import reload from importlib import reload
import timers
import random as r import random as r
import handlers import handlers
import bare import bare
from threading import Thread
def mfind(message: str, find: list, usePrefix: bool = True) -> bool: def mfind(message: str, find: list, usePrefix: bool = True) -> bool:
@ -43,6 +45,8 @@ class bot(bare.bot):
self.queue: list[bbytes] = [] # pyright: ignore [reportInvalidTypeForm] self.queue: list[bbytes] = [] # pyright: ignore [reportInvalidTypeForm]
self.sock = socket(AF_INET, SOCK_STREAM) self.sock = socket(AF_INET, SOCK_STREAM)
self.current = "user" self.current = "user"
self.threads = conf.servers[server]["threads"]
self.lastfmLink = conf.lastfmLink
self.log(f"Start init for {self.server}") self.log(f"Start init for {self.server}")
def connect(self) -> None: def connect(self) -> None:
@ -220,6 +224,16 @@ class bot(bare.bot):
sleep(0.5) sleep(0.5)
for chan in self.channels: for chan in self.channels:
self.join(chan, "null", False) self.join(chan, "null", False)
tMgr = None
if self.threads:
tdict = {}
for thread in self.threads:
tdict[thread] = timers.data[thread]
if thread in ["radio"]:
tdict[thread]["args"] = [self]
tMgr = Thread(target=timers.threadManager, args=(tdict,))
tMgr.daemon = True
tMgr.start()
while 1: while 1:
raw = self.recv() raw = self.recv()
ircmsg = raw.safe_decode() ircmsg = raw.safe_decode()

View file

@ -21,7 +21,7 @@ def goat(bot: bare.bot, chan: str, name: str, message: str) -> None:
def botlist(bot: bare.bot, chan: str, name: str, message: str) -> None: def botlist(bot: bare.bot, chan: str, name: str, message: str) -> None:
bot.msg( bot.msg(
f"Hi! I'm FireBot (https://git.amcforum.wiki/Firepup650/fire-ircbot)! {'My admins on this server are' + bot.adminnames + '.' if bot.adminnames else ''}", # pyright: ignore [reportOperatorIssue] f"Hi! I'm FireBot (https://git.amcforum.wiki/Firepup650/fire-ircbot)! {'My admins on this server are' + str(bot.adminnames) + '.' if bot.adminnames else ''}", # pyright: ignore [reportOperatorIssue]
chan, chan,
) )

View file

@ -3,10 +3,10 @@ from os import environ as env
from dotenv import load_dotenv # type: ignore from dotenv import load_dotenv # type: ignore
import re, codecs import re, codecs
from typing import Optional, Any from typing import Optional, Any
import bare import bare, pylast
load_dotenv() load_dotenv()
__version__ = "v2.0.14" __version__ = "v3.0.0"
npbase: str = "\[\x0303last\.fm\x03\] [A-Za-z0-9_[\]{}\\|\-^]{1,$MAX} (is listening|last listened) to: \x02.+ - .*\x02( \([0-9]+ plays\)( \[.*\])?)?" # pyright: ignore [reportInvalidStringEscapeSequence] npbase: str = "\[\x0303last\.fm\x03\] [A-Za-z0-9_[\]{}\\|\-^]{1,$MAX} (is listening|last listened) to: \x02.+ - .*\x02( \([0-9]+ plays\)( \[.*\])?)?" # pyright: ignore [reportInvalidStringEscapeSequence]
su = "^(su|sudo|(su .*|sudo .*))$" su = "^(su|sudo|(su .*|sudo .*))$"
servers: dict[str, dict[str, Any]] = { servers: dict[str, dict[str, Any]] = {
@ -19,6 +19,7 @@ servers: dict[str, dict[str, Any]] = {
"ignores": ["#main/replirc"], "ignores": ["#main/replirc"],
"admins": [], "admins": [],
"hosts": ["9pfs.repl.co"], "hosts": ["9pfs.repl.co"],
"threads": [],
}, },
"efnet": { "efnet": {
"address": "irc.mzima.net", "address": "irc.mzima.net",
@ -26,21 +27,25 @@ servers: dict[str, dict[str, Any]] = {
"ignores": [], "ignores": [],
"admins": [], "admins": [],
"hosts": ["154.sub-174-251-241.myvzw.com"], "hosts": ["154.sub-174-251-241.myvzw.com"],
"threads": [],
}, },
"replirc": { "replirc": {
"address": "localhost", "address": "localhost",
"pass": env["replirc_pass"], "pass": env["replirc_pass"],
"channels": {"#random": 0, "#dice": 0, "#main": 0, "#bots": 0, "#firebot": 0, "#sshchat": 0}, "channels": {"#random": 0, "#dice": 0, "#main": 0, "#bots": 0, "#firebot": 0, "#sshchat": 0, "#firemc": 0, "#fp-radio": 0},
"ignores": [], "ignores": ["#fp-radio"],
"admins": ["h-tl"], "admins": ["h-tl"],
"hosts": ["owner.firepi"], "hosts": ["owner.firepi"],
"threads": ["radio"],
}, },
"backupbox": { "backupbox": {
"address": "172.23.11.5", "address": "localhost",
"port": 6607,
"channels": {"#default": 0, "#botrebellion": 0, "#main/replirc": 0}, "channels": {"#default": 0, "#botrebellion": 0, "#main/replirc": 0},
"ignores": ["#main/replirc"], "ignores": ["#main/replirc"],
"admins": [], "admins": [],
"hosts": ["172.20.171.225", "169.254.253.107"], "hosts": ["172.20.171.225", "169.254.253.107", "2600-6c5a-637f-1a85-0000-0000-0000-6667.inf6.spectrum.com"],
"threads": [],
}, },
} }
admin_hosts: list[str] = ["firepup.firepi", "47.221.227.180"] admin_hosts: list[str] = ["firepup.firepi", "47.221.227.180"]
@ -56,6 +61,7 @@ ESCAPE_SEQUENCE_RE = re.compile(
re.UNICODE | re.VERBOSE, re.UNICODE | re.VERBOSE,
) )
prefix = "." prefix = "."
lastfmLink = pylast.LastFMNetwork(env["FM_KEY"], env["FM_SECRET"])
npallowed: list[str] = ["FireBitBot"] npallowed: list[str] = ["FireBitBot"]
def decode_escapes(s: str) -> str: def decode_escapes(s: str) -> str:

40
core.py
View file

@ -3,43 +3,19 @@ from os import system
from time import sleep from time import sleep
from threading import Thread from threading import Thread
from logs import log from logs import log
from timers import threadManager
def launch(server: str) -> None: def launch(server: str) -> None:
system(f"python3 -u ircbot.py {server}") system(f"python3 -u ircbot.py {server}")
threads = {} servers = {
servers = [ "ircnow": {"noWrap": True, "func": launch, "args": ["ircnow"]},
"ircnow", "replirc": {"noWrap": True, "func": launch, "args": ["replirc"]},
"replirc", # "efnet": {"noWrap": True, "func": launch, "args": ["efnet"]},
# "efnet", "backupbox": {"noWrap": True, "func": launch, "args": ["backupbox"]},
"backupbox", }
]
def is_dead(thr: Thread) -> bool:
thr.join(timeout=0)
return not thr.is_alive()
def start(server: str) -> Thread:
t = Thread(target=launch, args=(server,))
t.daemon = True
t.start()
return t
if __name__ == "__main__": if __name__ == "__main__":
log("Begin initialization", "CORE") threadManager(servers, True, "CORE")
for server in servers:
threads[server] = start(server)
log("Started all instances. Idling...", "CORE")
while 1:
sleep(60)
log("Running a checkup on all running instances", "CORE")
for server in threads:
t = threads[server]
if is_dead(t):
log(f"The thread for {server} died, restarting it...", "CORE", "WARN")
threads[server] = start(server)

View file

@ -10,7 +10,7 @@ def log(
level: str = "LOG", level: str = "LOG",
time: Union[dt, str] = "now", time: Union[dt, str] = "now",
) -> None: ) -> None:
if level in ["EXIT", "CRASH"]: if level in ["EXIT", "CRASH", "FATAL"]:
stream = stderr stream = stderr
else: else:
stream = stdout stream = stdout

159
poetry.lock generated
View file

@ -1,9 +1,33 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. # This file is automatically @generated by Poetry and should not be changed by hand.
[[package]]
name = "anyio"
version = "4.3.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
category = "main"
optional = false
python-versions = ">=3.8"
files = [
{file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"},
{file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"},
]
[package.dependencies]
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
idna = ">=2.8"
sniffio = ">=1.1"
typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
[package.extras]
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
trio = ["trio (>=0.23)"]
[[package]] [[package]]
name = "apiclient" name = "apiclient"
version = "1.0.4" version = "1.0.4"
description = "Framework for making good API client libraries using urllib3." description = "Framework for making good API client libraries using urllib3."
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -18,6 +42,7 @@ urllib3 = "*"
name = "certifi" name = "certifi"
version = "2023.11.17" version = "2023.11.17"
description = "Python package for providing Mozilla's CA Bundle." description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -25,10 +50,115 @@ files = [
{file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"},
] ]
[[package]]
name = "exceptiongroup"
version = "1.2.0"
description = "Backport of PEP 654 (exception groups)"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
{file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
]
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "h11"
version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
]
[[package]]
name = "httpcore"
version = "1.0.4"
description = "A minimal low-level HTTP client."
category = "main"
optional = false
python-versions = ">=3.8"
files = [
{file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"},
{file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"},
]
[package.dependencies]
certifi = "*"
h11 = ">=0.13,<0.15"
[package.extras]
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (>=1.0.0,<2.0.0)"]
trio = ["trio (>=0.22.0,<0.25.0)"]
[[package]]
name = "httpx"
version = "0.27.0"
description = "The next generation HTTP client."
category = "main"
optional = false
python-versions = ">=3.8"
files = [
{file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
{file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
]
[package.dependencies]
anyio = "*"
certifi = "*"
httpcore = ">=1.0.0,<2.0.0"
idna = "*"
sniffio = "*"
[package.extras]
brotli = ["brotli", "brotlicffi"]
cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (>=1.0.0,<2.0.0)"]
[[package]]
name = "idna"
version = "3.6"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
python-versions = ">=3.5"
files = [
{file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
{file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
]
[[package]]
name = "pylast"
version = "5.2.0"
description = "A Python interface to Last.fm and Libre.fm"
category = "main"
optional = false
python-versions = ">=3.8"
files = [
{file = "pylast-5.2.0-py3-none-any.whl", hash = "sha256:89c7c01ea9f08c83865999d8907835157a8096e77dd9dc23420246eb66cfcff5"},
{file = "pylast-5.2.0.tar.gz", hash = "sha256:bb046804ef56a0c18072c750d61a282d47ac102a3b0b9c44a023eaf5b0934b0a"},
]
[package.dependencies]
httpx = "*"
[package.extras]
tests = ["flaky", "pytest", "pytest-cov", "pytest-random-order", "pyyaml"]
[[package]] [[package]]
name = "python-dotenv" name = "python-dotenv"
version = "1.0.0" version = "1.0.0"
description = "Read key-value pairs from a .env file and set them as environment variables" description = "Read key-value pairs from a .env file and set them as environment variables"
category = "main"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -39,10 +169,35 @@ files = [
[package.extras] [package.extras]
cli = ["click (>=5.0)"] cli = ["click (>=5.0)"]
[[package]]
name = "sniffio"
version = "1.3.1"
description = "Sniff out which async library your code is running under"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
]
[[package]]
name = "typing-extensions"
version = "4.10.0"
description = "Backported and Experimental Type Hints for Python 3.8+"
category = "main"
optional = false
python-versions = ">=3.8"
files = [
{file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"},
{file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"},
]
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.1.0" version = "2.1.0"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -58,4 +213,4 @@ zstd = ["zstandard (>=0.18.0)"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.9" python-versions = "^3.9"
content-hash = "48808e6f2c2ef0f18b46ffba3d13d473eacccad61c6bc369c655a127a7df48fd" content-hash = "5f0106196ba3a316e887fe2748bb5ca19159f2905d7678393b8ee51430f9ca45"

View file

@ -8,6 +8,7 @@ authors = ["Firepup Sixfifty <firepup650@gmail.com>"]
python = "^3.9" python = "^3.9"
apiclient = "^1.0.4" apiclient = "^1.0.4"
python-dotenv = "^1.0.0" python-dotenv = "^1.0.0"
pylast = "^5.2.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]

93
timers.py Normal file
View file

@ -0,0 +1,93 @@
#!/usr/bin/python3
import bare, pylast
import config as conf
import random as r
from logs import log
from typing import Any, Callable, NoReturn
from threading import Thread
from time import sleep
from traceback import format_exc
def is_dead(thr: Thread) -> bool:
thr.join(timeout=0)
return not thr.is_alive()
def threadWrapper(data: dict) -> NoReturn:
if not data["noWrap"]:
while 1:
if ignoreErrors:
try:
data["func"](*data["args"])
except Exception:
Err = format_exc()
for line in Err.split("\n"):
log(line, "Thread", "WARN")
else:
try:
data["func"](*data["args"])
except Exception:
Err = format_exc()
for line in Err.split("\n"):
log(line, "Thread", "CRASH")
exit(1)
sleep(data["interval"])
log("Threaded loop broken", "Thread", "FATAL")
else:
data["func"](*data["args"])
exit(1)
def startThread(data: dict) -> Thread:
t = Thread(target=threadWrapper, args=(data,))
t.daemon = True
t.start()
return t
def threadManager(threads: dict[str, dict[str, Any]], output: bool = False, mgr: str = "TManager", interval: int = 60) -> NoReturn:
if output:
log("Begin init of thread manager", mgr)
running = {}
for name in threads:
data = threads[name]
running[name] = startThread(data)
if output:
log("All threads running, starting checkup loop", mgr)
while 1:
sleep(interval)
if output:
log("Checking threads", mgr)
for name in running:
t = running[name]
if is_dead(t):
if output:
log(f"Thread {name} has died, restarting", mgr, "WARN")
data = threads[name]
running[name] = startThread(data)
log("Thread manager loop broken", mgr, "FATAL")
exit(1)
def radio(instance: bare.bot) -> NoReturn:
lastTrack = ""
while 1:
try:
newTrack = instance.lastfmLink.get_user("Firepup650").get_now_playing()
if newTrack:
thisTrack = newTrack.__str__()
if thisTrack != lastTrack:
lastTrack = thisTrack
instance.msg("f.sp " + thisTrack, "#fp-radio")
instance.sendraw(f"TOPIC #fp-radio :Firepup radio ({thisTrack}) - https://open.spotify.com/playlist/4ctNy3O0rOwhhXIKyLvUZM")
except Exception:
Err = format_exc()
for line in Err.split("\n"):
instance.log(line, "WARN")
sleep(2)
instance.log("Thread while loop broken", "FATAL")
exit(1)
data: dict[str, dict[str, Any]] = {
"radio": {"noWrap": True, "func": radio, "args": []},
}