381 lines
14 KiB
Plaintext
381 lines
14 KiB
Plaintext
|
import socket,random,subprocess,threading,time,cPickle,string,time,prss,randwords,pstocks,chaninfo,primefac,re;
|
||
|
import os.path as fs;
|
||
|
from email.mime.text import MIMEText;
|
||
|
#with open("zws.txt","rb") as f:
|
||
|
# zws = f.readAll()
|
||
|
zws = ""
|
||
|
class NotRunningException (Exception):
|
||
|
pass;
|
||
|
|
||
|
log_format = "{} [{}] {}: {}"
|
||
|
|
||
|
def filterNick(nick):
|
||
|
if len(nick) == 1:
|
||
|
print "Either "+nick+" is not a nick, or the person who has that nick needs a longer nick."
|
||
|
return nick
|
||
|
s = nick[0]+zws+nick[1:]
|
||
|
return s.encode('utf-8')
|
||
|
|
||
|
def log(channel, nick, message):
|
||
|
with open("/home/minerobber/log.txt","ab") as fh:
|
||
|
fh.write(log_format.format(time.strftime("%Y-%m-%d %H:%M:%S"),channel,nick,message))
|
||
|
with open("/home/minerobber/logs/"+time.strftime("%Y-%m-%d")+".txt","ab") as fh:
|
||
|
fh.write(log_format.format(time.strftime("%Y-%m-%d %H:%M:%S"),channel,nick,message))
|
||
|
|
||
|
def logPublic(channel, nick, message):
|
||
|
with open("/home/minerobber/public_html/public_msgs.txt","ab") as fh:
|
||
|
fh.write(time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())+" ["+channel+"] "+nick+": "+message)
|
||
|
|
||
|
def announce(channel, nick, ann, prssfeed):
|
||
|
prssfeed.addItem("Announcement by {} in {}".format(channel, nick),"https://tilde.town/~{}".format(nick),ann)
|
||
|
with open(fs.expanduser("~/public_html/announcements/rss.xml"),"wb") as rss:
|
||
|
rss.write(prssfeed.make())
|
||
|
with open(fs.expanduser("~/public_html/announcements/index.text"),"ab") as html:
|
||
|
html.write('{}, while in {}, announced: "{}" \n'.format(nick, channel, ann));
|
||
|
subprocess.check_output(["make -C ~/public_html/announcements -B"],shell=True)
|
||
|
cPickle.dump(prssfeed,open("/home/minerobber/misc/annrss","wb"))
|
||
|
|
||
|
def logJoin(channel,nick):
|
||
|
with open("/home/minerobber/log.txt","ab") as fh:
|
||
|
fh.write(time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())+" "+nick+" joined "+channel)
|
||
|
|
||
|
def logPart(channel,nick):
|
||
|
with open("/home/minerobber/log.txt","ab") as fh:
|
||
|
fh.write(time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())+" "+nick+" left "+channel)
|
||
|
|
||
|
class minerbot:
|
||
|
def __init__(self, name, user, owner):
|
||
|
self.name = name;
|
||
|
self.owner = owner;
|
||
|
self.user = user;
|
||
|
self.running = False;
|
||
|
self.reload = 0;
|
||
|
|
||
|
def begin(self, server, port, channels):
|
||
|
# declare variables here
|
||
|
self.irc = socket.socket();
|
||
|
self.randWordsList = randwords.corpus;
|
||
|
self.userList = [];
|
||
|
if fs.isfile("/home/minerobber/userList"):
|
||
|
self.load();
|
||
|
self.running = True;
|
||
|
self.channels = channels;
|
||
|
self.emailTemplate = ["~{0} wanted me to tell you:\n"]
|
||
|
self.rss = prss.PageRSS("Announcements","https://tilde.town/~minerobber/announcements","Announcements made by tilde.town members in IRC.",time.localtime())
|
||
|
if fs.isfile("/home/minerobber/misc/annrss"):
|
||
|
self.rss = cPickle.load(open("/home/minerobber/misc/annrss"))
|
||
|
# self.guessgameplaying = False;
|
||
|
# self.gg_players = {};
|
||
|
# self.gg_state = "not-active";
|
||
|
# self.gg_votes = {}
|
||
|
# connecting, as well as main loop
|
||
|
self.irc.connect((server, port));
|
||
|
self.irc.send("NICK " + self.name + "\r\n");
|
||
|
self.irc.send("USER " + self.user + " 8 * Making an IRC bot is fun!\r\n");
|
||
|
for chan in channels:
|
||
|
self.irc.send("JOIN " + chan + "\r\n");
|
||
|
while self.running:
|
||
|
data = self.irc.recv(4096);
|
||
|
#print data;
|
||
|
if data.find("PING") != -1:
|
||
|
self.irc.send("PONG " + data.split()[1] + "\r\n");
|
||
|
elif data.find("NAMES") != -1 and not fs.isfile("/home/minerobber/userList"):
|
||
|
users = data[5:-3]
|
||
|
for name in users.split(" "):
|
||
|
table = []
|
||
|
table.append(name)
|
||
|
table.append(0)
|
||
|
self.userList.append(table);
|
||
|
elif data.find("JOIN") != -1:
|
||
|
logJoin(data.split(" ")[2],data.split(" ")[0].split("!")[0][1:])
|
||
|
if data.split(" ")[2] == "#music" and data.split(" ")[0].split("!")[0][1:] != self.nick:
|
||
|
self.say(data.split(" ")[2],"Hello, "+data.split(" ")[0].split("!")[0][1:]+"! If you want to listen along, the URL is: https://tilde.town/~kc/jukebot/listen")
|
||
|
elif data.find("PART") != -1:
|
||
|
logPart(data.split(" ")[2],data.split(" ")[0].split("!")[0][1:])
|
||
|
elif data.find("PRIVMSG") != -1:
|
||
|
self.spokento(data)
|
||
|
self.save()
|
||
|
return self.reload;
|
||
|
|
||
|
def runCMD(self, cmd):
|
||
|
i = subprocess.check_output([cmd],shell=True)
|
||
|
if "\n" in i:
|
||
|
i.replace("\n","; ")
|
||
|
return i;
|
||
|
|
||
|
def sendEmail(self, channel, sender, recipient, text):
|
||
|
msg = MIMEText("~{0} wanted me to tell you:\n> {1}".format(sender,text))
|
||
|
msg["From"] = "minerbot-messages@tilde.town"
|
||
|
msg["To"] = "{0}@tilde.town".format(recipient)
|
||
|
msg["Subject"] = "Message from ~{0}".format(sender)
|
||
|
msg["Cc"] = "{0}@tilde.town".format(sender)
|
||
|
mail = subprocess.Popen("{0} -t -oi".format(which("sendmail")).split(" "),stdin=subprocess.PIPE)
|
||
|
mail.communicate(msg.as_string())
|
||
|
self.say(channel,"Mail sent!")
|
||
|
# mail.close()
|
||
|
|
||
|
def spokento(self, data):
|
||
|
if not self.running:
|
||
|
raise Exception("spokento was prematurely called.");
|
||
|
data_parts = data.split(" ",3);
|
||
|
id = data_parts[0];
|
||
|
nick = id.split("!")[0][1:];
|
||
|
message = data_parts[3][1:];
|
||
|
channel = data_parts[2];
|
||
|
print "[" + channel + "] " + nick + ": " + message;
|
||
|
log(channel, nick, message)
|
||
|
self.rss.pubDate = time.localtime()
|
||
|
if message.find(self.owner) != -1:
|
||
|
logPublic(channel,nick,message)
|
||
|
elif message.find("!log") == 0:
|
||
|
logPublic(channel,nick,message.split(" ",2)[1])
|
||
|
if message.find("!quit") == 0 and nick == self.owner:
|
||
|
self.irc.send("QUIT :beep boop I'm a bot.\r\n");
|
||
|
self.running = False;
|
||
|
elif message.find("!rollcall") == 0:
|
||
|
self.say(channel,"Hey! I'm " + self.name + "! Use '!help' to see a list of my commands.")
|
||
|
elif message.find("!twitch ") == 0:
|
||
|
mparts = message.split(" ")
|
||
|
if len(mparts) == 2:
|
||
|
c = chaninfo.ChannelInfo(mparts[1][:-2],"h7lvoe263k42haljbhlbv2ag1nzt5nd")
|
||
|
if not c.isOnline():
|
||
|
return
|
||
|
verb = "playing"
|
||
|
if c.getDisplay():
|
||
|
verb = "being"
|
||
|
self.say(channel, '{} is {} {}. Status: "{}"; Viewers: {!s}'.format(c.channel,verb,c.getGame(),c.getStatus(),c.getViewers()))
|
||
|
elif message.strip == "!last5said":
|
||
|
with open("/home/minerobber/log.txt") as f:
|
||
|
lines = []
|
||
|
for line in f:
|
||
|
lines.append(line.strip())
|
||
|
for line in lines[:-5]:
|
||
|
self.say(nick,line)
|
||
|
elif message.find("!dicegame") == 0:
|
||
|
message_parts = message.split(" ",2)
|
||
|
sides = 6
|
||
|
choice = int(random.uniform(1,sides))
|
||
|
b_choice = int(random.uniform(1,sides))
|
||
|
if b_choice > choice:
|
||
|
winMSG = "I win!"
|
||
|
else:
|
||
|
if b_choice == choice:
|
||
|
winMSG = "It's a tie!"
|
||
|
else:
|
||
|
winMSG = "You win!"
|
||
|
self.say(channel,"You rolled a " + str(choice) + ", and I rolled a " + str(b_choice) + ". " + winMSG)
|
||
|
elif channel == self.name and nick == self.owner:
|
||
|
print(self.owner + " used the global to say: \"" + message + "\"")
|
||
|
for chan in self.channels:
|
||
|
self.say(chan,"[GLOBAL] " + message)
|
||
|
elif message.find("!online") == 0:
|
||
|
results = self.runCMD("onlinepeople4irc")
|
||
|
leet = string.maketrans("aelosiuUc","43105|Uu(")
|
||
|
self.say(channel, results.translate(leet))
|
||
|
elif message.find("!reload") == 0 and self.owner == nick:
|
||
|
self.irc.send("QUIT :Reloading bot code...\r\n");
|
||
|
self.reload = 1;
|
||
|
self.running = False;
|
||
|
# elif message.find("!shop") == 0:
|
||
|
#nyi
|
||
|
elif message.find("!tbadmin") == 0 and self.owner == nick:
|
||
|
message_parts = message.split(" ")
|
||
|
if message_parts[1] == "add":
|
||
|
for i in self.userList:
|
||
|
if message_parts[2] == i[0]:
|
||
|
i[1] += int(message_parts[3])
|
||
|
self.save()
|
||
|
elif message_parts[1] == "rm":
|
||
|
for i in self.userList:
|
||
|
if message_parts[2] == i[0]:
|
||
|
i[1] += int(message_parts[3])
|
||
|
self.save()
|
||
|
elif message_parts[1] == "view":
|
||
|
for i in self.userList:
|
||
|
if message_parts[2] == i[0]:
|
||
|
self.say(channel,i[0] + " has " + str(i[1]) + " tildebucks.")
|
||
|
elif message.find("!announce ") == 0:
|
||
|
announce(channel, nick, " ".join(message.split(" ")[1:]), self.rss)
|
||
|
# elif message.find("!guessing_game") == 0 and self.gg_state == "not-active":
|
||
|
# thread = threading.Thread(target=self.playGuessingGame, args=(channel));
|
||
|
# elif message.find("!join_guessing_game") == 0 and self.gg_state == "player-wait":
|
||
|
# self.gg_players.append(nick);
|
||
|
elif message.find("!save") == 0 and nick == self.owner:
|
||
|
self.save()
|
||
|
elif message.find("!load") == 0 and nick == self.owner:
|
||
|
self.load()
|
||
|
#elif message.find("!money") == 0:
|
||
|
# for i in self.userList:
|
||
|
# if i[0] == nick:
|
||
|
# self.say(channel,(nick + " has " + str(i[1]) + " tildebucks."))
|
||
|
elif message.find("!update-qdb-commits") == 0 and self.owner == nick:
|
||
|
self.say(channel, self.runCMD("/home/minerobber/qdb-commits-gen.sh"))
|
||
|
elif message.find("!run ") == 0 and self.owner == nick:
|
||
|
prog = message.split(" ",1)[1]
|
||
|
nprog = which(prog.split(" ",1)[0])
|
||
|
if nprog is None:
|
||
|
self.say(channel,"Error: program does not exist!")
|
||
|
else:
|
||
|
self.say(channel,self.runCMD(prog.replace(prog.split(" ",1)[0],nprog)))
|
||
|
elif message.find("!tell") == 0:
|
||
|
parts = message.split(" ",2)
|
||
|
if not len(parts) == 3:
|
||
|
self.say(channel,"Usage: !tell <user> <message>")
|
||
|
else:
|
||
|
self.sendEmail(channel,nick,parts[1],parts[2][:-2])
|
||
|
# elif message.find("!get-gg-state") == 0 and self.owner == nick:
|
||
|
# self.say(channel, "GG-State: " + self.gg_state)
|
||
|
elif message.find("!getStock") == 0:
|
||
|
ticker = pstocks.StockTicker(format="{0} ({1}) is currently priced at {2}. (a change of {3})")
|
||
|
parts = message.split(" ")
|
||
|
if len(parts) < 2:
|
||
|
self.say(channel, "Usage: '!getStock <ticker>'")
|
||
|
return
|
||
|
for symbol in parts[1:]:
|
||
|
ticker.addSymbol(symbol)
|
||
|
for message in ticker.getTickerValues():
|
||
|
self.say(channel,message)
|
||
|
elif message.find("!mbtilde ") == 0:
|
||
|
if nick != self.owner:
|
||
|
self.say(channel,"Don't tell me what to do!")
|
||
|
return
|
||
|
if channel != "#bots":
|
||
|
self.say(channel,"Remember: tildebot only awards tildes in \#bots.")
|
||
|
parts = message.split(" ",2)
|
||
|
parts = [s.rstrip() for s in parts]
|
||
|
if parts[1] == "request":
|
||
|
self.say("#bots","!tilde")
|
||
|
if parts[1] == "add":
|
||
|
nums = [int(s) for s in parts[2].split(" ")]
|
||
|
self.say(channel,"{!s}".format(sum(nums)))
|
||
|
if parts[1] == "sub":
|
||
|
nums = [int(s) for s in parts[2].split(" ",1)]
|
||
|
self.say(channel,"{!s}".format(nums[1]-nums[0]))
|
||
|
if parts[1] == "mult":
|
||
|
nums = [int(s) for s in parts[2].split(" ",1)]
|
||
|
self.say(channel,"{!s}".format(nums[1]*nums[0]))
|
||
|
if parts[1] == "divide":
|
||
|
nums = [int(s) for s in parts[2].split(" ",1)]
|
||
|
try:
|
||
|
self.say(channel,"{!s}".format(nums[0]/nums[1]))
|
||
|
except ZeroDivisionError:
|
||
|
self.say(channel,"Bugger off!")
|
||
|
if parts[1] == "mod":
|
||
|
nums = [int(s) for s in parts[2].split(" ",1)]
|
||
|
self.say(channel,"{!s}".format(nums[0]%nums[1]))
|
||
|
if parts[1] == "factor":
|
||
|
self.say(channel,"{!s},{!s}".format(*list(primefac.primefac(int(parts[2]))))
|
||
|
elif message.find("!rpg") and channel=="#rpg":
|
||
|
parts = message.split(" ")
|
||
|
if len(parts) < 2:
|
||
|
self.say(channel,"Usage: !rpg <action> [parameters]")
|
||
|
if parts[1]=="roll" and len(parts)==3:
|
||
|
descriptor = parts[2].rstrip()
|
||
|
t = re.match(descriptor,r"^(\d+)d(\d+)$")
|
||
|
if not t:
|
||
|
self.say(channel,"Usage: !rpg roll <amount of dice>d<number of sides on each>")
|
||
|
self.say(channel,"Example: !rpg roll 1d100")
|
||
|
number = t.match(0)
|
||
|
sides =
|
||
|
# elif message.find("!helpme") == 0:
|
||
|
# help_parts = message.split(" ",2)
|
||
|
# if len(help_parts) < 2:
|
||
|
# self.say(channel,"Commands: 'dicegame', 'online', 'tell';")
|
||
|
# else:
|
||
|
# if help_parts[1] == "dicegame":
|
||
|
# self.say(channel,"Play a dice game against " + self.name + ". Usage: '!dicegame'")
|
||
|
# elif help_parts[1] == "online":
|
||
|
# self.say(channel,"Returns a list of online users, as well as a user count. Usage: '!online'")
|
||
|
# elif help_parts[1] == "tell":
|
||
|
# self.say(channel,"Sends a user an email to let them know of something. Usage: '!tell <user> <thing to tell them>'")
|
||
|
# elif help_parts[1] == "words":
|
||
|
# self.say(channel,"Generates a random string of words. Usage: '!words'")
|
||
|
# elif help_parts[1] == "getStock":
|
||
|
# self.say(channel,"Gets stock info for a specific ticker. Usage: '!getStock <ticker>'")
|
||
|
# elif self.gg_state == "player-wait-vote" and message.find("!guess"):
|
||
|
# if nick in self.gg_players:
|
||
|
# for i in self.gg_votes:
|
||
|
# if i[0] == nick:
|
||
|
# return;
|
||
|
# self.gg_votes.append({nick,int(message.split(" ")[1])})
|
||
|
# if self.gg_state == "player-wait-vote":
|
||
|
# playerDouble = {};
|
||
|
# for i in self.gg_votes:
|
||
|
# if i[0] in self.gg_players:
|
||
|
# playerDouble.append(i[0])
|
||
|
# if self.gg_players == playerDouble:
|
||
|
# self.gg_state = "all-voted"
|
||
|
# self.randWordsList.append(message)
|
||
|
|
||
|
def say(self, chan, message):
|
||
|
self.irc.send("PRIVMSG " + chan + " :" + message + "\r\n")
|
||
|
if chan.find("#") == 0:
|
||
|
log(chan, self.name, message)
|
||
|
|
||
|
def playGuessingGame(self,chan):
|
||
|
self.gg_players = []
|
||
|
self.say(chan, "Generating number to guess...")
|
||
|
gg_number = random.uniform(1,100)
|
||
|
self.gg_state = "player-wait"
|
||
|
self.say(chan, "Game will start soon! To join, type \"!join_guessing_game\"")
|
||
|
wait(15, True)
|
||
|
self.gg_state = "started"
|
||
|
self.say(chan, "The game has started!")
|
||
|
playerString = "Players: "
|
||
|
for player in self.gg_players:
|
||
|
playerString += player + " "
|
||
|
self.say(chan, playerString)
|
||
|
self.say(chan,"How to play: guess a number between 1 and 100.")
|
||
|
self.gg_state = "player-wait-vote"
|
||
|
while not self.gg_state == "all-voted":
|
||
|
wait(5,False)
|
||
|
correct_players = [];
|
||
|
for i in self.gg_votes:
|
||
|
if gg_number == i[1]:
|
||
|
correct_players.append(i[0])
|
||
|
correctVoteString = "Winners: "
|
||
|
for player in correct_players:
|
||
|
correctVoteString += player + " "
|
||
|
self.say(chan, correctVoteString)
|
||
|
self.say(chan, "All of the winners get 3 tildebucks!")
|
||
|
for user in self.userList:
|
||
|
if user[0] in correct_players:
|
||
|
user[1] += 3;
|
||
|
self.save()
|
||
|
self.gg_state = "not-active"
|
||
|
|
||
|
def save(self):
|
||
|
cPickle.dump(self.userList, open("/home/minerobber/userList","wb"))
|
||
|
|
||
|
def load(self):
|
||
|
cPickle.load(open("/home/minerobber/userList","rb"))
|
||
|
|
||
|
def num(s):
|
||
|
try:
|
||
|
int(s)
|
||
|
except ValueError:
|
||
|
float(s)
|
||
|
|
||
|
def wait(s, minutes):
|
||
|
if minutes:
|
||
|
time.sleep(s * 60)
|
||
|
else:
|
||
|
time.sleep(s)
|
||
|
|
||
|
def which(s):
|
||
|
import os
|
||
|
def is_exe(fpath):
|
||
|
return fs.isfile(fpath) and os.access(fpath, os.X_OK)
|
||
|
|
||
|
fpath, fname = fs.split(s)
|
||
|
if fpath:
|
||
|
if is_exe(program):
|
||
|
return program
|
||
|
else:
|
||
|
for path in os.environ["PATH"].split(os.pathsep):
|
||
|
path = path.strip('"')
|
||
|
exe_file = os.path.join(path, s)
|
||
|
if is_exe(exe_file):
|
||
|
return exe_file
|
||
|
return None;
|