forked from aewens/babili-bot
Compare commits
36 Commits
enlightenm
...
master
Author | SHA1 | Date |
---|---|---|
aewens | b7d668cedf | |
aewens | 579f7df5a5 | |
aewens | 64d0208837 | |
aewens | 286c144bdf | |
Ben Harris | a20a675e25 | |
aewens | 1296a65b39 | |
aewens | 7a58cd789d | |
Robert Miles | 2e870c3796 | |
aewens | 7acbceca8e | |
aewens | 675a8099b5 | |
aewens | 7d44bfad19 | |
aewens | 5246008861 | |
aewens | f32dce7faf | |
aewens | 957160b686 | |
Robert Miles | 0d367b0a42 | |
aewens | a28241c718 | |
aewens | dc5d1ca756 | |
aewens | f45531d022 | |
aewens | 73eb573f83 | |
aewens | 721215b013 | |
aewens | 57419aa78a | |
aewens | b56de77a3f | |
aewens | 4e0978b534 | |
aewens | 3749f1d121 | |
aewens | 219baa47f6 | |
aewens | 2453aa1601 | |
aewens | d90d1a9567 | |
aewens | 97cdb98542 | |
aewens | 746ed85333 | |
aewens | e7424f690b | |
aewens | 626c0c4883 | |
aewens | 27ac672d6d | |
aewens | 31e4d6da67 | |
aewens | 39ca8287ea | |
aewens | cff5edd86b | |
aewens | 0cc70c0ee7 |
|
@ -1,5 +1,11 @@
|
|||
# App-specific
|
||||
settings.json
|
||||
settings.demo.json
|
||||
settings.test.json
|
||||
*.local
|
||||
data/*.json
|
||||
logs/*.log*
|
||||
logs/*.log.*
|
||||
irc/
|
||||
|
||||
# ---> Python
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) <year> <owner> . All rights reserved.
|
||||
Copyright (c) 2018 Austin Ewens. All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
from actions.botlist import botlist
|
||||
from actions.access import banish, pardon
|
||||
from actions.control import puppet, inject, nomad
|
||||
from actions.web import summon, whois, how_dare_you
|
||||
from actions.stupid import score_word, wordscore, wordscoreboard
|
||||
|
||||
actions = [
|
||||
{
|
||||
"type": "response",
|
||||
"pattern": "!botlist",
|
||||
"callback": botlist
|
||||
},
|
||||
{
|
||||
"type": "response",
|
||||
"pattern": "!rollcall",
|
||||
"callback": botlist
|
||||
},
|
||||
{
|
||||
"type": "response",
|
||||
"pattern": "/!summon \S+ .+/",
|
||||
"callback": summon
|
||||
},
|
||||
{
|
||||
"type": "response",
|
||||
"pattern": "/!summon \S+$/",
|
||||
"callback": how_dare_you
|
||||
},
|
||||
{
|
||||
"type": "response",
|
||||
"pattern": "/!banish \S+ .+/",
|
||||
"callback": banish
|
||||
},
|
||||
{
|
||||
"type": "response",
|
||||
"pattern": "/!pardon \S+/",
|
||||
"callback": pardon
|
||||
},
|
||||
{
|
||||
"type": "response",
|
||||
"pattern": "/!puppet \S+ .+/",
|
||||
"callback": puppet
|
||||
},
|
||||
{
|
||||
"type": "response",
|
||||
"pattern": "/!inject \S+/",
|
||||
"callback": inject
|
||||
},
|
||||
{
|
||||
"type": "response",
|
||||
"pattern": "/!nomad \S+ \S+/",
|
||||
"callback": nomad
|
||||
},
|
||||
{
|
||||
"type": "passive",
|
||||
"pattern": "/^[^!]*hm+/",
|
||||
"callback": score_word("hmm", "hm+")
|
||||
},
|
||||
{
|
||||
"type": "response",
|
||||
"pattern": "/!hmmscore(\s|$)/",
|
||||
"callback": wordscore("hmm")
|
||||
},
|
||||
{
|
||||
"type": "response",
|
||||
"pattern": "!hmmscoreboard",
|
||||
"callback": wordscoreboard("hmm")
|
||||
},
|
||||
{
|
||||
"type": "passive",
|
||||
"pattern": "/^[^!]*oo+f/",
|
||||
"callback": score_word("oof", "oo+f")
|
||||
},
|
||||
{
|
||||
"type": "response",
|
||||
"pattern": "/!oofscore(\s|$)/",
|
||||
"callback": wordscore("oof")
|
||||
},
|
||||
{
|
||||
"type": "response",
|
||||
"pattern": "!oofscoreboard",
|
||||
"callback": wordscoreboard("oof")
|
||||
},
|
||||
{
|
||||
"type": "response",
|
||||
"pattern": "/!whois \S+/",
|
||||
"callback": whois
|
||||
}
|
||||
]
|
|
@ -0,0 +1,39 @@
|
|||
from datetime import datetime
|
||||
|
||||
def banish(self, name, source, response):
|
||||
botnick = self.bot.botnick
|
||||
author = self.bot.author
|
||||
user, reason = response.split("!banish ")[1].split(" ", 1)
|
||||
|
||||
if name != author:
|
||||
return
|
||||
|
||||
if user not in self.bot.memories["users"]:
|
||||
self.bot.memories["users"][user] = dict()
|
||||
|
||||
self.bot.memories["users"][user]["blacklist"] = {
|
||||
"reason": reason.strip(),
|
||||
"when": datetime.now().timestamp()
|
||||
}
|
||||
|
||||
self.bot.thread(self.bot.save_memories)
|
||||
|
||||
confirmation = "{} has been banished for reason: {}".format(user, reason)
|
||||
self.bot.send_message(source, confirmation)
|
||||
|
||||
def pardon(self, name, source, response):
|
||||
botnick = self.bot.botnick
|
||||
author = self.bot.author
|
||||
user = response.split("!pardon ")[1].strip()
|
||||
|
||||
if name != author:
|
||||
return
|
||||
|
||||
user_memories = self.bot.memories["users"].get(user, dict())
|
||||
if user_memories.get("blacklist", None) is not None:
|
||||
del user_memories["blacklist"]
|
||||
|
||||
self.bot.thread(self.bot.save_memories)
|
||||
|
||||
confirmation = "{} has been pardoned".format(user)
|
||||
self.bot.send_message(source, confirmation)
|
|
@ -0,0 +1,18 @@
|
|||
def botlist(self, name, source, response):
|
||||
botnick = self.bot.botnick
|
||||
author = self.bot.author
|
||||
email = self.bot.settings["email"]
|
||||
about = "the meta chat bot"
|
||||
commands = ", ".join([
|
||||
"!botlist",
|
||||
"!rollcall",
|
||||
"!summon",
|
||||
"!banish",
|
||||
"!pardon",
|
||||
"!hmmscore",
|
||||
"!hmmscoreboard",
|
||||
"!whois"
|
||||
])
|
||||
args = (botnick, author, email, about, commands)
|
||||
message = "{} | {} <{}> | {} | {}".format(*args)
|
||||
self.bot.send_message(source, message)
|
|
@ -0,0 +1,41 @@
|
|||
def puppet(self, name, source, response):
|
||||
botnick = self.bot.botnick
|
||||
author = self.bot.author
|
||||
command = response.split("!puppet ")[1]
|
||||
mode, place, message = command.split(" ", 2)
|
||||
|
||||
if name != author:
|
||||
return
|
||||
|
||||
modes = {
|
||||
"say": self.bot.send_message,
|
||||
"act": self.bot.send_action
|
||||
}
|
||||
default = lambda _, msg: self.bot.send_message(source, "Invalid action!")
|
||||
modes.get(mode, default)(place, message)
|
||||
|
||||
def inject(self, name, source, response):
|
||||
botnick = self.bot.botnick
|
||||
author = self.bot.author
|
||||
command = response.split("!inject ")[1]
|
||||
|
||||
if name != author:
|
||||
return
|
||||
|
||||
self.bot.send(command)
|
||||
|
||||
def nomad(self, name, source, response):
|
||||
botnick = self.bot.botnick
|
||||
author = self.bot.author
|
||||
command = response.split("!nomad ")[1]
|
||||
action, place = command.split(" ", 1)
|
||||
|
||||
if name != author:
|
||||
return
|
||||
|
||||
actions = {
|
||||
"join": self.bot.join,
|
||||
"leave": self.bot.leave
|
||||
}
|
||||
default = lambda p: self.bot.send_message(source, "Invalid action!")
|
||||
actions.get(action, default)(place)
|
|
@ -0,0 +1,81 @@
|
|||
import re
|
||||
import operator
|
||||
|
||||
def capitalize(word):
|
||||
return word[0].upper() + word[1:]
|
||||
|
||||
def score_word(word, regex):
|
||||
def wording(self, name, source, response):
|
||||
check = response.lower().strip()
|
||||
|
||||
botnick = self.bot.botnick
|
||||
pattern = re.compile(regex)
|
||||
matches = re.findall(pattern, check)
|
||||
maximum = 10
|
||||
score = len(matches) if len(matches) <= maximum else maximum
|
||||
|
||||
if len(matches) > 1 and len("".join(re.split(pattern, check))) == 0:
|
||||
return
|
||||
|
||||
if name not in self.bot.memories["users"]:
|
||||
self.bot.memories["users"][name] = dict()
|
||||
|
||||
keyword = "{}score".format(word)
|
||||
|
||||
if keyword not in self.bot.memories["users"][name]:
|
||||
self.bot.memories["users"][name][keyword] = 0
|
||||
|
||||
current_score = self.bot.memories["users"][name][keyword]
|
||||
self.bot.memories["users"][name][keyword] = current_score + score
|
||||
|
||||
self.bot.thread(self.bot.save_memories)
|
||||
return wording
|
||||
|
||||
def wordscore(word):
|
||||
def scoring(self, name, source, response):
|
||||
botnick = self.bot.botnick
|
||||
score = 0
|
||||
score_format = "%s score for '{}': {}" % (capitalize(word))
|
||||
|
||||
if " " in response:
|
||||
name = response.split(" ", 1)[1].strip()
|
||||
|
||||
if name not in self.bot.memories["users"]:
|
||||
self.bot.send_message(source, score_format.format(name, score))
|
||||
return
|
||||
|
||||
keyword = "{}score".format(word)
|
||||
|
||||
if keyword not in self.bot.memories["users"][name]:
|
||||
self.bot.send_message(source, score_format.format(name, score))
|
||||
return
|
||||
|
||||
score = self.bot.memories["users"][name][keyword]
|
||||
self.bot.send_message(source, score_format.format(name, score))
|
||||
return scoring
|
||||
|
||||
def wordscoreboard(word):
|
||||
def scoreboard(self, name, source, response):
|
||||
botnick = self.bot.botnick
|
||||
scores = list()
|
||||
|
||||
for user, values in self.bot.memories["users"].items():
|
||||
scores.append({
|
||||
"name": user,
|
||||
"score": values.get("{}score".format(word), 0)
|
||||
})
|
||||
|
||||
size = 3
|
||||
start = -size
|
||||
|
||||
sort_scores = sorted(scores, key=lambda k: k["score"])
|
||||
top_scores = sort_scores[start:][::-1]
|
||||
|
||||
leaders = " | ".join([
|
||||
"{} {}".format(ts["name"], ts["score"]) for ts in top_scores
|
||||
])
|
||||
|
||||
response = "{} Score Leaderboard: {}".format(capitalize(word), leaders)
|
||||
|
||||
self.bot.send_message(source, response)
|
||||
return scoreboard
|
|
@ -0,0 +1,106 @@
|
|||
from subprocess import Popen, PIPE
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.parse import urlencode
|
||||
from urllib.error import HTTPError
|
||||
from json import loads
|
||||
|
||||
def get_iden(devices, device_name):
|
||||
for device in devices:
|
||||
if device.get("nickname", "") == device_name:
|
||||
return device.get("iden", "")
|
||||
|
||||
def push_note(bot, title, body):
|
||||
api_url = "https://api.pushbullet.com/v2"
|
||||
extra_settings = bot.settings.get("extras", dict())
|
||||
pb_settings = extra_settings.get("pushbullet", dict())
|
||||
api_key = pb_settings.get("api", "")
|
||||
device_name = pb_settings.get("device", "")
|
||||
|
||||
list_devices = Request("{}/devices".format(api_url))
|
||||
list_devices.add_header("Access-Token", api_key)
|
||||
|
||||
try:
|
||||
data = loads(urlopen(list_devices).read())
|
||||
except HTTPError:
|
||||
return
|
||||
|
||||
devices = data.get("devices", list())
|
||||
iden = get_iden(devices, device_name)
|
||||
|
||||
params = {
|
||||
"device_iden": iden,
|
||||
"type": "note",
|
||||
"title": title,
|
||||
"body": body
|
||||
}
|
||||
|
||||
post_params = urlencode(params).encode()
|
||||
|
||||
pushes = Request("{}/pushes".format(api_url), post_params)
|
||||
pushes.add_header("Access-Token", api_key)
|
||||
|
||||
try:
|
||||
response = loads(urlopen(pushes).read())
|
||||
except HTTPError as e:
|
||||
return
|
||||
|
||||
def summon(self, name, source, response):
|
||||
botnick = self.bot.botnick
|
||||
author = self.bot.author
|
||||
user, reason = response.split("!summon ")[1].split(" ", 1)
|
||||
|
||||
email = "{}@tilde.team"
|
||||
subject = "You have been summoned!"
|
||||
|
||||
text = " ".join([
|
||||
"My bot, {}, received a summoning request for you".format(botnick),
|
||||
"from {} in channel {} for reason: {}".format(name, source, reason)
|
||||
])
|
||||
message = MIMEText(text)
|
||||
|
||||
message["From"] = email.format(botnick)
|
||||
message["To"] = email.format(user)
|
||||
message["Subject"] = subject
|
||||
|
||||
command = "/usr/sbin/sendmail -t -oi".split(" ")
|
||||
p = Popen(command, stdin=PIPE, universal_newlines=True)
|
||||
p.communicate(message.as_string())
|
||||
|
||||
if user == author:
|
||||
push_note(self.bot, subject, text)
|
||||
|
||||
confirmation = "{}: You have summoned {}".format(name, user)
|
||||
self.bot.send_message(source, confirmation)
|
||||
|
||||
def how_dare_you(self, name, source, response):
|
||||
rude = "{}: You think you can just summon someone without a reason? Rude."
|
||||
self.bot.send_message(source, rude.format(name))
|
||||
|
||||
def whois(self, name, source, response):
|
||||
botnick = self.bot.botnick
|
||||
domain = response.split("!whois ")[1]
|
||||
|
||||
api_url = "https://api.jsonwhoisapi.com/v1/whois"
|
||||
api_key = self.bot.settings.get("extras", dict()).get("jsonwhoisapi", "")
|
||||
|
||||
req = Request("{}?identifier={}".format(api_url, domain))
|
||||
req.add_header("Authorization", api_key)
|
||||
|
||||
try:
|
||||
data = loads(urlopen(req).read())
|
||||
except HTTPError:
|
||||
self.bot.send_message(source, "{} cannot exist".format(domain))
|
||||
return
|
||||
|
||||
registered = data.get("registered", False)
|
||||
nameservers = len(data.get("nameservers", list())) > 0
|
||||
self.bot.logger.debug("WHOIS: {}".format(data))
|
||||
|
||||
if registered and nameservers:
|
||||
self.bot.send_message(source, "{} is '{}'".format(domain, "registered"))
|
||||
elif not (registered or nameservers):
|
||||
self.bot.send_message(source, "{} is '{}'".format(domain, "available"))
|
||||
else:
|
||||
self.bot.send_message(source, "{} might be available".format(domain))
|
115
app.py
115
app.py
|
@ -1,12 +1,115 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from bot import Bot
|
||||
from argparse import ArgumentParser
|
||||
from os.path import dirname, realpath
|
||||
|
||||
bot = Bot("127.0.0.1", 6667, "BabiliBot|py", ["#bots"])
|
||||
from bot import Bot, Tasks, Responses
|
||||
from actions import actions
|
||||
from coroutines import coroutines
|
||||
|
||||
def processor(name, source, response):
|
||||
#bot.send_message(source, "Got response")
|
||||
print(name, source, response)
|
||||
parser = ArgumentParser(description="A meta bot for ~team")
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
dest="config",
|
||||
default="settings.json",
|
||||
help="Load config file"
|
||||
)
|
||||
arguments = parser.parse_args()
|
||||
|
||||
bot = Bot("localhost", 6667)
|
||||
responses = Responses(bot)
|
||||
tasks = Tasks(bot)
|
||||
tasks.coroutines = coroutines
|
||||
|
||||
for action in actions:
|
||||
if "type" in action and "pattern" in action and "callback" in action:
|
||||
responses.add_trigger(
|
||||
action["type"],
|
||||
action["pattern"],
|
||||
action["callback"]
|
||||
)
|
||||
|
||||
def try_to_king_me(channel):
|
||||
bot.send_message("ChanServ", "REGISTER {}", channel)
|
||||
bot.send_message("ChanServ", "SET Successor {} {}", channel, bot.botnick)
|
||||
bot.send_message("ChanServ", "SET Founder {} {}", channel, bot.author)
|
||||
|
||||
def handle_pm(name, response):
|
||||
print("PM: {} - {}".format(name, response))
|
||||
|
||||
def handle_mode(channel, mode):
|
||||
if mode == "-r":
|
||||
try_to_king_me(channel)
|
||||
|
||||
def handle_invite(channel, name):
|
||||
changed = False
|
||||
kingme = bot.settings.get("extras", dict()).get("kingme", [])
|
||||
|
||||
if channel in kingme:
|
||||
try_to_king_me(channel)
|
||||
|
||||
users = bot.memories["users"]
|
||||
if name not in users:
|
||||
bot.memories["users"][name] = dict()
|
||||
changed = True
|
||||
|
||||
if "invites" not in users[name]:
|
||||
bot.memories["users"][name]["invites"] = list()
|
||||
changed = True
|
||||
|
||||
if channel not in bot.memories["users"][name]["invites"]:
|
||||
bot.memories["users"][name]["invites"].append(channel)
|
||||
changed = True
|
||||
|
||||
#if changed:
|
||||
# bot.thread(bot.save_memories)
|
||||
|
||||
def handle_kick(name, source):
|
||||
if source in bot.settings.get("extras", dict()).get("rejoin", list()):
|
||||
bot.join(source)
|
||||
users = bot.memories["users"]
|
||||
if name not in users:
|
||||
bot.memories["users"][name] = dict()
|
||||
|
||||
bot.memories["users"][name]["kicker"] = True
|
||||
#bot.thread(bot.save_memories)
|
||||
|
||||
def handle_message(name, source, response):
|
||||
responses.parse(name, source, response)
|
||||
if response == "!debug":
|
||||
bot.logger.debug(":: {}".format(bot.memories))
|
||||
|
||||
def handle_raw(message):
|
||||
if "KICK #chaos {} :".format(bot.author) in message:
|
||||
bot.send("INVITE {} :#chaos".format(bot.author))
|
||||
|
||||
def handle_crashed():
|
||||
bot.logger.debug("Rebooting")
|
||||
bot.crashed = True
|
||||
bot.tasks.stop()
|
||||
|
||||
tasks = Tasks(bot)
|
||||
tasks.coroutines = coroutines
|
||||
|
||||
bot.tasks = tasks
|
||||
bot.start(arguments.config, dirname(realpath(__file__)), {
|
||||
"pm": handle_pm,
|
||||
"mode": handle_mode,
|
||||
"invite": handle_invite,
|
||||
"kick": handle_kick,
|
||||
"crashed": handle_crashed,
|
||||
"message": handle_message
|
||||
})
|
||||
|
||||
if __name__ == "__main__":
|
||||
bot.start(processor, "settings.json")
|
||||
bot.tasks = tasks
|
||||
bot.start(arguments.config, dirname(realpath(__file__)), {
|
||||
"pm": handle_pm,
|
||||
"mode": handle_mode,
|
||||
"invite": handle_invite,
|
||||
"kick": handle_kick,
|
||||
"crashed": handle_crashed,
|
||||
"message": handle_message,
|
||||
"raw": handle_raw
|
||||
})
|
||||
|
|
22
bot.scm
22
bot.scm
|
@ -1,22 +0,0 @@
|
|||
(use tcp irregex ports srfi-13 srfi-14 srfi-69)
|
||||
|
||||
(define bot-nick "BabiliBot|scm")
|
||||
|
||||
(define (send out message)
|
||||
(format out (string-append message "\r\n")))
|
||||
|
||||
(let-values
|
||||
([(in out)
|
||||
(tcp-connect "127.0.0.1" 6667)])
|
||||
(send out
|
||||
(string-append
|
||||
"USER "
|
||||
(string-join (vector->list (make-vector 4 bot-nick)) " ")))
|
||||
(send out (string-append "NICK " bot-nick))
|
||||
(let loop
|
||||
([line (read-line in)])
|
||||
(if (eof-object? line)
|
||||
line
|
||||
(begin
|
||||
(print line)
|
||||
(loop (read-line in))))))
|
|
@ -1 +1,3 @@
|
|||
from bot.core import Bot
|
||||
from bot.core import Bot
|
||||
from bot.tasks import Tasks
|
||||
from bot.responses import Responses
|
346
bot/core.py
346
bot/core.py
|
@ -1,113 +1,290 @@
|
|||
import re
|
||||
import sys
|
||||
import ssl
|
||||
import time
|
||||
import json
|
||||
import socket
|
||||
import os.path
|
||||
import logging
|
||||
from threading import Thread
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="[%(levelname)s] [%(asctime)s] >> \n%(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
|
||||
class Bot:
|
||||
def __init__(self, server, port, botnick, channels):
|
||||
def __init__(self, server, port, secure=False):
|
||||
self.ircsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.logger = logging.getLogger("")
|
||||
self.server = server
|
||||
self.port = port
|
||||
self.botnick = botnick
|
||||
self.channels = channels
|
||||
self.secure = secure
|
||||
self.channels = []
|
||||
self.running = True
|
||||
self.crashed = False
|
||||
|
||||
self.users = dict()
|
||||
self.kickers = list()
|
||||
self.settings = dict()
|
||||
self.places = list()
|
||||
self.tasks = None
|
||||
self.author = ""
|
||||
self.botnick = ""
|
||||
|
||||
self.recv_size = 2048
|
||||
self.splitter = "\r\n"
|
||||
|
||||
if self.secure:
|
||||
self.ircsock = ssl.wrap_socket(self.ircsock)
|
||||
|
||||
def send(self, message, *args):
|
||||
response = message.format(*args) + "\n"
|
||||
password = self.settings.get("password", None)
|
||||
|
||||
if password is not None:
|
||||
self.logger.info(response.replace(password, "*" * len(password)))
|
||||
else:
|
||||
self.logger.info(response)
|
||||
|
||||
print("DEBUG: ", response)
|
||||
self.ircsock.send(response.encode())
|
||||
|
||||
def send_message(self, target, message, *args):
|
||||
msg = message.format(*args)
|
||||
response = "PRIVMSG {0} :{1}".format(target, msg) + "\n"
|
||||
password = self.settings.get("password", None)
|
||||
|
||||
if password is not None:
|
||||
self.logger.info(response.replace(password, "*" * len(password)))
|
||||
else:
|
||||
self.logger.info(response)
|
||||
|
||||
print("DEBUG: ", response)
|
||||
self.ircsock.send(response.encode())
|
||||
try:
|
||||
self.ircsock.send(response.encode())
|
||||
except BrokenPipeError:
|
||||
self.stop()
|
||||
|
||||
def send_action(self, target, message, *args):
|
||||
self.send_message(target, "\001ACTION {}\001".format(message), *args)
|
||||
|
||||
def join(self, chan):
|
||||
self.send("JOIN {}", chan)
|
||||
self.places.append(chan)
|
||||
|
||||
message = ""
|
||||
magic_string = "End of /NAMES list."
|
||||
banned = "Cannot join channel (You're banned)"
|
||||
start = time.time()
|
||||
while magic_string not in message:
|
||||
message = self.ircsock.recv(self.recv_size).decode()
|
||||
message = message.strip("\n\r")
|
||||
print(message)
|
||||
# Taking too long, escaping JOIN request
|
||||
if time.time() - start == 2000:
|
||||
return
|
||||
try:
|
||||
message = self.ircsock.recv(self.recv_size).decode()
|
||||
if banned in message:
|
||||
self.places.remove(chan)
|
||||
return
|
||||
# message = message.strip(self.splitter)
|
||||
#self.logger.debug(message)
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
|
||||
user_list = "= {} :".format(chan)
|
||||
raw_users = message.split(user_list)[1].split(" \r\n")[0].split(" ")
|
||||
prefix_filter = lambda u: u[1:] if "~" in u or "@" in u else u
|
||||
users = list(filter(prefix_filter, raw_users))
|
||||
list_pattern = re.compile("[@=] {} :".format(chan))
|
||||
user_listing = re.split(list_pattern, message)
|
||||
if len(user_listing) < 2:
|
||||
print("DEBUG: Skipping adding users from {}".format(chan))
|
||||
return
|
||||
|
||||
splitter = " {}".format(self.splitter)
|
||||
raw_users = user_listing[1].split(splitter)[0].split(" ")
|
||||
users = list(filter(self.parse_name, raw_users))
|
||||
remember = self.memories["users"]
|
||||
for user in users:
|
||||
if user not in self.users:
|
||||
self.users[user] = dict()
|
||||
user = self.parse_name(user)
|
||||
|
||||
if user not in remember:
|
||||
self.memories["users"][user] = dict()
|
||||
|
||||
def leave(self, chan):
|
||||
message = "PART {} :Bye-bye!"
|
||||
self.send(message, chan)
|
||||
if chan in self.places:
|
||||
self.places.remove(chan)
|
||||
|
||||
def ping(self, message):
|
||||
response = message.split("PING :")[1]
|
||||
self.send("PONG :{0}", response)
|
||||
|
||||
def get_name(self, text):
|
||||
return text.split("!", 1)[0][1:]
|
||||
|
||||
def parse_name(self, name):
|
||||
if len(name) == 0 or name is None:
|
||||
return name
|
||||
if name[0] in ["~", "@", "+", "%"]:
|
||||
return name[1:]
|
||||
else:
|
||||
return name
|
||||
|
||||
def parse(self, message):
|
||||
before, after = message.split("PRIVMSG ", 1)
|
||||
name = before.split("!", 1)[0][1:]
|
||||
name = self.parse_name(self.get_name(before))
|
||||
source, response = after.split(" :", 1)
|
||||
return name, source, response
|
||||
|
||||
def track_rename(self, message):
|
||||
before, new_name = message.split("NICK ", 1)
|
||||
name = before.split("!", 1)[0][1:]
|
||||
user = self.users[name]
|
||||
del self.users[name]
|
||||
self.users[new_name] = user
|
||||
def handle_mode(self, message):
|
||||
before, after = message.split("MODE ", 1)
|
||||
name = self.parse_name(self.get_name(before))
|
||||
channel, mode = after.split(" ")[:2]
|
||||
return channel, mode
|
||||
|
||||
def answer_invite(self, message):
|
||||
def handle_rename(self, message):
|
||||
before, new_name = message.split("NICK ", 1)
|
||||
name = self.get_name(before)
|
||||
|
||||
new_name = self.parse_name(new_name)
|
||||
name = self.parse_name(name)
|
||||
|
||||
user = self.memories["users"][name]
|
||||
self.memories["users"][new_name] = user
|
||||
del self.memories["users"][name]
|
||||
return user, new_name
|
||||
|
||||
def handle_invite(self, message):
|
||||
before, after = message.split("INVITE ", 1)
|
||||
name = self.parse_name(self.get_name(before))
|
||||
channel = after.split(":", 1)[1]
|
||||
self.join(channel)
|
||||
return channel, name
|
||||
|
||||
def log_kick(self, message):
|
||||
# :aewens!aewens@rightful.heir.to.chaos KICK #insane BabiliBot|py :aewens
|
||||
regex = "KICK #\S+ {} :".format(self.botnick)
|
||||
before, kicker = re.split(regex, message)
|
||||
self.kickers.append(kicker)
|
||||
def handle_kick(self, message):
|
||||
before, after = message.split("KICK ", 1)
|
||||
source = after.split(" ", 1)[0]
|
||||
name = self.parse_name(self.get_name(before))
|
||||
return name, source
|
||||
|
||||
def handle_join(self, message):
|
||||
before, after = message.split("JOIN ", 1)
|
||||
user = self.parse_name(self.get_name(before))
|
||||
|
||||
if user not in self.memories["users"]:
|
||||
self.memories["users"][user] = dict()
|
||||
|
||||
return user
|
||||
|
||||
def handle_part(self, message):
|
||||
before, after = message.split("PART ", 1)
|
||||
user = self.parse_name(self.get_name(before))
|
||||
return user
|
||||
|
||||
def load_memories(self, location):
|
||||
path = "{}/{}".format(self.location, location)
|
||||
self.memories_path = path
|
||||
|
||||
if not os.path.isfile(path):
|
||||
self.memories = {
|
||||
"users": dict()
|
||||
}
|
||||
else:
|
||||
with open(path, "r") as f:
|
||||
self.memories = json.loads(f.read())
|
||||
|
||||
def thread(self, fn, *args):
|
||||
t = Thread(target=fn, args=args)
|
||||
t.start()
|
||||
|
||||
def save_memories(self):
|
||||
with open(self.memories_path, "w") as f:
|
||||
try:
|
||||
f.write(json.dumps(self.memories))
|
||||
except ValueError as e:
|
||||
f.write("")
|
||||
|
||||
def load_settings(self, location):
|
||||
with open(location, "r") as f:
|
||||
set_vars = [
|
||||
"author",
|
||||
"botnick",
|
||||
"channels"
|
||||
]
|
||||
|
||||
path = "{}/{}".format(self.location, location)
|
||||
with open(path, "r") as f:
|
||||
self.settings = json.loads(f.read())
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
self.send("QUIT")
|
||||
for name, attr in self.settings.items():
|
||||
if name in set_vars:
|
||||
setattr(self, name, attr)
|
||||
|
||||
def start(self, callback, settings):
|
||||
def stop(self):
|
||||
self.send("QUIT :Overheating, powering down")
|
||||
self.ircsock.close()
|
||||
self.running = False
|
||||
|
||||
def start(self, config, location, callback):
|
||||
message = ""
|
||||
registered = False
|
||||
confirmed = True
|
||||
|
||||
self.ircsock.connect((self.server, self.port))
|
||||
self.location = location
|
||||
self.load_settings(config)
|
||||
self.load_memories("data/memories.json")
|
||||
|
||||
logfile = "{}/logs/{}.log".format(self.location, self.botnick)
|
||||
logfmt = "[%(levelname)s] [%(asctime)s] >> \n%(message)s"
|
||||
datefmt = "%Y-%m-%d %H:%M:%S"
|
||||
logger = TimedRotatingFileHandler(logfile, "midnight", 1)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.setFormatter(logging.Formatter(logfmt, datefmt))
|
||||
self.logger.addHandler(logger)
|
||||
|
||||
try:
|
||||
self.ircsock.connect((self.server, self.port))
|
||||
except ConnectionRefusedError:
|
||||
sys.exit(1)
|
||||
self.send("USER {0} {0} {0} {0}", self.botnick)
|
||||
self.send("NICK {0}", self.botnick)
|
||||
|
||||
self.load_settings(settings)
|
||||
|
||||
password = self.settings["password"] or ""
|
||||
confirm = self.settings["confirm"] or ""
|
||||
email = self.settings["email"] or ""
|
||||
|
||||
magic_string = "MODE {} +r".format(self.botnick)
|
||||
while magic_string not in message:
|
||||
message = self.ircsock.recv(self.recv_size).decode()
|
||||
message = message.strip("\n\r")
|
||||
print(message)
|
||||
if not registered and "Password accepted" in message:
|
||||
magic_phrase = {
|
||||
"has_registered": "Password accepted",
|
||||
"needs_to_register": "choose a different nick",
|
||||
"needs_to_confirm": "Your account will expire",
|
||||
"not_registered": "Your nickname is not registered"
|
||||
#"ready_to_id": "is now your displayed host",
|
||||
#"nickserv_missing": "No such nick/channel"
|
||||
}
|
||||
authenticate = len(password) > 0 and len(confirm) > 0
|
||||
magic_string = "MODE {} :+r".format(self.botnick)
|
||||
while magic_string not in message and authenticate:
|
||||
try:
|
||||
message = self.ircsock.recv(self.recv_size).decode()
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
|
||||
message = message.strip(self.splitter)
|
||||
#self.logger.debug(message)
|
||||
if not registered and magic_phrase["has_registered"] in message:
|
||||
registered = True
|
||||
if not registered and "choose a different nick" in message:
|
||||
if not registered and magic_phrase["needs_to_register"] in message:
|
||||
self.send_message("NickServ", "IDENTIFY {}", password)
|
||||
if not confirmed and "Your account will expire" in message:
|
||||
if not confirmed and magic_phrase["needs_to_confirm"] in message:
|
||||
self.send_message("NickServ", "CONFIRM {}", self.confirm)
|
||||
confirmed = True
|
||||
if not registered and magic_phrase["not_registered"] in message:
|
||||
break
|
||||
#if not registered and magic_phrase["ready_to_id"] in message:
|
||||
# self.send_message("NickServ", "IDENTIFY {}", password)
|
||||
#if not registered and magic_phrase["nickserv_missing"] in message:
|
||||
# break
|
||||
if not authenticate:
|
||||
time.sleep(3)
|
||||
|
||||
self.send("MODE {} +B".format(self.botnick))
|
||||
|
||||
print("DEBUG: Joining")
|
||||
|
||||
|
@ -116,17 +293,72 @@ class Bot:
|
|||
|
||||
print("DEBUG: Joined")
|
||||
|
||||
while self.running:
|
||||
message = self.ircsock.recv(self.recv_size).decode()
|
||||
message = message.strip("\n\r")
|
||||
print(message)
|
||||
if self.tasks is not None:
|
||||
if getattr(self.tasks, "run", None) is not None:
|
||||
self.tasks.run()
|
||||
|
||||
if "PING :" in message:
|
||||
self.ping(message)
|
||||
elif "NICK " in message:
|
||||
self.track_rename(message)
|
||||
elif "INVITE " in message:
|
||||
self.answer_invite(message)
|
||||
elif "PRIVMSG" in message:
|
||||
name, source, response = self.parse(message)
|
||||
callback(name, source, response)
|
||||
while self.running:
|
||||
_message = ""
|
||||
while self.splitter not in _message:
|
||||
try:
|
||||
_message = self.ircsock.recv(self.recv_size).decode()
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
|
||||
if "raw" in callback:
|
||||
callback["raw"](_message)
|
||||
|
||||
_message = _message.strip(self.splitter)
|
||||
messages = [msg for msg in _message.split(self.splitter) if msg]
|
||||
|
||||
for message in messages:
|
||||
if message[:4] == "PING":
|
||||
self.ping(message)
|
||||
if "ping" in callback:
|
||||
callback["ping"]()
|
||||
elif "PRIVMSG " in message:
|
||||
name, source, response = self.parse(message)
|
||||
self.logger.debug(message)
|
||||
if source == self.botnick and "pm" in callback:
|
||||
callback["pm"](name, response)
|
||||
elif "message" in callback:
|
||||
callback["message"](name, source, response)
|
||||
elif "MODE " in message:
|
||||
channel, mode = self.handle_mode(message)
|
||||
self.logger.debug(message)
|
||||
if "mode" in callback:
|
||||
callback["mode"](channel, mode)
|
||||
elif "NICK " in message:
|
||||
old_name, new_name = self.handle_rename(message)
|
||||
self.logger.debug(message)
|
||||
if "nick" in callback:
|
||||
callback["nick"](old_name, new_name)
|
||||
#elif "KICK " in message:
|
||||
# kicker, source = self.handle_kick(message)
|
||||
# self.logger.debug(message)
|
||||
# if "kick" in callback:
|
||||
# callback["kick"](kicker, source)
|
||||
elif "JOIN " in message:
|
||||
user = self.handle_join(message)
|
||||
self.logger.debug(message)
|
||||
if "join" in callback:
|
||||
callback["join"](user)
|
||||
elif "PART " in message:
|
||||
user = self.handle_part(message)
|
||||
self.logger.debug(message)
|
||||
if "part" in callback:
|
||||
callback["part"](user)
|
||||
elif "INVITE " in message:
|
||||
channel, name = self.handle_invite(message)
|
||||
self.logger.debug(message)
|
||||
if "invite" in callback:
|
||||
callback["invite"](channel, name)
|
||||
elif "unhandled" in callback:
|
||||
if "unhandled" in callback:
|
||||
callback["unhandled"](message)
|
||||
elif ":Closing link:" in message:
|
||||
self.logger.warning(message)
|
||||
self.logger.error("Activing crash mode")
|
||||
if "crashed" in callback:
|
||||
callback["crashed"]()
|
||||
break
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
import re
|
||||
from datetime import datetime
|
||||
|
||||
class Responses:
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.triggers = {
|
||||
"name": dict(),
|
||||
"source": dict(),
|
||||
"response": dict(),
|
||||
"passive": dict()
|
||||
}
|
||||
|
||||
def add_trigger(self, trigger_type, pattern, callback):
|
||||
if trigger_type in self.triggers:
|
||||
self.triggers[trigger_type][pattern] = callback
|
||||
|
||||
def allowed(self, name, source):
|
||||
memories = self.bot.memories
|
||||
users = memories["users"]
|
||||
if name not in users:
|
||||
return False
|
||||
|
||||
last_response = 0
|
||||
if "last_response" in self.bot.memories["users"][name]:
|
||||
last_response = self.bot.memories["users"][name]["last_response"]
|
||||
|
||||
now = datetime.now().timestamp()
|
||||
author = self.bot.author
|
||||
wait = 1
|
||||
|
||||
if name != author and last_response > 0 and now - last_response < wait:
|
||||
reason = "Auto-banished"
|
||||
self.bot.memories["users"][name]["blacklist"] = {
|
||||
"reason": reason,
|
||||
"when": now
|
||||
}
|
||||
message = "is ignoring {} for reason '{}'".format(name, reason)
|
||||
self.bot.send_action(source, message)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def log(self, name, trigger):
|
||||
if trigger != "response":
|
||||
return
|
||||
now = datetime.now().timestamp()
|
||||
self.bot.memories["users"][name]["last_response"] = now
|
||||
|
||||
def parse(self, name, source, response):
|
||||
users = self.bot.memories["users"]
|
||||
if name not in users:
|
||||
return False
|
||||
|
||||
check = response.lower().strip()
|
||||
trig = {
|
||||
"name": name,
|
||||
"source": source,
|
||||
"response": response.lower().strip(),
|
||||
"passive": response.lower().strip()
|
||||
}
|
||||
|
||||
for trigger in list(self.triggers.keys()):
|
||||
for pattern, callback in self.triggers[trigger].items():
|
||||
if pattern[0] != "/" and pattern[-1] != "/":
|
||||
if pattern == check:
|
||||
if self.allowed(name, source):
|
||||
self.log(name, trigger)
|
||||
callback(self, name, source, response)
|
||||
elif "blacklist" in users[name]:
|
||||
reason = users[name]["blacklist"]["reason"]
|
||||
message = "You were banished for reason '{}'"
|
||||
message = message.format(reason)
|
||||
#self.bot.send_message(name, message)
|
||||
return False
|
||||
elif trigger != "passive":
|
||||
regex = re.compile(pattern[1:-1])
|
||||
if regex.match(trig[trigger]) is not None:
|
||||
if self.allowed(name, source):
|
||||
self.log(name, trigger)
|
||||
callback(self, name, source, response)
|
||||
elif "blacklist" in users[name]:
|
||||
reason = users[name]["blacklist"]["reason"]
|
||||
message = "You were banished for reason '{}'"
|
||||
message = message.format(reason)
|
||||
#self.bot.send_message(name, message)
|
||||
return False
|
||||
else:
|
||||
regex = re.compile(pattern[1:-1])
|
||||
if regex.match(trig[trigger]) is not None:
|
||||
callback(self, name, source, response)
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import time
|
||||
import sched
|
||||
from threading import Thread
|
||||
|
||||
class Tasks:
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.scheduler = sched.scheduler(time.time, time.sleep)
|
||||
self.thread = Thread(target=self.worker, args=(self,))
|
||||
self.coroutines = list()
|
||||
self.states = dict()
|
||||
self.halt = False
|
||||
|
||||
def periodic(self, scheduler, interval, action, index, state=dict()):
|
||||
if self.halt:
|
||||
return
|
||||
|
||||
self.states[index] = action(state)
|
||||
scheduler.enter(interval, 1, self.periodic, (
|
||||
scheduler, interval, action, index, self.states[index]
|
||||
))
|
||||
|
||||
def worker(self, tasks):
|
||||
for c, coro in enumerate(tasks.coroutines):
|
||||
interval = coro["interval"]
|
||||
worker = coro["worker"]
|
||||
state = coro.get("state", dict())
|
||||
state["bot"] = tasks.bot
|
||||
tasks.periodic(tasks.scheduler, interval, worker, c, state)
|
||||
tasks.scheduler.run()
|
||||
|
||||
def add_coroutine(self, worker, interval, state=dict()):
|
||||
self.coroutines.append({
|
||||
"worker": worker,
|
||||
"interval": interval,
|
||||
"state": state
|
||||
})
|
||||
|
||||
def stop(self):
|
||||
self.halt = True
|
||||
list(map(self.scheduler.cancel, self.scheduler.queue))
|
||||
for key, value in self.states.items():
|
||||
self.states[key] = False
|
||||
self.thread.join()
|
||||
|
||||
def run(self):
|
||||
self.thread.daemon = True
|
||||
self.thread.start()
|
|
@ -0,0 +1,44 @@
|
|||
from coroutines.bbj import BBJ
|
||||
from coroutines.rss import RSS
|
||||
|
||||
# {
|
||||
# "worker": test,
|
||||
# "interval": 3
|
||||
# }
|
||||
# def test(bot):
|
||||
# print("Testing {}".format(bot.botnick))
|
||||
|
||||
def use(cls):
|
||||
return lambda state: cls(state).start()
|
||||
|
||||
coroutines = [
|
||||
{
|
||||
"worker": use(BBJ),
|
||||
"interval": 5,
|
||||
"state": {
|
||||
"alias": "bbj",
|
||||
"source": "http://localhost:7099/api",
|
||||
"channels": ["#team", "#tildeverse"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"worker": use(RSS),
|
||||
"interval": 6,
|
||||
"state": {
|
||||
"alias": "links",
|
||||
"source": "https://tilde.news/newest.rss",
|
||||
"use": "title",
|
||||
"channels": ["#meta", "#tildeverse"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"worker": use(RSS),
|
||||
"interval": 8,
|
||||
"state": {
|
||||
"alias": "links-comments",
|
||||
"source": "https://tilde.news/comments.rss",
|
||||
"use": "description",
|
||||
"channels": ["#tildeverse"]
|
||||
}
|
||||
}
|
||||
]
|
|
@ -0,0 +1,89 @@
|
|||
from urllib.request import Request, urlopen
|
||||
from urllib.parse import urlencode
|
||||
from urllib.error import HTTPError
|
||||
from datetime import datetime
|
||||
from json import loads, dumps
|
||||
from re import sub
|
||||
|
||||
class BBJ:
|
||||
def __init__(self, state):
|
||||
self.name = "BBJ"
|
||||
self.bot = state["bot"]
|
||||
self.alias = state["alias"]
|
||||
self.source = state["source"]
|
||||
self.channels = state["channels"]
|
||||
self.memory = state.get("memory", {
|
||||
"initialized": False,
|
||||
"known": dict()
|
||||
})
|
||||
|
||||
def start(self):
|
||||
if not self.memory["initialized"]:
|
||||
self.memory["initialized"] = True
|
||||
self.fetch(self.cache)
|
||||
return self.run()
|
||||
|
||||
def run(self):
|
||||
self.fetch(self.mirror)
|
||||
return {
|
||||
"bot": self.bot,
|
||||
"alias": self.alias,
|
||||
"source": self.source,
|
||||
"channels": self.channels,
|
||||
"memory": self.memory
|
||||
}
|
||||
|
||||
def cache(self, item):
|
||||
self.memory["known"][item["thread_id"]] = item["last_mod"]
|
||||
|
||||
def process_thread(self, thread_id, thread):
|
||||
data = thread.get("data", dict())
|
||||
title = data.get("title", "")
|
||||
replies = data.get("reply_count", "")
|
||||
messages = data.get("messages", "")
|
||||
usermap = thread.get("usermap", dict())
|
||||
reply = messages[replies]
|
||||
author = reply.get("author", "")
|
||||
username = usermap[author].get("user_name", "")
|
||||
body = reply.get("body", "")
|
||||
body = sub(r">>\d\n\n", r"", body)
|
||||
body = sub(r"\n", r" ", body)
|
||||
php = "https://bbj.tildeverse.org/"
|
||||
link = "{}?thread_id={}".format(php, thread_id)
|
||||
for channel in self.channels:
|
||||
response = "'{}' ({}) : {} <{}>".format(title, username, body, link)
|
||||
message = "[{}] {}".format(self.alias, response)
|
||||
self.bot.send_message(channel, message)
|
||||
|
||||
def get_thread(self, thread_id, callback):
|
||||
params = {
|
||||
"thread_id": thread_id
|
||||
}
|
||||
post_params = str(dumps(params)).encode()
|
||||
thread_load = Request("{}/thread_load".format(self.source), post_params)
|
||||
thread_load.add_header("Content-Type", "application/json")
|
||||
|
||||
try:
|
||||
response = callback(thread_id, loads(urlopen(thread_load).read()))
|
||||
except HTTPError:
|
||||
return
|
||||
|
||||
def mirror(self, item):
|
||||
thread_id = item["thread_id"]
|
||||
last_mod = self.memory["known"][thread_id]
|
||||
if last_mod == item["last_mod"]:
|
||||
return
|
||||
|
||||
self.memory["known"][thread_id] = item["last_mod"]
|
||||
self.get_thread(thread_id, self.process_thread)
|
||||
|
||||
def fetch(self, callback):
|
||||
thread_index = Request("{}/thread_index".format(self.source))
|
||||
|
||||
try:
|
||||
response = loads(urlopen(thread_index).read())
|
||||
threads = response.get("data", dict())
|
||||
for thread in threads:
|
||||
callback(thread)
|
||||
except HTTPError:
|
||||
return
|
|
@ -0,0 +1,77 @@
|
|||
from xml.etree import ElementTree as etree
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.error import HTTPError, URLError
|
||||
from json import loads, dumps
|
||||
from re import sub
|
||||
|
||||
class RSS:
|
||||
def __init__(self, state):
|
||||
self.name = "RSS"
|
||||
self.bot = state["bot"]
|
||||
self.alias = state["alias"]
|
||||
self.use = state["use"]
|
||||
self.source = state["source"]
|
||||
self.channels = state["channels"]
|
||||
self.memory = state.get("memory", {
|
||||
"initialized": False,
|
||||
"known": list()
|
||||
})
|
||||
|
||||
def start(self):
|
||||
if not self.memory["initialized"]:
|
||||
self.memory["initialized"] = True
|
||||
self.fetch(self.cache)
|
||||
return self.run()
|
||||
|
||||
def run(self):
|
||||
self.fetch(self.mirror)
|
||||
return {
|
||||
"bot": self.bot,
|
||||
"alias": self.alias,
|
||||
"use": self.use,
|
||||
"source": self.source,
|
||||
"channels": self.channels,
|
||||
"memory": self.memory
|
||||
}
|
||||
|
||||
def cache(self, item):
|
||||
guid = item.findtext("guid", None)
|
||||
if guid is not None:
|
||||
self.memory["known"].append(guid)
|
||||
|
||||
def mirror(self, item):
|
||||
guid = item.findtext("guid", None)
|
||||
if guid is None:
|
||||
return
|
||||
|
||||
if guid in self.memory["known"]:
|
||||
return
|
||||
|
||||
self.memory["known"].append(guid)
|
||||
|
||||
use = sub(r"(<\/?[^>]+>)|\n", "", item.findtext(self.use, ""))
|
||||
user = item.findtext("author", "").split("@")[0]
|
||||
metadata = "(posted by {}) <{}>".format(user, guid)
|
||||
header = "[{}] {}".format(self.alias, use)
|
||||
splitter = " "
|
||||
max_size = 450 - len(splitter)
|
||||
if len(header) + len(metadata) >= max_size:
|
||||
header_size = max_size - len(metadata)
|
||||
header = header[:header_size]
|
||||
response = "{}{}{}".format(header, splitter, metadata)
|
||||
for channel in self.channels:
|
||||
self.bot.send_message(channel, response)
|
||||
|
||||
def fetch(self, callback):
|
||||
req = Request(self.source)
|
||||
try:
|
||||
response = urlopen(req).read()
|
||||
except HTTPError:
|
||||
return
|
||||
except URLError:
|
||||
return
|
||||
|
||||
feed = etree.fromstring(response)
|
||||
items = feed.findall("channel/item")
|
||||
for item in items:
|
||||
callback(item)
|
|
@ -1,5 +1,9 @@
|
|||
{
|
||||
"botnick": "",
|
||||
"password": "",
|
||||
"email": "",
|
||||
"confirm": ""
|
||||
}
|
||||
"confirm": "",
|
||||
"author": "",
|
||||
"channels": [],
|
||||
"extras": {}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue