Compare commits

..

49 commits

Author SHA1 Message Date
6aef607dc3
FIX: Extra # in channel names when kickbanning, FEAT: Hardcoded blacklist now implemented, for known malicous hosts 2024-11-20 12:50:05 -06:00
40ff789fe1
sudo: a password is required to list this diff 2024-11-14 12:01:34 -06:00
e5a1ac6897
Add a ping monitor, to prevent/recover from servers silently disconnecting 2024-11-14 10:39:10 -06:00
9402a536f7
Bump version 2024-09-17 12:16:25 -05:00
3db808ad49
Change repo URL everywhere it's still outdated 2024-09-17 12:14:39 -05:00
56e60f59ce
Formatting, adding some aliases, early work on better help command 2024-09-17 12:05:56 -05:00
3053948bdc
Add some commands, correct some strings, update admin IP, and add +k to possible reasons joining a channel failed. 2024-09-13 09:34:55 -05:00
67cbb64a31
Bump version 2024-07-02 22:37:35 -05:00
50473cc700
remove async file, add slapping, add handling for user modes changing 2024-07-02 22:33:18 -05:00
72d554340b
Start work on async 2024-06-10 11:55:20 -05:00
af89d25fce
Formatting 2024-06-10 04:20:54 -05:00
433f57beb8
cache dnsbl lookups (v3.0.19) 2024-06-10 04:20:45 -05:00
19ff5ee010
Assume the user gave a valid hostname if it's not the proper format. (v3.0.18) 2024-06-10 03:50:46 -05:00
47e377a68d
Actually work if a command is multi-worded 2024-05-25 14:49:48 -05:00
b720b5edf8
Formatting 2024-05-24 12:06:41 -05:00
ce6dc597ef
Small Typo 3 2024-05-24 12:06:32 -05:00
1feb1e2813
Little typo 2 2024-05-23 23:48:25 -05:00
aa0dd3cc22
We don't handle these, and they're normal things, so ignore 'em. 2024-05-23 23:47:18 -05:00
152c87a8db
Little typo 2024-05-23 23:42:50 -05:00
b695b0e676
Add a custom blacklist provider for droneBL, since the default one doesn't work for it. 2024-05-23 22:46:36 -05:00
8b41a048c5
Error handling and dnsbl improvements 2024-05-23 13:12:46 -05:00
58060a5249
Improve cmd checking, add two new debug commands, and watch for gaining/losing ops 2024-05-23 09:05:02 -05:00
fdd490518c
Allow admins to premptively check DNSBLs. Also remove some Debug lines and fix minor issues. 2024-05-23 08:17:56 -05:00
8dc9755c46
Status support, fix minor bug in data logic 2024-05-22 21:07:48 -05:00
971a889cc1
Oversight in dnsbl checks 2024-05-20 14:59:55 -05:00
49aa61417a
Multi-line parsing bug fixes 2024-05-18 22:52:40 -05:00
385d44c81a
Fix dnsbl errors, bug fixes in bot.py 2024-05-18 22:23:36 -05:00
74bc044fec
DNSBL support 2024-05-18 17:40:37 -05:00
3ff4b1a3fb
New Router 2024-05-16 21:42:38 -05:00
0cdaa715a3
poetry update, add black 2024-05-13 11:09:52 -05:00
b0e79861ad
Formatting 2024-05-11 22:30:31 -05:00
13b9f27994
Add more messages to the log 2024-05-11 22:28:44 -05:00
4325b1c6d6
Allow changing of prefix 2024-05-11 22:28:22 -05:00
39f7f118e5
Oops. This should have been added earlier 2024-05-11 22:27:24 -05:00
d8a612b6f6
Formatting 2024-05-10 20:51:14 -05:00
4f0f5d50e6
Set up auto-markov replies, if set in config 2024-05-10 20:50:31 -05:00
c69d555e90
Changeable auto-reply soon 2024-05-10 19:43:14 -05:00
c90df959d5
Add markov functionality, don\'t allow regex to be a fatal crash, and disable radio debugging 2024-05-09 22:50:51 -05:00
11044a5296
New master messages file 2024-05-09 20:01:50 -05:00
a0893dd4b2
Radio debugging on homeserver 2024-05-09 20:01:05 -05:00
df71efc4c9
Reset missed chunks when a track fails 2024-05-06 09:47:30 -05:00
7989715c1d
Don't reset the counter when the song changes 2024-05-06 09:45:02 -05:00
eaa1f14bf9
Formatting 2024-05-06 09:36:40 -05:00
e5e64bd7c1
Try to have the radio dynamically adjust miss rates, based on success rates. 2024-05-06 09:36:31 -05:00
20a2c7bf1b
Change timers.py to threads.py for clarity 2024-05-02 13:25:21 -05:00
9684152def
Formatting 2024-05-01 15:41:32 -05:00
23dfa854ac
Fix bugs & remove some debug stuff 2024-04-30 21:19:28 -05:00
a1a722e0cd
idk where I copied that from? 2024-04-30 21:08:41 -05:00
0528f07a7d
Debugging QUIT 2024-04-30 21:04:28 -05:00
13 changed files with 11055 additions and 84 deletions

View file

@ -3,6 +3,7 @@ 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 from pylast import LastFMNetwork
from markov import MarkovBot
logs = ... logs = ...
re = ... re = ...
@ -39,6 +40,12 @@ class bot:
onIdntCmds: list[str] onIdntCmds: list[str]
onJoinCmds: list[str] onJoinCmds: list[str]
onStrtCmds: list[str] onStrtCmds: list[str]
markov: MarkovBot
autoMethod: str
dnsblMode: str
statuses: dict[str, dict[str, str]]
ops: dict[str, bool]
dns: dict[str, dict[str, Union[str, list[str]]]]
def __init__(self, server: str): ... def __init__(self, server: str): ...

97
bot.py
View file

