415 lines
14 KiB
Python
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;
|