diff --git a/.gitignore b/.gitignore index b41d147..9c57510 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,15 @@ target/ #Ipython Notebook .ipynb_checkpoints + +# Keep git from pushing `settings.json`s with keys/tokens while keeping +# default settings.json format +testing.json +vocab.json +words.json + +# Keep pycharm files out of repo +.idea/S.C.S.I..iml +.idea/misc.xml +.idea/modules.xml +.idea/workspace.xml diff --git a/main.py b/main.py index be9bf59..94efb63 100644 --- a/main.py +++ b/main.py @@ -8,6 +8,8 @@ import random import datetime import re +import markov as mk +import newmarkov from discord.ext import commands from pathlib import Path @@ -15,14 +17,29 @@ description = '''An automod bot for auto modding ''' -reminders = [] -polls = [] +mk = mk.Markov +nmk = newmarkov.NewMarkov() + +TESTING = True +if TESTING: + settings = open('testing.json', '+r') +else: + settings = open('settings.json', '+r') -settings = open('settings.json', 'r') ds = json.load(settings) prefix = ds['bot']['prefix'] +if 'reminders' in ds: + reminders = ds['reminders'] +else: + reminders = [] + +if 'polls' in ds: + polls = ds['polls'] +else: + polls = [] + bot = commands.Bot(command_prefix=prefix, description=description) loop = bot.loop @@ -36,15 +53,18 @@ logger.info("Starting SCSI {0} using discord.py {1}".format(ds['bot']["version"], discord.__version__)) print("Starting SCSI {0} using discord.py {1}".format(ds['bot']['version'], discord.__version__)) + def findServer(ident): return bot.get_server(ident) + def findChannel(server, channel): '''finds the channel''' for all in ds['servers']: if all['id'] == server: return bot.get_channel(all[channel]) + ## may not get used but I'm just keeping it def findUser(id): users = list(bot.get_all_members()) @@ -54,6 +74,7 @@ def findUser(id): return all return -1 + def checkRole(user, roleRec): '''Checks if the user has the recuired role''' ok = False @@ -62,6 +83,7 @@ def checkRole(user, roleRec): ok = True return ok + def timeToTicks(time): '''converts time into seconds than to ticks''' time = time.lower() @@ -69,7 +91,7 @@ def timeToTicks(time): timeSec = 0 for all in time: if "w" in all or "week" in all or "weeks" in all: - tmp = all.strip('weks') + tmp = all.strip('weeks') timeSec += datetime.timedelta(weeks=int(tmp)).total_seconds() elif "d" in all or "day" in all or "days" in all: tmp = all.strip('days') @@ -96,29 +118,50 @@ async def timer(): loop.create_task(on_tick()) await asyncio.sleep(ds['bot']['ticklength']) + @bot.event async def on_channel_delete(channel): server = channel.server.id msg = "Channel {0} has been deleted!".format(channel.mention) - await bot.send_message(findChannel(server, 'announcements'), msg, tts=ds['bot']['tts']) + try: + await bot.send_message(findChannel(server, 'announcements'), msg, tts=ds['bot']['tts']) + except discord.Forbidden: + msg = "Missing Permissions for announcements!" + await bot.send_message(findChannel(server, 'botspam'), msg, tts=ds['bot']['tts']) + @bot.event async def on_channel_create(channel): server = channel.server.id msg = "Channel {0} has been created!".format(channel.mention) - await bot.send_message(findChannel(server, 'announcements'), msg, tts=ds['bot']['tts']) + try: + await bot.send_message(findChannel(server, 'announcements'), msg, tts=ds['bot']['tts']) + except discord.Forbidden: + msg = "Missing Permissions for announcements!" + await bot.send_message(findChannel(server, 'botspam'), msg, tts=ds['bot']['tts']) + @bot.event async def on_member_join(member): server = member.server.id msg = "New member {0} has joined the server!".format(member.mention) - await bot.send_message(findChannel(server, 'announcements'), msg, tts=ds['bot']['tts']) + try: + await bot.send_message(findChannel(server, 'announcements'), msg, tts=ds['bot']['tts']) + except discord.Forbidden: + msg = "Missing Permissions for announcements!" + await bot.send_message(findChannel(server, 'botspam'), msg, tts=ds['bot']['tts']) + @bot.event async def on_member_remove(member): server = member.server.id msg = "Member {0} has left the server!".format(member.name) - await bot.send_message(findChannel(server, 'announcements'), msg, tts=ds['bot']['tts']) + try: + await bot.send_message(findChannel(server, 'announcements'), msg, tts=ds['bot']['tts']) + except discord.Forbidden: + msg = "Missing Permissions for announcements!" + await bot.send_message(findChannel(server, 'botspam'), msg, tts=ds['bot']['tts']) + @bot.event async def on_command(command, ctx): @@ -129,30 +172,40 @@ async def on_command(command, ctx): else: destination = "#{0.channel.name} ({0.server.name})".format(message) + +@bot.group(pass_context=True) +async def poll(ctx): + '''The poll command group''' + if ctx.invoked_subcommand == None: + await bot.say("Must be used with a subcommand!") + + @bot.command() async def test(): - '''Prints a test message''' - await bot.say("HELLO WORLD!") + '''Prints a test message''' + await bot.say("HELLO WORLD!") -@bot.command(pass_context=True) -async def poll(ctx, time, description, *options): + +@poll.command(pass_context=True) +async def create(ctx, time, description, *options): '''Creates a poll''' pollNum = ds['bot']['pollNum'] ds['bot']['pollNum'] += 1 try: -## time = int(time) + # time = int(time) time = timeToTicks(time) desc = description pos = {} server = ctx.message.server.id for all in options: pos[all] = 0 - polls.append({"time":time, 'pollNum':pollNum, "desc":desc, "pos":pos, "server":server}) + polls.append({"time": time, 'pollNum': pollNum, "desc": desc, "pos": pos, "server": server}) await bot.say("New poll created! #{0}, possibilities: {1}".format(pollNum, pos)) except: await bot.say('Incorrect number format') -@bot.command() + +@poll.command() async def vote(number, option): '''Votes on a poll''' try: @@ -162,15 +215,16 @@ async def vote(number, option): if all['pollNum'] == pollNum: if pos in all['pos'].keys(): all['pos'][pos] += 1 - break # Why waste valuable processing cycles? + break # Why waste valuable processing cycles? await bot.say('Invalid option for that poll') except ValueError: await bot.say('Incorrect number format') + @bot.command() async def timeto(ticks): '''says how much time will pass in ticks - !!obsolite!!''' + !!obsolete!!''' try: ticks = int(''.join(ticks)) seconds = ds['bot']['ticklength'] * ticks @@ -184,19 +238,25 @@ async def timeto(ticks): except ValueError: await bot.say("Invalid arguments") -@bot.command(pass_context=True) -async def shutdown(ctx): - '''Shuts down the bot''' - author = ctx.message.author - if checkRole(author, ds['bot']['botmin']): - msg = "Shutting down now!" - await bot.say(msg) - timerTask.cancel() - bot.logout() - settings.close() - sys.exit() - else: - await bot.say("User is not {0}, ask a {0} to use this command!".format(ds['bot']['botmin'])) + +# @bot.command(pass_context=True) +# async def shutdown(ctx): +# '''Shuts down the bot''' +# author = ctx.message.author +# if checkRole(author, ds['bot']['botmin']): +# msg = "Shutting down now!" +# await bot.say(msg) +# timerTask.cancel() +# #settings.flush() +# #ds['reminders'] = reminders +# #ds['polls'] = poll +# #json.dump(ds, settings) +# settings.close() +# mk.stop() +# bot.logout() +# bot.close() +# else: +# await bot.say("User is not {0}, ask a {0} to use this command!".format(ds['bot']['botmin'])) @bot.command() async def timeup(): @@ -209,9 +269,10 @@ async def timeup(): msg = "Time up is: *{0} Hours, {1} Minutes and, {2} Seconds*".format(hoursUp, minutesUp, timeUp) await bot.say(msg) -#the following code does not work, and so we will not keep it -#@bot.command(pass_context=True) -#async def tts(ctx): + +# the following code does not work, and so we will not keep it +# @bot.command(pass_context=True) +# async def tts(ctx): # '''Turns TTS on or off''' # if ctx.message.content[len(prefix) + 4:] == "on": # ds['bot']['tts'] = True @@ -227,29 +288,31 @@ async def echo(*, message): logger.info('Echoing: {0}'.format(message)) await bot.say(message) + @bot.command(pass_context=True) async def changegame(ctx, *game): '''Changes the game being displayed''' author = ctx.message.author if checkRole(author, ds['bot']['botmin']): gameName = ' '.join(game) - await bot.change_status(game=discord.Game(name=gameName)) + await bot.change_presence(game=discord.Game(name=gameName)) await bot.say("Changing game to: \"{0}\"!".format(gameName)) else: await bot.say("User is not {0}, ask a {0} to use this command!".format(ds['bot']['botmin'])) + @bot.command(pass_context=True) async def remind(ctx, delay, *message): '''Sets a reminder for several seconds in the future''' msg = ' '.join(message) chan = ctx.message.channel try: -## following code kept for posterity -## delay = int(float(delay) / ds['bot']['ticklength']) -## if delay == 0: -## delay = 1 -## reminders.append([delay, chan, msg]) -## await bot.say("Reminder set") + ## following code kept for posterity + ## delay = int(float(delay) / ds['bot']['ticklength']) + ## if delay == 0: + ## delay = 1 + ## reminders.append([delay, chan, msg]) + ## await bot.say("Reminder set") delay = timeToTicks(delay) if delay == 0: delay = 1 @@ -258,6 +321,19 @@ async def remind(ctx, delay, *message): except ValueError: await bot.say("Incorrect format for the delay") + +@bot.command() +async def about(): + try: + msg = "```Version: {0}\nPrefix: {1}\n\"Game\": {2}\nContributors: {3}```".format(ds['bot']['version'], + ds['bot']['prefix'], + ds['bot']['game'], + str(ds['contrib']).strip("[]")) + await bot.say(msg) + except: + pass + + @bot.command(pass_context=True) async def backup(ctx, num="1000"): '''Backs up messages in the current channel. "all" will back up the entire channel. If num is not provided, defaults to 1000''' @@ -272,7 +348,7 @@ async def backup(ctx, num="1000"): if not p.exists(): p.mkdir() newliner = re.compile('\n') end = last_backup_time(servPath + chanPath) - + if num.lower() == "all": await bot.send_message(msg.channel, "Starting backup") count = 1000 @@ -282,37 +358,39 @@ async def backup(ctx, num="1000"): # Probably a better way to do this, but I don't know it async for m in bot.logs_from(msg.channel, limit=1): now_time = m.timestamp - + while count == 1000: count = 0 first = True f = open(servPath + chanPath + 'temp', 'w') - + async for message in bot.logs_from(msg.channel, limit=1000, before=now_time, after=end): if first: start_time = message.timestamp first = False - + m = message.clean_content m = newliner.sub('\n\t', m) - f.write(str(message.timestamp) + ': ' + message.author.name + ' (' + str(message.author.nick) + '):\n\t' + m + '\n') + f.write(str(message.timestamp) + ': ' + message.author.name + ' (' + str( + message.author.display_name) + '):\n\t' + m + '\n') f.write('attachments:\n') for a in message.attachments: f.write('\t') f.write(a['url']) f.write('\n') f.write('\n') - + now_time = message.timestamp count += 1 total += 1 - + f.close() - Path(servPath + chanPath + 'temp').rename(servPath + chanPath + str(now_time) + ' -- ' + str(start_time) + '.log') + Path(servPath + chanPath + 'temp').rename( + servPath + chanPath + str(now_time) + ' -- ' + str(start_time) + '.log') await bot.say("Backed up " + str(total) + " messages") - + await bot.say("Backup finished") - + else: num = int(num) f = open(servPath + chanPath + 'temp', 'w') @@ -327,21 +405,26 @@ async def backup(ctx, num="1000"): else: m = message.clean_content m = newliner.sub('\n\t', m) - f.write(str(message.timestamp) + ': ' + message.author.name + ' (' + str(message.author.nick) + '):\n\t' + m + '\n') + f.write(str(message.timestamp) + ': ' + message.author.name + ' (' + str( + message.author.nick) + '):\n\t' + m + '\n') f.write('attachments:\n') for a in message.attachments: f.write('\t') f.write(a['url']) f.write('\n') f.write('\n') - + end_time = message.timestamp - + f.close() - Path(servPath + chanPath + 'temp').rename(servPath + chanPath + str(end_time) + ' -- ' + str(start_time) + '.log') + Path(servPath + chanPath + 'temp').rename( + servPath + chanPath + str(end_time) + ' -- ' + str(start_time) + '.log') await bot.say('Backup finished') except ValueError: await bot.say('Incorrect number format') + except e: + await bot.send_message() + @bot.command(pass_context=True) async def who(ctx, user): @@ -354,11 +437,67 @@ async def who(ctx, user): break if user == None: await bot.say("mention a user!") - msg = "Name: {0}\nID: {1}\nDiscriminator: {2}\nBot: {3}\nAvatar URL: {4}\nCreated: {5}\nNickname: {6}".format(user.name, user.id, user.discriminator, user.bot, user.avatar_url, user.created_at, user.display_name) - await bot.say(msg) + msg = "```Name: {0}\nID: {1}\nDiscriminator: {2}\nBot: {3}\nCreated: {5}\nNickname: {6}```Avatar URL: {4}\n".format( + user.name, user.id, user.discriminator, user.bot, user.avatar_url, user.created_at, user.display_name) await bot.say(str(user)) + await bot.say(msg) except: - await bot.say("Please mention a user!") + await bot.say("Please mention a user!") + + +@bot.group(pass_context=True) +async def markov(ctx): + '''The Markov command group''' + if ctx.invoked_subcommand == None: + words = len(nmk.words) + await bot.say("I know {0} words!\nPlease use a subcommand to use or expand my knowledge!".format(words)) + + +@markov.command() +async def read(text): + '''Reads passed text''' + # mk.readText(text) + # mk.save() + nmk.read(text) + nmk.save() + await bot.say("Got it!") + + +@markov.command(pass_context=True) +async def readchannel(ctx, msgs=1000): + '''reads messages in the channel''' + if ctx.message.channel_mentions: + channel = ctx.message.channel_mentions[0] + else: + channel = ctx.message.channel + await bot.say("Reading {0} messages from {1}".format(msgs, channel)) + async for m in bot.logs_from(channel, msgs): + if m.author != bot.user: + # mk.readText(m.clean_content) + nmk.read(m.clean_content) + # mk.save() + nmk.save() + await bot.say("Got it!") + + +@markov.command() +async def write(words=100): + '''Think, swine!''' + # msg = mk.writeText(words) + msg = nmk.write(words) + msgs = list(chunkstring(msg, 1000)) + print(msgs) + for all in msgs: + await bot.say("Lucky: {0}".format(all)) + +# @markov.command(pass_context=True) +# async def upload(ctx): +# '''uploads the jsons for markov''' +# await mk.save() +# await bot.say("Uploading files!") +# await bot.send_file(ctx.message.channel, mk.vocabFile) +# await bot.send_file(ctx.message.channel, mk.wordsFile) + @asyncio.coroutine async def on_tick(): @@ -377,24 +516,32 @@ async def on_tick(): await bot.send_message(channel, "poll #{0} is now over!".format(poll['pollNum'])) polls.remove(poll) + def last_backup_time(backup_dir): p = Path(backup_dir) last_file = None for f in p.iterdir(): last_file = f - + if last_file is None: return None file_name = str(last_file) split_name = file_name.split('-- ') date_str = split_name[1] return string_to_datetime(date_str) + +def chunkstring(string, length): + return (string[0+i:length+i] for i in range(0, len(string), length)) + def string_to_datetime(s): date_and_time = s.split() date = date_and_time[0].split('-') time = date_and_time[1].split(':') seconds_and_micro = time[2].split('.') - return datetime.datetime(year=int(date[0]), month=int(date[1]), day=int(date[2]), hour=int(time[0]), minute=int(time[1]), second=int(seconds_and_micro[0]), microsecond=int(seconds_and_micro[1])) + return datetime.datetime(year=int(date[0]), month=int(date[1]), day=int(date[2]), hour=int(time[0]), + minute=int(time[1]), second=int(seconds_and_micro[0]), + microsecond=int(seconds_and_micro[1])) + @bot.event async def on_ready(): @@ -410,9 +557,21 @@ async def on_ready(): logger.info('Game set to:') logger.info(ds['bot']['game']) logger.info('------') - await bot.change_status(game=discord.Game(name=ds['bot']['game'])) + await bot.change_presence(game=discord.Game(name=ds['bot']['game'])) + startTime = time.time() timerTask = loop.create_task(timer()) -bot.run(ds['bot']["token"]) -settings.close() +try: + bot.run(ds['bot']["token"]) +except SystemExit: + print('Shutting down!') + # settings.flush() + # ds['reminders'] = reminders + # ds['polls'] = poll + # json.dump(ds, settings) + settings.close() + nmk.save() + nmk.words_file.close() +sys.exit() +# EOF error prevention diff --git a/markov.py b/markov.py index 69323fc..6e68db8 100644 --- a/markov.py +++ b/markov.py @@ -1,33 +1,100 @@ import random -#import io -#import json +import io +import json +import time + class Markov: - vocab = {} - words = [] - def readText(txt): - txt = txt.split() - for i in range(len(txt) - 1): - try: - Markov.vocab[txt[i]].append(txt[i + 1]) - except: - Markov.vocab[txt[i]] = [txt[i + 1]] - Markov.words.append(txt[i]) - - - def writeText(n = 100): - text = [random.choice(Markov.words)] - n -= 1 - try: - for i in range(n): - tmp = Markov.vocab[text[i]] - text.append(random.choice(tmp)) - except KeyError: - print("Just a key error, nothing to see here!") - return " ".join(text) + try: + wordsFile = open("words.json", "+r") + vocabFile = open("vocab.json", "+r") + words = json.load(wordsFile) + vocab = json.load(vocabFile) + except: + wordsFile = open("words.json", "+w") + vocabFile = open("vocab.json", "+w") + words = [] + vocab = {} + + # def __init__(self): + # self.words = json.load(Markov.wordsFile) + # self.vocab = json.load(Markov.vocabFile) + # + # def readText(self, txt): + # txt = txt.split() + # try: + # for i in range(len(txt) - 1): + # try: + # self.vocab[txt[i]].append(txt[i + 1]) + # except: + # self.vocab[txt[i]] = [txt[i + 1]] + # self.words.append(txt[i]) + # except: + # pass + # + # def writeText(self, n = 100): + # text = [random.choice(self.words)] + # n -= 1 + # try: + # for i in range(n): + # tmp = self.vocab[text[i]] + # text.append(random.choice(tmp)) + # except KeyError: + # return " ".join(text) + # except IndexError as e: + # return e + # return " ".join(text) + # + # def save(self): + # Markov.wordsFile.flush(); + # json.dump(self.words, Markov.wordsFile) + # Markov.vocabFile.flush(); + # json.dump(self.vocab, Markov.vocabFile) + # + # def stop(self): + # self.save(self) + # self.vocabFile.close() + # self.wordsFile.close() + # print("Stopping Markov!") + + def readText(txt): + txt = txt.split() + for i in range(len(txt) - 1): + try: + Markov.vocab[txt[i]].append(txt[i + 1]) + except: + Markov.vocab[txt[i]] = [txt[i + 1]] + Markov.words.append(txt[i]) + + def save(): + Markov.wordsFile.flush() + Markov.vocabFile.flush() + json.dump(Markov.words, Markov.wordsFile, sort_keys=True) + json.dump(Markov.vocab, Markov.vocabFile, sort_keys=True) + + def writeText(n=10): + print("writing") + text = [random.choice(Markov.words)] + n -= 1 + for i in range(n): + try: + tmp = Markov.vocab[text[i]] + except KeyError: + print("Just a key error, nothing to see here!") + time.sleep(0.05) + tmp = Markov.vocab[random.choice(Markov.words)] + text.append(random.choice(tmp)) + return " ".join(text) + + def stop(): + Markov.save() + Markov.vocabFile.close() + Markov.wordsFile.close() + if __name__ == "__main__": - text = """test""" - Markov.readText(text) - print(Markov.writeText()) -#test + Markov.readText("this is a test") + Markov.readText("a mighty fine test indeed") + print(Markov.writeText()) + Markov.stop() +# test diff --git a/newmarkov.py b/newmarkov.py new file mode 100644 index 0000000..a236012 --- /dev/null +++ b/newmarkov.py @@ -0,0 +1,60 @@ +import json +import io +import random +import numpy + + +class NewMarkov: + + def __init__(self): + self.words = {} # {word: {word: prob}} + try: + self.words_file = open("words.json", "+r") + try: + self.words = json.load(self.words_file) + except json.JSONDecodeError: + print("File empty!") + + except FileNotFoundError: + self.words_file = open("words.json", "+w") + + def read(self, text): + txt = text.split() + for i in range(len(txt) - 1): + if txt[i] in self.words: + if txt[i + 1] in self.words[txt[i]]: + self.words[txt[i]][txt[i + 1]] += 1 + else: + self.words[txt[i]][txt[i + 1]] = 1 + else: + self.words[txt[i]] = {} + self.words[txt[i]][txt[i+1]] = 1 + + def write(self, n=10): + text = [random.choice(list(self.words.keys()))] + n -= 1 + for i in range(n): + if text[i] in self.words: + wordlist = list(self.words[text[i]].keys()) + counts = list(self.words[text[i]].values()) + sum = 0 + for all in counts: + sum += all + for i in range(len(counts)): + counts[i] /= sum + word = numpy.random.choice(wordlist, p=counts) + text.append(word) + else: + text.append(random.choice(list(self.words.keys()))) + return " ".join(text) + + def save(self): + self.words_file.flush() + json.dump(self.words, self.words_file) + +if __name__ == "__main__": + mk = NewMarkov() + mk.read("this is a test") + mk.save() + print(mk.write(10)) + mk.words_file.close() \ No newline at end of file diff --git a/settings.json b/settings.json index f55c47d..7b43584 100644 --- a/settings.json +++ b/settings.json @@ -1,7 +1,7 @@ { "bot":{ "token":"", - "version":"v0.5", + "version":"v0.6", "tts":false, "prefix":"!", "game":"with entity.human.programmer", @@ -21,8 +21,12 @@ "id":"SERVER ID", "announcements":"CHANNEL ID", "poll":"", - "adminLog":"" + "adminLog":"", + "botspam":"" } + ], + "contrib":[ + "jellopudding", + "onionsans." ] - }