@ -8,11 +8,13 @@ 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 threads
import random as r import random as r
import handlers import handlers
import bare import bare
from threading import Thread from threading import Thread
from markov import MarkovBot
from traceback import format_exc
def mfind(message: str, find: list, usePrefix: bool = True) -> bool: def mfind(message: str, find: list, usePrefix: bool = True) -> bool:
@ -51,6 +53,8 @@ class bot(bare.bot):
else "FireBot" else "FireBot"
) )
self.queue: list[bbytes] = [] # pyright: ignore [reportInvalidTypeForm] self.queue: list[bbytes] = [] # pyright: ignore [reportInvalidTypeForm]
self.statuses = {"firepup": {}}
self.ops = {}
self.sock = socket(AF_INET, SOCK_STREAM) self.sock = socket(AF_INET, SOCK_STREAM)
self.current = "user" self.current = "user"
self.threads = ( self.threads = (
@ -71,7 +75,26 @@ class bot(bare.bot):
if "onStrtCmds" in conf.servers[server] if "onStrtCmds" in conf.servers[server]
else [] else []
) )
self.autoMethod = (
conf.servers[server]["autoMethod"]
if "autoMethod" in conf.servers[server]
else "QUOTE"
)
self.dnsblMode = (
conf.servers[server]["dnsblMode"]
if "dnsblMode" in conf.servers[server]
else "none"
)
self.dns = {}
self.lastfmLink = conf.lastfmLink self.lastfmLink = conf.lastfmLink
with open("mastermessages.txt") as f:
TMFeed = []
for line in f.readlines():
TMFeed.extend([line.strip().split()])
self.markov = MarkovBot(TMFeed)
conf.prefix = (
conf.servers[server]["prefix"] if "prefix" in conf.servers[server] else "."
)
self.log(f"Start init for {self.server}") self.log(f"Start init for {self.server}")
def connect(self) -> None: def connect(self) -> None:
@ -168,6 +191,11 @@ class bot(bare.bot):
if origin != "null": if origin != "null":
self.msg(f"I'm banned from {chan}.", origin) self.msg(f"I'm banned from {chan}.", origin)
break break
elif code == 475:
self.log(f"Joining {chan} failed (+k without/with bad key)")
if origin != "null":
self.msg(f"{chan} is +k, and either you didn't give me a key, or you gave me the wrong one.", origin)
break
elif code == 480: elif code == 480:
self.log(f"Joining {chan} failed (+S)", "WARN") self.log(f"Joining {chan} failed (+S)", "WARN")
if origin != "null": if origin != "null":
@ -213,7 +241,12 @@ class bot(bare.bot):
def recv(self) -> bytes: def recv(self) -> bytes:
if self.queue: if self.queue:
return bytes(self.queue.pop(0)) return bytes(self.queue.pop(0))
data = bytes(self.sock.recv(2048).strip(b"\r\n")) data = bytes(self.sock.recv(2048))
if data.lazy_decode() == "":
return data
while not data.endswith(b"\r\n"):
data += bytes(self.sock.recv(2048))
data = bytes(data.strip(b"\r\n"))
if b"\r\n" in data: if b"\r\n" in data:
self.queue.extend(data.split(b"\r\n")) self.queue.extend(data.split(b"\r\n"))
return bytes(self.queue.pop(0)) return bytes(self.queue.pop(0))
@ -269,10 +302,10 @@ class bot(bare.bot):
if self.threads: if self.threads:
tdict = {} tdict = {}
for thread in self.threads: for thread in self.threads:
tdict[thread] = timers.data[thread] tdict[thread] = threads.data[thread]
if tdict[thread]["passInstance"]: if tdict[thread]["passInstance"]:
tdict[thread]["args"] = [self] tdict[thread]["args"] = [self]
tMgr = Thread(target=timers.threadManager, args=(tdict,)) tMgr = Thread(target=threads.threadManager, args=(tdict,))
tMgr.daemon = True tMgr.daemon = True
tMgr.start() tMgr.start()
while 1: while 1:
@ -291,27 +324,41 @@ class bot(bare.bot):
if action in handlers.handles: if action in handlers.handles:
res, chan = handlers.handles[action](self, ircmsg) res, chan = handlers.handles[action](self, ircmsg)
if res == "reload" and type(chan) == str: if res == "reload" and type(chan) == str:
reload(conf) try:
self.adminnames = ( reload(conf)
conf.servers[self.server]["admins"] self.adminnames = (
if "admins" in conf.servers[self.server] conf.servers[self.server]["admins"]
else [] if "admins" in conf.servers[self.server]
) else []
self.ignores = ( )
conf.servers[self.server]["ignores"] self.ignores = (
if "ignores" in conf.servers[self.server] conf.servers[self.server]["ignores"]
else [] if "ignores" in conf.servers[self.server]
) else []
self.__version__ = conf.__version__ )
self.npallowed = conf.npallowed self.__version__ = conf.__version__
self.interval = ( self.npallowed = conf.npallowed
conf.servers[self.server]["interval"] self.interval = (
if "interval" in conf.servers[self.server] conf.servers[self.server]["interval"]
else 50 if "interval" in conf.servers[self.server]
) else 50
reload(cmds) )
reload(handlers) conf.prefix = (
self.msg("Reloaded successfully", chan) conf.servers[self.server]["prefix"]
if "prefix" in conf.servers[self.server]
else "."
)
reload(cmds)
reload(handlers)
self.msg("Reloaded successfully", chan)
except Exception:
Err = format_exc()
for line in Err.split("\n"):
self.log(line, "ERROR")
self.msg(
"Reload failed, likely partially reloaded. Please check error logs.",
chan,
)
else: else:
if ircmsg.startswith("PING "): if ircmsg.startswith("PING "):
self.ping(ircmsg) self.ping(ircmsg)

View file

