Added year and name into LICENSE, added in most of babili's actions

This commit is contained in:
aewens 2018-09-13 15:37:32 -04:00
parent 27ac672d6d
commit 626c0c4883
13 changed files with 331 additions and 31 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
# App-specific
settings.json
data/*.json
irc/
# ---> Python

View File

@ -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.

View File

@ -1,4 +1,8 @@
from actions.botlist import botlist
from actions.summon import summon
from actions.access import banish, pardon
from actions.control import puppet, nomad
from actions.stupid import hmm, hmmscore, hmmscoreboard
actions = [
{
@ -10,5 +14,45 @@ actions = [
"type": "response",
"pattern": "!rollcall",
"callback": botlist
},
{
"type": "response",
"pattern": ";;!summon \S+ .+",
"callback": summon
},
{
"type": "response",
"pattern": ";;!banish \S+ .+",
"callback": banish
},
{
"type": "response",
"pattern": ";;!pardon \S+",
"callback": pardon
},
{
"type": "response",
"pattern": ";;!puppet [^|]+\|.+",
"callback": puppet
},
{
"type": "response",
"pattern": ";;!nomad \S+ \S+",
"callback": nomad
},
{
"type": "response",
"pattern": ";;hm+",
"callback": hmm
},
{
"type": "response",
"pattern": ";;!hmmscore",
"callback": hmmscore
},
{
"type": "response",
"pattern": "!hmmscoreboard",
"callback": hmmscoreboard
}
]

37
actions/access.py Normal file
View File

@ -0,0 +1,37 @@
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,
"when": datetime.now().timestamp()
}
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]
if name != author:
return
del self.bot.memories["users"][user]["blacklist"]
self.bot.save_memories()
confirmation = "{} has been pardoned".format(user)
self.bot.send_message(source, confirmation)

View File

@ -1,12 +1,15 @@
def botlist(self, name, source, response):
name = self.bot.botnick
botnick = self.bot.botnick
author = self.bot.author
email = self.bot.settings["email"]
about = "the meta chat bot"
commands = ", ".join([
"!botlist",
"!rollcall"
"!rollcall",
"!summon",
"!banish",
"!pardon"
])
args = (name, author, email, about, commands)
args = (botnick, author, email, about, commands)
message = "{} | {} <{}> | {} | {}".format(*args)
self.bot.send_message(source, message)

27
actions/control.py Normal file
View File

@ -0,0 +1,27 @@
def puppet(self, name, source, response):
botnick = self.bot.botnick
author = self.bot.author
command = response.split("!puppet ")[1]
place, message = command.split("|", 1)
if name != author:
return
self.bot.send_message(place, message)
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
actions/nomad.py Normal file
View File

58
actions/stupid.py Normal file
View File

@ -0,0 +1,58 @@
import re
import operator
def hmm(self, name, source, response):
botnick = self.bot.botnick
pattern = re.compile("hm+")
matches = re.findall(pattern, response)
score = len(matches)
if name not in self.bot.memories["users"]:
self.bot.memories["users"][name] = dict()
if "hmmscore" not in self.bot.memories["users"][name]:
self.bot.memories["users"][name]["hmmscore"] = 0
current_score = self.bot.memories["users"][name]["hmmscore"]
self.bot.memories["users"][name]["hmmscore"] = current_score + score
self.bot.save_memories()
def hmmscore(self, name, source, response):
botnick = self.bot.botnick
score = 0
score_format = "Hmm score for '{}': {}"
if " " in response:
name = response.split(" ", 1)[1]
if name not in self.bot.memories["users"]:
self.bot.send_message(source, score_format.format(name, score))
return
if "hmmscore" in self.bot.memories["users"][name]:
score = self.bot.memories["users"][name]["hmmscore"]
self.bot.send_message(source, score_format.format(name, score))
return
def hmmscoreboard(self, name, source, response):
botnick = self.bot.botnick
hmmscores = list()
for user, values in self.bot.memories["users"].items():
hmmscores.append({
"name": user,
"score": values.get("hmmscore", 0)
})
size = 3
start = -size
sort_scores = sorted(hmmscores, key=lambda k: k["score"])
top_scores = sort_scores[start:][::-1]
leaders = " | ".join([
"{} {}".format(ts["name"], ts["score"]) for ts in top_scores
])
self.bot.send_message(source, "Hmm Score Leaderboard: {}".format(leaders))

25
actions/summon.py Normal file
View File

@ -0,0 +1,25 @@
from subprocess import Popen, PIPE
from email.mime.text import MIMEText
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"
message = MIMEText(" ".join([
"My bot, {}, received a summoning request for you".format(botnick),
"from {} in channel {} for reason: {}".format(name, source, reason)
]))
message["From"] = email.format(botnick)
message["To"] = email.format(user)
message["Subject"] = "You have been summoned!"
command = "/usr/sbin/sendmail -t -oi".split(" ")
p = Popen(command, stdin=PIPE, universal_newlines=True)
p.communicate(message.as_string())
confirmation = "{}: You have summoned {}".format(name, user)
self.bot.send_message(source, confirmation)

40
app.py
View File

@ -1,15 +1,18 @@
#!/usr/bin/env python3
from os.path import dirname, realpath
from bot import Bot, Tasks, Responses
from actions import actions
kickers = list()
inviters = dict()
kingme = [
"#chaos"
]
bot = Bot("127.0.0.1", 6667, "BabiliBot|py", ["#bots"])
bot = Bot("127.0.0.1", 6667, "BabiliBot|py", [
"#bots",
"#insane"
])
responses = Responses(bot)
for action in actions:
@ -20,7 +23,7 @@ for action in actions:
action["callback"]
)
def try_to_king_me(bot, channel):
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)
@ -30,26 +33,35 @@ def handle_pm(name, response):
def handle_mode(channel, mode):
if mode == "-r":
try_to_king_me(bot, channel)
try_to_king_me(channel)
def handle_invite(channel, name):
if channel in kingme:
try_to_king_me(bot, channel)
try_to_king_me(channel)
invites.append({
"channel": channel,
"name": name
})
users = bot.memories["users"]
if name not in users:
bot.memories["users"][name] = dict()
def handle_kick(kicker):
kickers.append(kicker)
if "invites" not in users[name]:
bot.memories["users"][name]["invites"] = list()
bot.memories["users"][name]["invites"].append(channel)
def handle_kick(name):
users = bot.memories["users"]
if name not in users:
bot.memories["users"][name] = dict()
bot.memories["users"][name]["kicker"] = True
def handle_message(name, source, response):
print("MSG: {} {} - {}".format(name, source, response))
responses.parse(name, source, response)
if response == "!debug":
print("::", bot.memories)
if __name__ == "__main__":
bot.start("settings.json", {
bot.start(dirname(realpath(__file__)), {
"pm": handle_pm,
"mode": handle_mode,
"invite": handle_invite,

View File

@ -1,6 +1,7 @@
import re
import json
import socket
import os.path
class Bot:
def __init__(self, server, port, botnick, channels):
@ -12,7 +13,7 @@ class Bot:
self.running = True
self.settings = dict()
self.users = dict()
self.places = list()
self.author = ""
self.recv_size = 2048
@ -29,8 +30,12 @@ class Bot:
print("DEBUG: ", response)
self.ircsock.send(response.encode())
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."
@ -43,9 +48,18 @@ class Bot:
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))
remember = self.memories["users"]
for user in users:
if user not in self.users:
self.users[user] = dict()
if user[0] == "~" or user[0] == "@":
user = user[1:]
if user not in remember:
self.memories["users"][user] = dict()
def leave(self, chan):
message = "PART {} :Bye-bye!"
self.send(message, chan)
self.places.remove(chan)
def ping(self, message):
response = message.split("PING :")[1]
@ -69,9 +83,9 @@ class Bot:
def handle_rename(self, message):
before, new_name = message.split("NICK ", 1)
name = self.get_name(before)
user = self.users[name]
del self.users[name]
self.users[new_name] = user
user = self.memories["users"][name]
del self.memories["users"][name]
self.memories["users"][new_name] = user
return user, new_name
def handle_invite(self, message):
@ -86,12 +100,46 @@ class Bot:
before, kicker = re.split(regex, message)
return kicker
def handle_join(self, message):
before, after = message.split("JOIN ", 1)
user = 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.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 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):
set_vars = [
"author"
]
with open(location, "r") as f:
path = "{}/{}".format(self.location, location)
with open(path, "r") as f:
self.settings = json.loads(f.read())
for name, attr in self.settings.items():
@ -102,7 +150,7 @@ class Bot:
self.running = False
self.send("QUIT")
def start(self, settings, callback):
def start(self, location, callback):
message = ""
registered = False
confirmed = True
@ -111,7 +159,9 @@ class Bot:
self.send("USER {0} {0} {0} {0}", self.botnick)
self.send("NICK {0}", self.botnick)
self.load_settings(settings)
self.location = location
self.load_settings("settings.json")
self.load_memories("data/memories.json")
password = self.settings["password"] or ""
confirm = self.settings["confirm"] or ""
@ -169,6 +219,14 @@ class Bot:
kicker = self.handle_kick(message)
if "kick" in callback:
callback["kick"](kicker)
elif "JOIN " in message:
user = self.handle_join(message)
if "join" in callback:
callback["join"](user)
elif "PART " in message:
user = self.handle_part(message)
if "part" in callback:
callback["part"](user)
elif "INVITE " in message:
channel, name = self.handle_invite(message)
if "invite" in callback:

View File

@ -1,4 +1,5 @@
import re
from datetime import datetime
class Responses:
def __init__(self, bot):
@ -13,6 +14,32 @@ class Responses:
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 in users and "blacklist" in users[name]:
reason = users[name]["blacklist"]["reason"]
message = "is ignoring {} for reason '{}'".format(name, reason)
self.bot.send_action(source, message)
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:
self.bot.memories["users"][name]["blacklist"] = {
"reason": "Auto-banished",
"when": now
}
return False
return True
def parse(self, name, source, response):
check = {
"name": name,
@ -20,11 +47,19 @@ class Responses:
"response": response
}
marker = ";;"
mlen = len(marker)
for trigger in list(self.triggers.keys()):
for pattern, callback in self.triggers[trigger].items():
if pattern[0] == "!" and pattern == response:
callback(self, name, source, response)
if pattern[:mlen] != marker:
if pattern == response and self.allowed(name, source):
callback(self, name, source, response)
else:
regex = re.compile(pattern)
regex = re.compile(pattern[mlen:])
if regex.match(check[trigger]) is not None:
callback(self, name, source, response)
if self.allowed(name, source):
callback(self, name, source, response)
now = datetime.now().timestamp()
self.bot.memories["users"][name]["last_response"] = now

0
data/.gitkeep Normal file
View File