minerbot/minerbot.py

415 lines
14 KiB
Python

import socket,random,subprocess,threading,time,cPickle,string,time,prss,randwords,pstocks,chaninfo,primefac,choose_parser,notes,mbtilde,kwam,lua,urn,scplugin,json,math;
import os.path as fs;
from email.mime.text import MIMEText;
import markovuniverse as mu;
zws = ""
class NotRunningException (Exception):
pass;
log_format = "{} [{}] {}: {}"
def loadLNR():
with open("/home/minerobber/lnr.txt") as f:
return [l.rstrip() for l in f if l.rstrip()]
def saveLNR(lnr):
with open("/home/minerobber/lnr.txt","w") as f:
f.write("\n".join(lnr))
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 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)
def getBotanyPlantPath(user,file="{0}_plant_data.json"):
return "/home/{{0}}/.botany/{}".format(file).format(user)
def hasBotanyPlant(user):
return fs.exists(getBotanyPlantPath(user))
def hasVisitors(user):
return fs.exists(getBotanyPlantPath(user,"visitors.json"))
def getBotanyPlant(user):
if not hasBotanyPlant(user):
return False, {}
else:
with open(getBotanyPlantPath(user)) as f:
data = json.load(f)
# if not hasVisitors(user): # TODO: fix this
return True, data
# else:
# with open(getBotanyPlantPath(user,"visitors.json")):
# return True, data, json.load(f)
#def getBotanyLastWatered(dlw,vt):
# rvt = []
# for t in vt:
# if t < time.time():
# rvt.append(t)
# if not rvt:
# return dlw
# allts = [dlw]+rvt
# allts.sort()
# diffs = [(j-i)/86400.0 for i, j in zip(allts[:-1], allts[1:])]
# lve = next((x for x in diffs if x > 5), None)
# if not lve:
# return allts[-1]
# return allts[:diffs.index(lve)+1][-1] #this should actually work, believe it or not
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 = [];
self.running = True;
self.channels = channels;
self.kwam = kwam.KWAMPlugin(self)
self.lua = lua.LuaPlugin(self)
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.notebook = notes.DatedNotebook()
self.solver = mbtilde.Solver(self)
self.urn = urn.UrnPlugin(self)
if fs.isfile("/home/minerobber/lnr.txt"):
self.lnr = loadLNR()
else:
self.lnr = []
saveLNR([])
# self.xkcd = xkcdpl.XKCDPlugin(self)
# self.g7sc = scplugin.G7SCPlugin(self,"!shinycalc")
#self.quiz = quiz.QuizGame("/home/minerobber/quiz.txt")
# 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");
self.irc.recv(4096)
for chan in channels:
self.irc.send("JOIN " + chan + "\r\n");
while self.running:
data = self.irc.recv(4096);
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("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 addNote(self,touser,fromuser,msg):
# print "ADDNOTE: {} {} {}".format(touser,fromuser,msg)
self.notebook.addNote(touser,fromuser,msg)
def readNotes(self,user):
# print "READNOTES: {}".format(user)
for msg in self.notebook.readNotes(user):
self.say(user,msg)
def haveNotesFor(self,user):
# print "CHECK: {}".format(user)
return self.notebook.checkNotes(user)
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];
if nick=="tilde.town":
return
print "[" + channel + "] " + nick + ": " + message;
log(channel, nick, message)
self.rss.pubDate = time.localtime()
if self.haveNotesFor(nick):
self.readNotes(nick)
if message.strip()=="!water minerbot":
self.mention(channel, nick, "BZZ-ZAP! Please don't water me! I'm not waterproofed yet!!!!! BZZ-BZZ-ZZZ-ZAP!")
# elif message.find("!urlme "):
# parts = message.split()
# if len(parts)>1:
# self.mention(channel,nick,"https://tilde.town/~{}/{}".format(nick,parts[1]))
# elif message.find("!talklikeme"):
# self.say(channel,"!talklike "+nick)
# elif message.strip()=="!lnrdebug":
# self.say(channel,"lnr = {!s}".format(self.lnr))
# return
elif message.find("!plant ")==0:
ok, data = getBotanyPlant(message.split(" ",1)[1].rstrip())
if not ok:
self.mention(channel,nick,"{} doesn't have a plant".format(message.split(" ",1)[1].rstrip()))
else:
# user requested does indeed have a plant, and data is the loaded JSON representation of said plant
msg = "{d[owner]}'s ".format(d=data)
status = data["description"]
if data["is_dead"]:
status = "dead "+status
status+=" was watered"
lastwatered = data["last_watered"] #,[x['timestamp'] for x in visitors])
if (lastwatered-time.time())<=(24*60*60):
status+=" today"
hours = str(int(math.floor((time.time()-lastwatered)/3600)))
if hours=='0':
seconds = str(int(math.floor((time.time()-lastwatered)/60)))
if seconds=='1':
status+=" (about a second ago)"
else:
status+=" (about {} seconds ago)".format(seconds)
elif hours=='1':
status+=" (about an hour ago)"
else:
status+=" (about {} hours ago)".format(hours)
self.mention(channel,nick,msg+status)
elif message.find("!steven-universe") == 0:
self.mention(channel, nick, "\"{}\" - {}".format(*mu.new_episode()))
elif 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 + "! I'm a mess of code. Ask my developer (my nick, but replace bot with \"obber\") about commands of mine!")
elif message.find("!twitch ") == 0:
mparts = message.split(" ")
if len(mparts) == 2:
c = chaninfo.ChannelInfo(mparts[1][:-2],"h7lvoe263k42haljbhlbv2ag1nzt5nd")
if not c.isOnline():
self.say(channel,"{} is not streaming.".format(c.channel))
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.find("!choose ") == 0:
self.say(channel,nick+": "+random.choice(choose_parser.parse(message)))
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:
# d = False
# if message.startwith("s "):
# d=True
# message=message.split(" ",2)[1]
print(self.owner + " used the global to say: \"" + message + "\"")
# if d:
self.globalmsg(message)
# else:
# self.globalmsg("[GLOBAL] "+message)
# self.globalmsg(("[GLOBAL] " if not d else "") + message)
elif message.find("!lnr") == 0:
if nick==self.owner and len(message.split(" "))>1:
self.lnr.append(message.split(" ",1)[1].rstrip())
saveLNR(self.lnr)
self.mention(channel,nick,"I'll remember that...")
return
for i in self.lnr:
self.mention(nick,nick,"Late night rambling: {}".format(i))
self.mention(channel,nick,"All late night ramblings have been PM'd to you.")
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\r\n") == 0 and self.owner == nick:
self.irc.send("QUIT :Reloading bot code...\r\n");
self.reload = 1;
self.running = False;
elif message.find("!announce ") == 0:
announce(channel, nick, " ".join(message.split(" ")[1:]), self.rss)
elif message.find("!reloadchannels") == 0 and nick == self.owner:
channels = []
with open("/home/minerobber/misc/mbchan.txt") as f:
channels = f.readlines()
self.channels_2 = channels
for channel in self.channels_2:
if channel not in self.channels:
self.irc.send("JOIN {}\r\n".format(channel))
self.channels = self.channels_2
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("!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:
self.say(nick,"DEPRECATED: Use new \"!note\" command (same syntax)")
elif message.find("!note ") == 0:
parts = message.split(" ",2)
if not len(parts) == 3:
self.mention(channel,nick,"usage: !note <user> <message>")
else:
self.addNote(parts[1],nick,parts[2])
self.mention(channel,nick,"Message sent for {}.".format(parts[1]))
elif message.find("!getStock") == 0:
self.mention(channel,nick,"This command is out-of-service while my developer figures out what's wrong with me.")
return
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.")
result = self.solver.run(message)
if result:
if type(result)==str:
self.say(channel,result)
else:
self.say(channel,str(result))
elif message.strip()=="!discord":
self.mention(channel,nick,"The discord invite link is: https://discord.gg/RS8gJAV")
self.solver.answer(channel,nick,message)
#self.xkcd.answer(channel,nick,message)
#self.xkcd.check_for_new_xkcd(channel)
self.kwam.answer(channel,nick,message)
# self.g7sc.handleCommand(channel,nick,message)
# self.urn.onMessage(self,channel,nick,message)
# self.lua.handleCommand(channel,nick,message)
def globalmsg(self,message):
for chan in self.channels:
self.say(chan,message)
def say(self, chan, message):
try:
if type(message)!=str:
message = str(message)
message = message.replace("\n","")
self.irc.send("PRIVMSG " + chan + " :" + message + "\r\n")
if chan.find("#") == 0:
log(chan, self.name, message)
print("[{}] {}: {}".format(chan,self.name,message))
except:
self.say(chan,"Failed to send message, try again!")
def mention(self,chan,nick,message):
self.say(chan,nick+": "+message)
# def playQuizGame(self,channel,nick,message):
# if self.quiz.notPlaying(nick):
# self.quiz.addPlayer(nick)
# self.mention(channel,nick,self.quiz.getQuestion(nick)+" Use !a, !b, !c, or !d to answer.")
# else:
# if message.rstrip() not in ("!a","!b","!c","!d"):
# return
# if not self.quiz.answer(nick,message[1]):
# self.mention(channel,nick,"Close, but no dice! Try again!")
# else:
# self.mention(channel,nick,"Correct!")
# self.quiz.addPoint(nick)
# self.quiz.save()
def save(self):
# self.quiz.save()
saveLNR(self.lnr)
return
def load(self):
# self.quiz.load()
return
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;