@ -23,7 +23,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' + str(bot.adminnames) + '.' if bot.adminnames else ''}", # pyright: ignore [reportOperatorIssue] f"Hi! I'm FireBot (https://git.h.hackclub.app/Firepup650/FireBot)! {'My admins on this server are' + str(bot.adminnames) + '.' if bot.adminnames else ''}", # pyright: ignore [reportOperatorIssue]
chan, chan,
) )
@ -67,27 +67,29 @@ def isAdmin(bot: bare.bot, chan: str, name: str, message: str) -> None:
def help(bot: bare.bot, chan: str, name: str, message: str) -> None: def help(bot: bare.bot, chan: str, name: str, message: str) -> None:
helpErr = False helpErr = False
category = None
if len(message.split(" ")) > 1:
category = message.split(" ")[1]
if bot.current == "bridge": if bot.current == "bridge":
helpErr = True helpErr = True
if not helpErr: if not helpErr:
bot.msg("Command list needs rework", name) bot.msg("Command list needs rework", chan)
return return
bot.msg("List of commands:", name) match category:
bot.msg(f'Current bot.prefix is "{bot.prefix}"', name) case None:
bot.msg(f"{bot.prefix}help - Sends this help list", name) bot.msg("Categories of commands: gen, dbg, adm, fun, msc", chan)
bot.msg(f"{bot.prefix}quote - Sends a random firepup quote", name) case "gen":
bot.msg( bot.msg("Commands in the General category: ", chan)
f"{bot.prefix}(eightball,8ball,8b) [question]? - Asks the magic eightball a question", case "dbg":
name, bot.msg("Commands in the [DEBUG] category: ", chan)
) case "adm":
bot.msg(f"(hi,hello) {bot.nick} - The bot says hi to you", name) bot.msg("Commands in the Admin category: ", chan)
if checks.admin(bot, name): case "fun":
bot.msg(f"reboot {bot.rebt} - Restarts the bot", name) bot.msg("Commands in the Fun category: ", chan)
bot.msg("op me - Makes the bot try to op you", name) case "msc":
bot.msg( bot.msg("Commands in the Misc. category: ", chan)
f"{bot.prefix}join [channel(s)] - Joins the bot to the specified channel(s)", case _:
name, bot.msg("Unknown commands category.", chan)
)
else: else:
bot.msg("Sorry, I can't send help to bridged users.", chan) bot.msg("Sorry, I can't send help to bridged users.", chan)
@ -112,7 +114,11 @@ def quote(bot: bare.bot, chan: str, name: str, message: str) -> None:
) # pyright: ignore [reportInvalidStringEscapeSequence] ) # pyright: ignore [reportInvalidStringEscapeSequence]
r.seed() r.seed()
with open("mastermessages.txt", "r") as mm: with open("mastermessages.txt", "r") as mm:
q = list(filter(lambda x: re.search(qfilter, x), mm.readlines())) q = []
try:
q = list(filter(lambda x: re.search(qfilter, x), mm.readlines()))
except re.error:
q = ["Sorry, your query is invalid regex. Please try again."]
if q == []: if q == []:
q = [f'No results for "{query}" '] q = [f'No results for "{query}" ']
sel = conf.decode_escapes( sel = conf.decode_escapes(
@ -148,6 +154,26 @@ def debug(bot: bare.bot, chan: str, name: str, message: str) -> None:
bot.msg(f"[DEBUG] {dbg_out}", chan) bot.msg(f"[DEBUG] {dbg_out}", chan)
def debugInternal(bot: bare.bot, chan: str, name: str, message: str) -> None:
things = dir(bot)
try:
thing = message.split(" ", 1)[1]
except IndexError:
bot.msg("You can't just ask me to lookup nothing.", chan)
return
if thing in things:
bot.msg(f"self.{thing} = {getattr(bot, thing)}", chan)
else:
bot.msg(f'I have nothing called "{thing}"', chan)
def debugEval(bot: bare.bot, chan: str, name: str, message: str) -> None:
try:
bot.msg(str(eval(message.split(" ", 1)[1])), chan)
except Exception as E:
bot.msg(f"Exception: {E}", chan)
def raw(bot: bare.bot, chan: str, name: str, message: str) -> None: def raw(bot: bare.bot, chan: str, name: str, message: str) -> None:
bot.sendraw(message.split(" ", 1)[1]) bot.sendraw(message.split(" ", 1)[1])
@ -159,11 +185,11 @@ def reboot(bot: bare.bot, chan: str, name: str, message: str) -> None:
def sudo(bot: bare.bot, chan: str, name: str, message: str) -> None: def sudo(bot: bare.bot, chan: str, name: str, message: str) -> None:
if checks.admin(bot, name): if checks.admin(bot, name):
bot.msg("Error - system failure, contact system operator", chan) bot.msg("Operation not permitted", chan)
elif "bot" in name.lower(): elif "bot" in name.lower():
bot.log("lol, no.") bot.log("lol, no.")
else: else:
bot.msg("Access Denied", chan) bot.msg('sudo: a password is required', chan)
def nowplaying(bot: bare.bot, chan: str, name: str, message: str) -> None: def nowplaying(bot: bare.bot, chan: str, name: str, message: str) -> None:
@ -199,6 +225,157 @@ def whoami(bot: bare.bot, chan: str, name: str, message: str) -> None:
) )
def markov(bot: bare.bot, chan: str, name: str, message: str) -> None:
word = None
if " " in message:
word = message.split()[1]
proposed = bot.markov.generate_text(word)
if proposed == word:
proposed = f'Chain failed. (Firepup has never been recorded saying "{word}")'
bot.msg(proposed, chan)
def setStatus(bot: bare.bot, chan: str, name: str, message: str) -> None:
user, stat, reas = ("", 0, "")
try:
if message.split(" ")[1] == "help":
bot.msg(
"Assuming you want help with status codes. 1 is Available, 2 is Busy, 3 is Unavailable, anything else is Unknown.",
chan,
)
return
message = message.split(" ", 1)[1]
user = message.split(" ")[0].lower()
stat = int(message.split(" ")[1])
reas = message.split(" ", 2)[2]
except IndexError:
bot.msg(
f"Insufficent information to set a status. Only got {len(message.split(' ')) - (1 if '.sS' in message else 0)}/3 expected args.",
chan,
)
return
except ValueError:
bot.msg("Status parameter must be an int.", chan)
return
match stat:
case 1:
stat = "Available"
case 2:
stat = "Busy"
case 3:
stat = "Unavailable"
case _:
stat = "Unknown"
if user in ["me", "my", "I"]:
user = "firepup"
bot.statuses[user] = {"status": stat, "reason": reas}
bot.msg(f"Status set for '{user}'. Raw data: {bot.statuses[user]}", chan)
def getStatus(bot: bare.bot, chan: str, name: str, message: str) -> None:
user = ""
try:
user = message.split(" ")[1]
except IndexError:
user = "firepup"
if bot.statuses.get(user) is None:
bot.msg("You've gotta provide a nick I actually recognize.", chan)
return
bot.msg(
f"{user}'s status: {'Unknown' if not bot.statuses[user].get('status') else bot.statuses[user]['status']} - {'Reason unset' if not bot.statuses[user].get('reason') else bot.statuses[user]['reason']}",
chan,
)
def check(bot: bare.bot, chan: str, name: str, message: str) -> None:
try:
msg = message.split(" ", 1)[1]
nick = msg.split("!")[0]
host = msg.split("@", 1)[1]
cache = host in bot.dns
dnsbl, raws = conf.dnsblHandler(bot, nick, host, chan)
bot.msg(
f"Blacklist check: {'(Cached) ' if cache else ''}{dnsbl if dnsbl else 'Safe.'} ({raws})",
chan,
)
except IndexError:
try:
host = message.split(" ", 1)[1]
cache = host in bot.dns
dnsbl, raws = conf.dnsblHandler(
bot, "thisusernameshouldbetoolongtoeveractuallybeinuse", host, chan
)
bot.msg(
f"Blacklist check: {'(Cached) ' if cache else ''}{dnsbl if dnsbl else 'Safe.'} ({raws})",
chan,
)
except Exception as E:
bot.msg("Blacklist Lookup Failed. Error recorded to bot logs.", chan)
bot.log(str(E), "FATAL")
except Exception as E:
bot.msg("Blacklist lookup failed. Error recorded to bot logs.", chan)
bot.log(str(E), "FATAL")
def slap(bot: bare.bot, chan: str, name: str, message: str) -> None:
msg = message.split(" ")
if len(msg) > 1:
msg = " ".join(msg[1:]).strip()
if msg == bot.nick or not msg:
msg = name
else:
msg = name
bot.msg(
f"\x01ACTION slaps {msg} around a bit with {r.choice(['a firewall', 'a fireball', 'a large trout', 'a computer', 'an rpi4', 'an rpi5', 'firepi', name])}\x01",
chan,
)
def morning(bot: bare.bot, chan: str, name: str, message: str) -> None:
msg = message.split(" ")
addresse = " ".join(msg[2:]).strip().lower()
postfix = ""
if addresse and addresse[-1] in ["!", ".", "?"]:
postfix = addresse[-1]
addresse = addresse[:-1]
elif message[-1] in ["!", ".", "?"]:
postfix = addresse[-1]
addresse = addresse[:-1]
if addresse not in ["everyone", "people", bot.nick.lower(), ""]:
return
bot.msg(f"Good morning {name}{postfix}", chan)
def night(bot: bare.bot, chan: str, name: str, message: str) -> None:
msg = message.split(" ")
addresse = " ".join(msg[2:]).strip().lower()
postfix = ""
if addresse and addresse[-1] in ["!", ".", "?"]:
postfix = addresse[-1]
addresse = addresse[:-1]
elif message[-1] in ["!", ".", "?"]:
postfix = addresse[-1]
addresse = addresse[:-1]
if addresse not in ["everyone", "people", bot.nick.lower(), ""]:
return
bot.msg(f"Good night {name}{postfix}", chan)
def afternoon(bot: bare.bot, chan: str, name: str, message: str) -> None:
msg = message.split(" ")
addresse = " ".join(msg[2:]).strip().lower()
postfix = ""
if addresse and addresse[-1] in ["!", ".", "?"]:
postfix = addresse[-1]
addresse = addresse[:-1]
elif message[-1] in ["!", ".", "?"]:
postfix = addresse[-1]
addresse = addresse[:-1]
if addresse not in ["everyone", "people", bot.nick.lower(), ""]:
return
bot.msg(f"Good afternoon {name}{postfix}", chan)
data: dict[str, dict[str, Any]] = { data: dict[str, dict[str, Any]] = {
"!botlist": {"prefix": False, "aliases": []}, "!botlist": {"prefix": False, "aliases": []},
"bugs bugs bugs": {"prefix": False, "aliases": []}, "bugs bugs bugs": {"prefix": False, "aliases": []},
@ -209,11 +386,17 @@ data: dict[str, dict[str, Any]] = {
"aliases": ["reboot", "stop", "hardreload", "hr"], "aliases": ["reboot", "stop", "hardreload", "hr"],
"check": checks.admin, "check": checks.admin,
}, },
"uptime": {"prefix": True, "aliases": []}, "uptime": {"prefix": True, "aliases": ["u"]},
"raw ": {"prefix": True, "aliases": ["cmd "], "check": checks.admin}, "raw": {"prefix": True, "aliases": ["cmd "], "check": checks.admin},
"debug": {"prefix": True, "aliases": ["dbg", "d"], "check": checks.admin}, "debug": {"prefix": True, "aliases": ["dbg", "d"], "check": checks.admin},
"debugInternal": {
"prefix": True,
"aliases": ["dbgInt", "dI"],
"check": checks.admin,
},
"debugEval": {"prefix": True, "aliases": ["dbgEval", "dE"], "check": checks.admin},
"8ball": {"prefix": True, "aliases": ["eightball", "8b"]}, "8ball": {"prefix": True, "aliases": ["eightball", "8b"]},
"join ": {"prefix": True, "aliases": [], "check": checks.admin}, "join": {"prefix": True, "aliases": ["j"], "check": checks.admin},
"quote": {"prefix": True, "aliases": ["q"]}, "quote": {"prefix": True, "aliases": ["q"]},
"goat.mode.activate": {"prefix": True, "aliases": ["g.m.a"], "check": checks.admin}, "goat.mode.activate": {"prefix": True, "aliases": ["g.m.a"], "check": checks.admin},
"goat.mode.deactivate": { "goat.mode.deactivate": {
@ -221,7 +404,7 @@ data: dict[str, dict[str, Any]] = {
"aliases": ["g.m.d"], "aliases": ["g.m.d"],
"check": checks.admin, "check": checks.admin,
}, },
"help": {"prefix": True, "aliases": ["?"]}, "help": {"prefix": True, "aliases": ["?", "list", "h"]},
"amiadmin": {"prefix": True, "aliases": []}, "amiadmin": {"prefix": True, "aliases": []},
"ping": {"prefix": True, "aliases": []}, "ping": {"prefix": True, "aliases": []},
"op me": {"prefix": False, "aliases": [], "check": checks.admin}, "op me": {"prefix": False, "aliases": [], "check": checks.admin},
@ -229,6 +412,17 @@ data: dict[str, dict[str, Any]] = {
"fpmp": {"prefix": True, "aliases": []}, "fpmp": {"prefix": True, "aliases": []},
"version": {"prefix": True, "aliases": ["ver", "v"]}, "version": {"prefix": True, "aliases": ["ver", "v"]},
"np": {"prefix": True, "aliases": []}, "np": {"prefix": True, "aliases": []},
"markov": {"prefix": True, "aliases": ["m"]},
"setStatus": {"prefix": True, "aliases": ["sS"], "check": checks.admin},
"getStatus": {"prefix": True, "aliases": ["gS"]},
"check": {"prefix": True, "aliases": [], "check": checks.admin},
"slap": {"prefix": True, "aliases": ["s"]},
"good morning": {
"prefix": False,
"aliases": ["g'morning", "morning", "mornin'", "good mornin'"],
},
"good night": {"prefix": False, "aliases": ["g'night", "night"]},
"good afternoon": {"prefix": False, "aliases": ["g'afternoon", "afternoon"]},
} }
regexes: list[str] = [conf.npbase, conf.su] regexes: list[str] = [conf.npbase, conf.su]
call: dict[str, Callable[[bare.bot, str, str, str], None]] = { call: dict[str, Callable[[bare.bot, str, str, str], None]] = {
@ -239,10 +433,12 @@ call: dict[str, Callable[[bare.bot, str, str, str], None]] = {
conf.su: sudo, conf.su: sudo,
"restart": reboot, "restart": reboot,
"uptime": uptime, "uptime": uptime,
"raw ": raw, "raw": raw,
"debug": debug, "debug": debug,
"debugInternal": debugInternal,
"debugEval": debugEval,
"8ball": eball, "8ball": eball,
"join ": join, "join": join,
"quote": quote, "quote": quote,
"goat.mode.activate": goatOn, "goat.mode.activate": goatOn,
"goat.mode.decativate": goatOff, "goat.mode.decativate": goatOff,
@ -254,4 +450,12 @@ call: dict[str, Callable[[bare.bot, str, str, str], None]] = {
"fpmp": fpmp, "fpmp": fpmp,
"version": version, "version": version,
"np": fmpull, "np": fmpull,
"markov": markov,
"setStatus": setStatus,
"getStatus": getStatus,
"check": check,
"slap": slap,
"good morning": morning,
"good night": night,
"good afternoon": afternoon,
} }

140
config.py
View file

@ -2,11 +2,39 @@
from os import environ as env 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, Union
import bare, pylast import bare, pylast
from pydnsbl import DNSBLIpChecker, DNSBLDomainChecker, providers as BL
class droneBL(BL.Provider):
def process_response(self, response):
reasons = set()
for result in response:
reason = result.host
if reason in ["127.0.0.3"]:
reasons.add("IRC Spambot")
elif reason in ["127.0.0.19"]:
reasons.add("Abused VPN")
elif reason in ["127.0.0.9", "127.0.0.8"]:
reasons.add("Open Proxy")
elif reason in ["127.0.0.13"]:
reasons.add("Automated Attacks")
else:
print("Unknown dnsbl reason: " + reason, flush=True)
reasons.add("unknown")
return reasons
providers = BL.BASE_PROVIDERS + [droneBL("dnsbl.dronebl.org")]
ipbl = DNSBLIpChecker(providers=providers)
hsbl = DNSBLDomainChecker(providers=providers)
hardbl = ["146.70.59.36"]
load_dotenv() load_dotenv()
__version__ = "v3.0.8" __version__ = "v3.0.22"
npbase: str = ( npbase: str = (
"\[\x0303last\.fm\x03\] [A-Za-z0-9_[\]{}\\|\-^]{1,$MAX} (is listening|last listened) to: \x02.+ - .*\x02( \([0-9]+ plays\)( \[.*\])?)?" # pyright: ignore [reportInvalidStringEscapeSequence] "\[\x0303last\.fm\x03\] [A-Za-z0-9_[\]{}\\|\-^]{1,$MAX} (is listening|last listened) to: \x02.+ - .*\x02( \([0-9]+ plays\)( \[.*\])?)?" # pyright: ignore [reportInvalidStringEscapeSequence]
) )
@ -20,11 +48,14 @@ servers: dict[str, dict[str, Any]] = {
"channels": {"#random": 0, "#dice": 0, "#offtopic": 0, "#main/replirc": 0}, "channels": {"#random": 0, "#dice": 0, "#offtopic": 0, "#main/replirc": 0},
"ignores": ["#main/replirc"], "ignores": ["#main/replirc"],
"hosts": ["9pfs.repl.co"], "hosts": ["9pfs.repl.co"],
"dnsblMode": "kickban",
}, },
"efnet": { "efnet": {
"address": "irc.underworld.no", "address": "irc.underworld.no",
"channels": {"#random": 0, "#dice": 0}, "channels": {"#random": 0, "#dice": 0},
"hosts": ["154.sub-174-251-241.myvzw.com"], "hosts": ["154.sub-174-251-241.myvzw.com"],
"threads": ["pingMon"],
"dnsblMode": "kickban",
}, },
"replirc": { "replirc": {
"address": "127.0.0.1", "address": "127.0.0.1",
@ -38,11 +69,16 @@ servers: dict[str, dict[str, Any]] = {
"#sshchat": 0, "#sshchat": 0,
"#firemc": 0, "#firemc": 0,
"#fp-radio": 0, "#fp-radio": 0,
"#fp-radio-debug": 0,
"#hardfork": 0,
"#opers": 0,
}, },
"ignores": ["#fp-radio"], "ignores": ["#fp-radio"],
"admins": ["h-tl"], "admins": ["h-tl"],
"hosts": ["owner.firepi"], "hosts": ["owner.firepi"],
"threads": ["radio"], "threads": ["radio"],
"autoMethod": "MARKOV",
"dnsblMode": "akill",
}, },
"backupbox": { "backupbox": {
"address": "127.0.0.1", "address": "127.0.0.1",
@ -55,6 +91,7 @@ servers: dict[str, dict[str, Any]] = {
"2600-6c5a-637f-1a85-0000-0000-0000-6667.inf6.spectrum.com", "2600-6c5a-637f-1a85-0000-0000-0000-6667.inf6.spectrum.com",
], ],
"onIdntCmds": ["OPER e e"], "onIdntCmds": ["OPER e e"],
"dnsbl-mode": "gline",
}, },
"twitch": { "twitch": {
"nick": "fireschatbot", "nick": "fireschatbot",
@ -64,9 +101,10 @@ servers: dict[str, dict[str, Any]] = {
"#firepup650": 0, "#firepup650": 0,
}, },
"admins": ["firepup650"], "admins": ["firepup650"],
"prefix": "!",
}, },
} }
admin_hosts: list[str] = ["firepup.firepi", "47.221.227.180"] admin_hosts: list[str] = ["firepup.firepi", "47.221.98.52"]
ESCAPE_SEQUENCE_RE = re.compile( ESCAPE_SEQUENCE_RE = re.compile(
r""" r"""
( \\U........ # 8-digit hex escapes ( \\U........ # 8-digit hex escapes
@ -90,6 +128,29 @@ def decode_escapes(s: str) -> str:
return ESCAPE_SEQUENCE_RE.sub(decode_match, s) return ESCAPE_SEQUENCE_RE.sub(decode_match, s)
def cmdFind(message: str, find: list, usePrefix: bool = True) -> bool:
cmd = message.split(" ")
if not cmd:
return False
if usePrefix:
for match in find:
sMatch = (prefix + match).split(" ")
try:
if all(cmd[i] == sMatch[i] for i in range(len(sMatch))):
return True
except IndexError:
...
else:
for match in find:
sMatch = match.split(" ")
try:
if all(cmd[i] == sMatch[i] for i in range(len(sMatch))):
return True
except IndexError:
...
return False
def mfind(message: str, find: list, usePrefix: bool = True) -> bool: def mfind(message: str, find: list, usePrefix: bool = True) -> bool:
if usePrefix: if usePrefix:
return any(message[: len(match) + 1] == prefix + match for match in find) return any(message[: len(match) + 1] == prefix + match for match in find)
@ -109,3 +170,76 @@ def sub(
if name: if name:
result = result.replace("$SENDER", name).replace("$NAME", name) result = result.replace("$SENDER", name).replace("$NAME", name)
return result return result
def dnsbl(hostname: str) -> tuple[str, dict[str, list[str]]]:
hosts = []
hstDT = {}
try:
hstDT = ipbl.check(hostname).detected_by
except ValueError: # It's not an IP
try:
hstDT = hsbl.check(hostname).detected_by
except ValueError: # It's also not a hostname
hstDT = {}
if hostname in hardbl:
hstDT["hardcoded"] = ["Known bad host"]
for host in hstDT:
if hstDT[host] != ["unknown"]:
hosts.append(host)
if not hosts:
return "", hstDT
hostStr = None
if len(hosts) >= 3:
hostStr = ", and ".join((", ".join(hosts)).rsplit(", ", 1))
else:
hostStr = " and ".join(hosts)
return hostStr, hstDT
def dnsblHandler(
bot: bare.bot, nick: str, hostname: str, chan: str
) -> tuple[str, dict[str, list[str]]]:
dnsblStatus = "Not enabled"
dnsblResps = {}
if bot.dnsblMode != "none":
dnsblStatus, dnsblResps = (
dnsbl(hostname)
if not hostname in bot.dns
else (bot.dns[hostname]["status"], bot.dns[hostname]["resps"])
)
bot.dns[hostname] = {"status": dnsblStatus, "resps": dnsblResps}
if dnsblStatus:
match bot.dnsblMode:
case "kickban":
bot.sendraw(
f"KICK {chan} {nick} :Sorry, but you're on the {dnsblStatus} blacklist(s)."
)
bot.sendraw(f"MODE {chan} +b *!*@{hostname}")
case "akill":
bot.sendraw(
f"OS AKILL ADD *@{hostname} !P Sorry, but you're on the {dnsblStatus} blacklist(s)."
)
case "kline":
bot.sendraw(
f"KILL {nick} :Sorry, but you're on the {dnsblStatus} blacklist(s)."
)
bot.sendraw(
f"KLINE 524160 *@{hostname} :Sorry, but you're on the {dnsblStatus} blacklist(s)."
)
bot.sendraw(
f"KLINE *@{hostname} :Sorry, but you're on the {dnsblStatus} blacklist(s)."
)
case "gline":
bot.sendraw(
f"KILL {nick} :Sorry, but you're on the {dnsblStatus} blacklist(s)."
)
bot.sendraw(
f"GLINE *@{hostname} 524160 :Sorry, but you're on the {dnsblStatus} blacklist(s)."
)
bot.sendraw(
f"GLINE *@{hostname} :Sorry, but you're on the {dnsblStatus} blacklist(s)."
)
case _:
bot.log(f'Unknown dnsbl Mode "{bot.dnsblMode}"!', "WARN")
return dnsblStatus, dnsblResps

View file

@ -3,7 +3,7 @@ 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 from threads import threadManager
def launch(server: str) -> None: def launch(server: str) -> None:

View file

@ -6,6 +6,7 @@ from typing import Union, Callable
from overrides import bytes, bbytes from overrides import bytes, bbytes
from importlib import reload from importlib import reload
import bare, re, checks import bare, re, checks
from traceback import format_exc
def CTCP(bot: bare.bot, msg: str) -> bool: def CTCP(bot: bare.bot, msg: str) -> bool:
@ -14,7 +15,7 @@ def CTCP(bot: bare.bot, msg: str) -> bool:
bot.log(f'Responding to CTCP "{kind}" from {sender}') bot.log(f'Responding to CTCP "{kind}" from {sender}')
if kind == "VERSION": if kind == "VERSION":
bot.notice( bot.notice(
f"\x01VERSION FireBot {conf.__version__} (https://git.amcforum.wiki/Firepup650/fire-ircbot)\x01", f"\x01VERSION FireBot {conf.__version__} (https://git.h.hackclub.app/Firepup650/FireBot)\x01",
sender, sender,
True, True,
) )
@ -24,7 +25,7 @@ def CTCP(bot: bare.bot, msg: str) -> bool:
return True return True
elif kind == "SOURCE": elif kind == "SOURCE":
bot.notice( bot.notice(
"\x01SOURCE https://git.amcforum.wiki/Firepup650/fire-ircbot\x01", "\x01SOURCE https://git.h.hackclub.app/Firepup650/FireBot\x01",
sender, sender,
True, True,
) )
@ -103,16 +104,34 @@ def PRIVMSG(bot: bare.bot, msg: str) -> Union[tuple[None, None], tuple[str, str]
triggers = [cmd] triggers = [cmd]
triggers.extend(cmds.data[cmd]["aliases"]) triggers.extend(cmds.data[cmd]["aliases"])
triggers = list(conf.sub(call, bot, chan, name).lower() for call in triggers) triggers = list(conf.sub(call, bot, chan, name).lower() for call in triggers)
if conf.mfind( if conf.cmdFind(
conf.sub(message, bot, chan, name).lower(), conf.sub(message, bot, chan, name).lower(),
triggers, triggers,
cmds.data[cmd]["prefix"], cmds.data[cmd]["prefix"],
): ):
if "check" in cmds.data[cmd] and cmds.data[cmd]["check"]: if "check" in cmds.data[cmd] and cmds.data[cmd]["check"]:
if cmds.data[cmd]["check"](bot, name, host, chan, cmd): if cmds.data[cmd]["check"](bot, name, host, chan, cmd):
cmds.call[cmd](bot, chan, name, message) try:
cmds.call[cmd](bot, chan, name, message)
except Exception:
Err = format_exc()
for line in Err.split("\n"):
bot.log(line, "ERROR")
bot.msg(
"Sorry, I had an error trying to execute that command. Please check error logs.",
chan,
)
else: else:
cmds.call[cmd](bot, chan, name, message) try:
cmds.call[cmd](bot, chan, name, message)
except Exception:
Err = format_exc()
for line in Err.split("\n"):
bot.log(line, "ERROR")
bot.msg(
"Sorry, I had an error trying to execute that command. Please check error logs.",
chan,
)
handled = True handled = True
break break
if not handled: if not handled:
@ -124,7 +143,7 @@ def PRIVMSG(bot: bare.bot, msg: str) -> Union[tuple[None, None], tuple[str, str]
cmds.call[check](bot, chan, name, message) cmds.call[check](bot, chan, name, message)
handled = True handled = True
break break
if not handled and conf.mfind(message, ["reload", "r"]): if not handled and conf.cmdFind(message, ["reload", "r"]):
if checks.admin(bot, name, host, chan, "reload"): if checks.admin(bot, name, host, chan, "reload"):
return "reload", chan return "reload", chan
handled = True handled = True
@ -139,13 +158,17 @@ def PRIVMSG(bot: bare.bot, msg: str) -> Union[tuple[None, None], tuple[str, str]
elif kind == "ACTION ducks": elif kind == "ACTION ducks":
bot.msg("\x01ACTION gets hit by a duck\x01", chan) bot.msg("\x01ACTION gets hit by a duck\x01", chan)
if chan in bot.channels and bot.channels[chan] >= bot.interval: if chan in bot.channels and bot.channels[chan] >= bot.interval:
r.seed() sel = ""
bot.channels[chan] = 0 bot.channels[chan] = 0
with open("mastermessages.txt", "r") as mm: if bot.autoMethod == "QUOTE":
sel = conf.decode_escapes( r.seed()
r.sample(mm.readlines(), 1)[0].replace("\\n", "").replace("\n", "") with open("mastermessages.txt", "r") as mm:
) sel = conf.decode_escapes(
bot.msg(f"[QUOTE] {sel}", chan) r.sample(mm.readlines(), 1)[0].replace("\\n", "").replace("\n", "")
)
else:
sel = bot.markov.generate_from_sentence(message)
bot.msg(f"[{bot.autoMethod}] {sel}", chan)
return None, None return None, None
@ -172,12 +195,46 @@ def PART(bot: bare.bot, msg: str) -> tuple[None, None]:
bot.channels.pop(channel, None) bot.channels.pop(channel, None)
return None, None return None, None
def QUIT(bot: bare.bot, msg: str) -> tuple[None, None]: def QUIT(bot: bare.bot, msg: str) -> tuple[None, None]:
if bot.server == "replirc": if bot.server == "replirc":
if msg.split("!", 1)[0][1:] == "FireMCBot": quitter = msg.split("!", 1)[0][1:]
if quitter == "FireMCbot":
bot.send("TOPIC #firemc :FireMC Relay channel (offline)\n") bot.send("TOPIC #firemc :FireMC Relay channel (offline)\n")
return None, None return None, None
def JOIN(bot: bare.bot, msg: str) -> tuple[None, None]:
nick = msg.split("!", 1)[0][1:]
hostname = msg.split("@", 1)[1].split(" ", 1)[0].strip()
chan = msg.split("#")[-1].strip()
conf.dnsblHandler(bot, nick, hostname, chan)
return None, None
def MODE(bot: bare.bot, msg: str) -> tuple[None, None]:
try:
chan = msg.split("#", 1)[1].split(" ", 1)[0]
add = True if msg.split("#", 1)[1].split(" ", 2)[1][0] == "+" else False
modes = msg.split("#", 1)[1].split(" ", 2)[1][1:]
users = ""
try:
users = msg.split("#", 1)[1].split(" ", 2)[2].split()
except IndexError:
...
if len(modes) != len(users):
bot.log("Refusing to handle modes that do not have corresponding users.")
return None, None
for i in range(len(modes)):
if users[i] == bot.nick:
if modes[i] == "o":
bot.ops[chan] = add
bot.log(f"{'Got' if add else 'Lost'} ops in {chan}")
except IndexError: # *our* modes are changing, not a channel
bot.log("Not handling changing of my modes")
return None, None
def NULL(bot: bare.bot, msg: str) -> tuple[None, None]: def NULL(bot: bare.bot, msg: str) -> tuple[None, None]:
return None, None return None, None
@ -189,7 +246,10 @@ handles: dict[
"NICK": NICK, "NICK": NICK,
"KICK": KICK, "KICK": KICK,
"PART": PART, "PART": PART,
"MODE": NULL, "MODE": MODE,
"TOPIC": NULL, "TOPIC": NULL,
"QUIT": QUIT "QUIT": QUIT,
"JOIN": JOIN,
"NOTICE": NULL,
"INVITE": NULL,
} }

View file

@ -112,7 +112,7 @@ def CTCPHandler(msg: str, sender: str = "", isRaw: bool = False):
log(f"Responding to CTCP {CTCP} from {sender}", server) log(f"Responding to CTCP {CTCP} from {sender}", server)
if CTCP == "VERSION": if CTCP == "VERSION":
notice( notice(
f"\x01VERSION FireBot {__version__} (https://git.amcforum.wiki/Firepup650/fire-ircbot)\x01", f"\x01VERSION FireBot {__version__} (https://git.h.hackclub.app/Firepup650/FireBot)\x01",
sender, sender,
True, True,
) )
@ -122,7 +122,7 @@ def CTCPHandler(msg: str, sender: str = "", isRaw: bool = False):
return True return True
elif CTCP == "SOURCE": elif CTCP == "SOURCE":
notice( notice(
"\x01SOURCE https://git.amcforum.wiki/Firepup650/fire-ircbot\x01", "\x01SOURCE https://git.h.hackclub.app/Firepup650/FireBot\x01",
sender, sender,
True, True,
) )
@ -304,7 +304,7 @@ def main():
False, False,
): ):
sendmsg( sendmsg(
f"Hi! I'm FireBot (https://git.amcforum.wiki/Firepup650/fire-ircbot)! My admins on this server are {adminnames}.", f"Hi! I'm FireBot (https://git.h.hackclub.app/Firepup650/FireBot)! My admins on this server are {adminnames}.",
chan, chan,
) )
if mfind( if mfind(

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", "FATAL"]: if level in ["EXIT", "CRASH", "FATAL", "ERROR"]:
stream = stderr stream = stderr
else: else:
stream = stdout stream = stdout

51
markov.py Normal file
View file

@ -0,0 +1,51 @@
import random
from typing import Union
class MarkovBot:
def __init__(self, text: list[list[str]]) -> None:
self.text = text
self.chains = {}
self.__build_chains()
def __build_chains(self) -> None:
for i in range(len(self.text)):
text = self.text[i]
for j in range(len(text) - 1):
current_word = text[j]
next_word = text[j + 1]
if current_word not in self.chains:
self.chains[current_word] = {}
if next_word not in self.chains[current_word]:
self.chains[current_word][next_word] = 0
self.chains[current_word][next_word] += 1
def generate_text(self, word: Union[str, None] = None) -> str:
if not word:
current_word = random.choice(list(self.chains.keys()))
else:
current_word = word
generated_text = current_word
while current_word in self.chains:
next_word = random.choices(
list(self.chains[current_word].keys()),
weights=list(self.chains[current_word].values()),
)[0]
generated_text += " " + next_word
current_word = next_word
return generated_text
def generate_from_sentence(self, msg: Union[str, None] = None) -> str:
if not msg:
word = random.choice(list(self.chains.keys()))
else:
word = random.choice(msg.split())
if (for_word := self.generate_text(word)) != word:
return for_word
else:
return self.generate_text()

File diff suppressed because it is too large Load diff

147
poetry.lock generated
View file

@ -38,6 +38,53 @@ files = [
certifi = "*" certifi = "*"
urllib3 = "*" urllib3 = "*"
[[package]]
name = "black"
version = "24.4.2"
description = "The uncompromising code formatter."
category = "main"
optional = false
python-versions = ">=3.8"
files = [
{file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"},
{file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"},
{file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"},
{file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"},
{file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"},
{file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"},
{file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"},
{file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"},
{file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"},
{file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"},
{file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"},
{file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"},
{file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"},
{file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"},
{file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"},
{file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"},
{file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"},
{file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"},
{file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"},
{file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"},
{file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"},
{file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"},
]
[package.dependencies]
click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
packaging = ">=22.0"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2024.2.2" version = "2024.2.2"
@ -50,16 +97,43 @@ files = [
{file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
] ]
[[package]]
name = "click"
version = "8.1.7"
description = "Composable command line interface toolkit"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]] [[package]]
name = "exceptiongroup" name = "exceptiongroup"
version = "1.2.0" version = "1.2.1"
description = "Backport of PEP 654 (exception groups)" description = "Backport of PEP 654 (exception groups)"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
{file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
] ]
[package.extras] [package.extras]
@ -136,6 +210,59 @@ files = [
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
] ]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
category = "main"
optional = false
python-versions = ">=3.5"
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
[[package]]
name = "packaging"
version = "24.0"
description = "Core utilities for Python packages"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
{file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
]
[[package]]
name = "pathspec"
version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths."
category = "main"
optional = false
python-versions = ">=3.8"
files = [
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
]
[[package]]
name = "platformdirs"
version = "4.2.1"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
category = "main"
optional = false
python-versions = ">=3.8"
files = [
{file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"},
{file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"},
]
[package.extras]
docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
type = ["mypy (>=1.8)"]
[[package]] [[package]]
name = "pylast" name = "pylast"
version = "5.2.0" version = "5.2.0"
@ -181,6 +308,18 @@ files = [
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
] ]
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.11.0" version = "4.11.0"
@ -214,4 +353,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 = "5f0106196ba3a316e887fe2748bb5ca19159f2905d7678393b8ee51430f9ca45" content-hash = "206264f4fe64babd8c2fecfc2051adc4e1c2971fe7e67eb5ff648ecae1095099"

View file

@ -9,6 +9,7 @@ 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" pylast = "^5.2.0"
black = "^24.4.2"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]

View file

@ -79,12 +79,24 @@ def radio(instance: bare.bot) -> NoReturn:
lastTrack = "" lastTrack = ""
complained = False complained = False
firstMiss = False firstMiss = False
misses = 0
missChunk = 0
missCap = -5
perChunk = 10
debug = False # instance.server == "replirc"
while 1: while 1:
try: try:
newTrack = instance.lastfmLink.get_user("Firepup650").get_now_playing() newTrack = instance.lastfmLink.get_user("Firepup650").get_now_playing()
if newTrack: if newTrack:
complained = False if complained:
firstMiss = False complained = False
misses = 0
missChunk = 0
elif misses > missCap:
missChunk += 1
if missChunk >= perChunk:
misses -= 1
missChunk = 0
thisTrack = newTrack.__str__() thisTrack = newTrack.__str__()
if thisTrack != lastTrack: if thisTrack != lastTrack:
lastTrack = thisTrack lastTrack = thisTrack
@ -93,8 +105,21 @@ def radio(instance: bare.bot) -> NoReturn:
f"TOPIC #fp-radio :Firepup radio ({thisTrack}) - https://open.spotify.com/playlist/4ctNy3O0rOwhhXIKyLvUZM" f"TOPIC #fp-radio :Firepup radio ({thisTrack}) - https://open.spotify.com/playlist/4ctNy3O0rOwhhXIKyLvUZM"
) )
elif not complained: elif not complained:
if not firstMiss: missChunk = 0
firstMiss = True if misses < 0:
misses += 1
if debug:
instance.msg(
str(
{
"misses": misses,
"missChunk": missChunk,
"misses exceed or meet limit": misses <= missCap,
}
),
"#fp-radio-debug",
)
sleep(2)
continue continue
instance.msg( instance.msg(
"Firepup seems to have stopped the music by mistake :/", "#fp-radio" "Firepup seems to have stopped the music by mistake :/", "#fp-radio"
@ -108,10 +133,27 @@ def radio(instance: bare.bot) -> NoReturn:
Err = format_exc() Err = format_exc()
for line in Err.split("\n"): for line in Err.split("\n"):
instance.log(line, "WARN") instance.log(line, "WARN")
if debug:
instance.msg(
str(
{
"misses": misses,
"missChunk": missChunk,
"misses exceed or meet limit": misses <= missCap,
}
),
"#fp-radio-debug",
)
sleep(2) sleep(2)
instance.log("Thread while loop broken", "FATAL") instance.log("Thread while loop broken", "FATAL")
exit(1) exit(1)
def ping(instance: bare.bot) -> NoReturn:
while 1:
instance.sendraw("PING :keepalive")
sleep(30)
data: dict[str, dict[str, Any]] = { data: dict[str, dict[str, Any]] = {
"radio": {"noWrap": True, "func": radio, "passInstance": True} "radio": {"noWrap": True, "func": radio, "passInstance": True},
"pingMon": {"noWrap": True, "func": ping, "passInstance": True}
} }