A fresh start

This commit is contained in:
Jacob Henry 2018-09-09 13:20:21 -04:00
parent d27991269a
commit 68380bd5f2
17 changed files with 1037 additions and 1636 deletions

View File

@ -1,11 +0,0 @@
from slackclient import SlackClient
#One last call from beyond the grave
apifile = open("apitoken.txt", 'r')
SLACK_API = next(apifile).strip()
apifile.close();
slack = SlackClient(SLACK_API)
slack.api_call("chat.postMessage", channel="@jacobhenry", text="one error. and im die?")

View File

@ -1,140 +0,0 @@
"""
Examples provided by google for using their api.
Very slightly modified by me to easily just get credentials
"""
import httplib2
import os
from apiclient import discovery
import oauth2client
from oauth2client import client
from oauth2client import tools
try:
import argparse
flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args(["--noauth_local_webserver"])
except ImportError:
flags = None
# If modifying these scopes, delete your previously saved credentials
# at ~/.credentials/sheets.googleapis.com-python-quickstart.json
SCOPES = 'https://www.googleapis.com/auth/spreadsheets.readonly'
CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'SlickSlacker'
def get_sheets_service(credentials):
http = credentials.authorize(httplib2.Http())
discoveryUrl = ('https://sheets.googleapis.com/$discovery/rest?'
'version=v4')
service = discovery.build('sheets', 'v4', http=http, discoveryServiceUrl=discoveryUrl)
return service
def get_sheets_credentials():
"""Gets valid user credentials from storage.
If nothing has been stored, or if the stored credentials are invalid,
the OAuth2 flow is completed to obtain the new credentials.
Returns:
Credentials, the obtained credential.
"""
home_dir = os.path.expanduser('~')
credential_dir = os.path.join(home_dir, '.credentials')
if not os.path.exists(credential_dir):
os.makedirs(credential_dir)
credential_path = os.path.join(credential_dir, 'sheets.googleapis.com-python-quickstart.json')
store = oauth2client.file.Storage(credential_path)
credentials = store.get()
if not credentials or credentials.invalid:
flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
flow.user_agent = APPLICATION_NAME
if flags:
credentials = tools.run_flow(flow, store, flags)
else: # Needed only for compatibility with Python 2.6
credentials = tools.run(flow, store)
print('Storing credentials to ' + credential_path)
return credentials
def get_calendar_credentials():
"""Gets valid user credentials from storage.
If nothing has been stored, or if the stored credentials are invalid,
the OAuth2 flow is completed to obtain the new credentials.
Returns:
Credentials, the obtained credential.
"""
home_dir = os.path.expanduser('~')
credential_dir = os.path.join(home_dir, '.credentials')
if not os.path.exists(credential_dir):
os.makedirs(credential_dir)
credential_path = os.path.join(credential_dir, 'calendar-python-quickstart.json')
store = oauth2client.file.Storage(credential_path)
credentials = store.get()
if not credentials or credentials.invalid:
flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
flow.user_agent = APPLICATION_NAME
if flags:
credentials = tools.run_flow(flow, store, flags)
else: # Needed only for compatibility with Python 2.6
credentials = tools.run(flow, store)
print('Storing credentials to ' + credential_path)
return credentials
def sheets_quickstart():
"""Shows basic usage of the Sheets API.
Creates a Sheets API service object and prints the names and majors of
students in a sample spreadsheet:
https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
"""
credentials = get_sheets_credentials()
http = credentials.authorize(httplib2.Http())
discoveryUrl = ('https://sheets.googleapis.com/$discovery/rest?'
'version=v4')
service = discovery.build('sheets', 'v4', http=http,
discoveryServiceUrl=discoveryUrl)
spreadsheetId = '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms'
rangeName = 'Class Data!A2:E'
result = service.spreadsheets().values().get(
spreadsheetId=spreadsheetId, range=rangeName).execute()
values = result.get('values', [])
if not values:
print('No data found.')
else:
print('Name, Major:')
for row in values:
# Print columns A and E, which correspond to indices 0 and 4.
print('%s, %s' % (row[0], row[4]))
def calendar_quickstart():
"""Shows basic usage of the Google Calendar API.
Creates a Google Calendar API service object and outputs a list of the next
10 events on the user's calendar.
"""
credentials = get_calendar_credentials()
http = credentials.authorize(httplib2.Http())
service = discovery.build('calendar', 'v3', http=http)
now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
print('Getting the upcoming 10 events')
eventsResult = service.events().list(
calendarId='primary', timeMin=now, maxResults=10, singleEvents=True,
orderBy='startTime').execute()
events = eventsResult.get('items', [])
if not events:
print('No upcoming events found.')
for event in events:
start = event['start'].get('dateTime', event['start'].get('date'))
print(start, event['summary'])
if __name__ == '__main__':
main()

View File

@ -1,81 +0,0 @@
"""
This file contains util for scroll polling
Only really kept separate for neatness sake.
"""
import re
from SlackUtil import reply
from fuzzywuzzy import fuzz
from fuzzywuzzy import process
#load the family tree
familyfile = open("sortedfamilytree.txt", 'r')
#Parse out
p = re.compile("([0-9]*): (.*)$")
brothers = [p.match(line) for line in familyfile]
brothers = [m for m in brothers if m]
brothers = [{
"scroll": int(m.group(1)),
"name": m.group(2)
} for m in brothers]
"""
Attempts to look up a user by scroll
"""
def handleScrollMsg(slack, msg):
#Initialize response
response = None
#Get text
text = msg['text']
p = re.compile("scroll\\s*(.*?)$")
m = p.match(text)
if not m:
response = "Could not parse your query. Please invoke as \"scroll <name>\" or \"scroll <scroll#>\""
else:
response = getResponseByQuery(m.group(1))
reply(slack, msg, response, username="scrollbot")
def getResponseByQuery(query):
try:
#Try get scroll number
scroll = int(query)
b = findBrotherByScroll(scroll)
if b:
return "Brother {0} has scroll {1}".format(b["name"], b["scroll"])
else:
return "Could not find scroll {0}".format(scroll)
except ValueError:
#Use as name
name = query
b = findBrotherByName(name)
if b:
return "Best match:\nBrother {0} has scroll {1}".format(b["name"], b["scroll"])
else:
return "Could not find brother {0}".format(name)
def findBrotherByScroll(scroll):
for b in brothers:
if b["scroll"] == scroll:
return b
return None
def findBrotherByName(name):
#Try direct lookup
"""
for b in brothers:
if name.toLower() in b["name"].toLower():
return b
"""
#Do fuzzy match
return process.extractOne(name, brothers, processor=lambda b: b["name"])[0]

View File

@ -1,61 +0,0 @@
"""
This file contains general utility for polling google sheets.
Note that it is not used for the REQUISITION of the service,
as that is in the domain of GoogleApi.py.
It is instead for more general things, like parsing a sheet into 2d array,
etc.
My goal is to abstract all of the sheets bullshit out, and make reading
from a sheet as easy as working with a 2d array, as well as making it
fairly fast.
"""
"""
Gets a spreadsheet object from a sheet id
"""
def getSpreadsheet(sheet_service, sheet_id):
#Get the spreadsheet
spreadsheet = sheet_service.spreadsheets().get(spreadsheetId=sheet_id).execute()
#And let him have it!
return spreadsheet
"""
Gets the names of every page in a spreadsheet.
"""
def getPageNames(spreadsheet):
pageNames = [sheet["properties"]["title"] for sheet in spreadsheet["sheets"]]
return pageNames
"""
Gets the contents of a page in its entirety, as a 2d array.
TODO: Make this take a spreadsheet object.
Unfortunately, the spreadsheet object doc is literally 20k lines long of
poorly formatted text(seriously, what the fuck)
"""
def getPageContents(sheet_service, sheet_id, pageName, range="$A1$1:$YY"):
sheet_range = pageName + "!" + range
values = sheet_service.spreadsheets().values()
result = values.get(spreadsheetId=sheet_id, range=sheet_range).execute()
return result.get('values', [])
"""
Gets all pages as 2d arrays. from spreadsheet.
Pages are appended, in order.
So basically, you get an array of 2d arrays representing spreadsheet pages
"""
def getAllPageValues(sheet_service, sheet_id):
#Get all page names
pageNames = getPageNames(getSpreadsheet(sheet_service, sheet_id))
#Get values for each page
pageContents = [getPageContents(sheet_service, sheet_id, name, range="A2:D") for name in pageNames]
return pageContents

View File

@ -1,66 +0,0 @@
from time import sleep
import re #Regular expressions
"""
Slack helpers. Separated for compartmentalization
"""
DEFAULT_USERNAME = "cylon" #NICE MEME
"""
Sends message with "text" as its content to the channel that message came from
"""
def reply(slack, msg, text, username=DEFAULT_USERNAME):
channel = msg['channel']
slack.api_call("chat.postMessage", channel=channel, text=text, username=username)
"""
Returns whether or not msg came from bot
"""
def isBotMessage(msg):
return ("bot_id" in msg or "user" not in msg)
"""
Generator that yields messages from slack.
Messages are in standard api format, look it up.
Checks on 2 second intervals (may be changed)
"""
def messageFeed(slack):
if slack.rtm_connect():
print("Waiting for messages")
while True:
sleep(2)
update = slack.rtm_read()
for item in update:
if item['type'] == 'message':
yield item
print("Critical slack connection failure")
return
"""
Returns whether or not user has configured profile
"""
def isValidProfile(user):
return ('profile' in user['user'] and
'first_name' in user['user']['profile'] and
'last_name' in user['user']['profile'])
"""
Gets the user info for whoever is first mentioned in the message,
or None if no mention is made
"""
def getForUser(slack, msg):
m_re = re.compile(".*?<@([A-Z0-9]*?)>")
mention_match = m_re.match(msg['text'])
if mention_match is not None:
mention_id = mention_match.group(1)
return slack.api_call("users.info", user=mention_id)
else:
return None

View File

@ -1,93 +0,0 @@
"""
This file handles conversion of users scrolls into numbers
"""
import re
from SlackUtil import reply
p = re.compile("truescroll\\s*(\d*)$")
def handleTrueScrollMsg(slack, msg):
text = msg['text']
response = None
m = p.match(text)
if not m:
response = "Could not parse your query. Please invoke as \"truescroll <scroll#>\""
else:
num = m.group(1)
brotherScroll = getBrotherScroll(num)
trueScroll = getTrueScroll(num)
#Correct for 666 (thanks TREVOR)
#Offset broscroll by one if >= 666
if int(brotherScroll) >= 666:
brotherScroll = getBrotherScroll(str(int(num) + 1))
#Offset truescroll in opposite direction
if int(num) > 666:
trueScroll = str(int(trueScroll) - 1)
#Memes
if int(num) == 666:
trueScroll = "spookiest"
elif "3" in num:
trueScroll = "worst"
response = "The brother with scroll {0} is in fact the {1} brother to sign\n"
response += "The {2} brother to sign will have scroll {3}\n"
response = response.format(num, trueScroll, num, brotherScroll)
reply(slack, msg, response, username = "scrollbot")
brotherNums = [str(x) for x in range(10) if (not x == 3)]
trueNums = [str(x) for x in range(10)]
#Returns string
def getTrueScroll(brotherScroll):
return convertBase(brotherScroll, brotherNums, trueNums)
#Returns string
def getBrotherScroll(trueScroll):
return convertBase(trueScroll, trueNums, brotherNums)
#Returns string
def convertBase(numStr, srcBaseNums, targBaseNums):
#Returns int value
def numberFromBase(ns, numerals):
base = len(numerals)
basePower = 1
total = 0
#Go by character.
for c in ns[::-1]:
try:
digitVal = numerals.index(c)
total += digitVal * basePower
except ValueError:
total += 0
basePower = basePower * base
return total
#Returns string, each elt is the corresponding numeral
def numberToBase(n, numerals):
if n==0:
return [0]
digits = []
while n:
digVal = int(n % len(numerals))
digits.append(numerals[digVal])
n /= len(numerals)
n = int(n)
return "".join(digits[::-1])
return numberToBase(numberFromBase(numStr, srcBaseNums), targBaseNums)

View File

@ -1,112 +0,0 @@
import GoogleApi as google#For read drive
from slackclient import SlackClient#Obvious
from SlackUtil import *
from WaitonUtil import handleWaitonMsg #For waitons
from ScrollUtil import handleScrollMsg #For scrolls
from TrueScrollUtil import handleTrueScrollMsg #For true scrolls
from kong import handleKongMsg
import re
#Read api token from file
apifile = open("apitoken.txt", 'r')
SLACK_API = next(apifile).strip()
apifile.close();
#Read killswitch from file
killswitchfile = open("killswitch.txt", 'r')
killswitch = next(killswitchfile).strip()
killswitchfile.close()
#Set default username.
#Authenticate, get sheets service. Done globally so we dont have to do this
#every fucking time, which is probably a bad idea
sheet_credentials = google.get_sheets_credentials()
sheet_service = google.get_sheets_service(sheet_credentials)
"""
Insults the scrub who tried to use the bot without setting up slack.
If for_user is supplied, assumes user was fine and insults for_user
Also provides helpful info for how to avoid future disgrace, but thats like, tertiary
"""
def handleProfilelessScum(slack, msg, user, for_user=None):
f_response = None
if for_user:
user_name = user['user']['name']
for_name = for_user['user']['name']
f_response = "Hey {0}, tell {1} to set up his fucking profile. Like, first and last name, and stuff".format(user_name, for_name)
else:
f_response = "Set up your profile before talking to me, scum.\n\nThat is to say, fill out your first and last name in your slack user profile! Please use what would be on the waiton list (IE your proper name, not a nickname)."
reply(slack, msg, f_response)
waiton_pattern = re.compile("^waiton")
scroll_pattern = re.compile("^scroll")
true_scroll_pattern = re.compile("^truescroll")
housejob_pattern = re.compile("^(house)?job")
kong_pattern = re.compile("^kong")
def main():
#Init slack
slack = SlackClient(SLACK_API)
print(slack)
slack.api_call("chat.postMessage", channel="@jacobhenry", text="I'm back baby!")
feed = messageFeed(slack)
for msg in feed:
#Check not bot
if isBotMessage(msg):
print("Message skipped. Reason: bot")
continue
#get user info
userid = msg['user']
user = slack.api_call("users.info", user=userid)
#If a mention is found, assign for_user
for_user = getForUser(slack, msg)
#Handle Message
text = msg['text'].lower()
msg['text'] = text
if not isValidProfile(user):#invalid profile
print("Received profileless")
handleProfilelessScum(slack, msg, user)
elif for_user and not isValidProfile(for_user):#invalid for_user profile
print("Received for profileless")
handleProfilelessScum(slack, msg, user, for_user)
elif waiton_pattern.match(text):
print("Received waiton from " + user['user']['name'])
handleWaitonMsg(slack, sheet_service, msg, user, for_user)
elif scroll_pattern.match(text):
print("Received scroll from " + user['user']['name'])
handleScrollMsg(slack, msg)
elif true_scroll_pattern.match(text):
print("Received true scroll from " + user['user']['name'])
handleTrueScrollMsg(slack, msg)
elif housejob_pattern.match(text):
print("Received housejob from " + user['user']['name'])
reply(slack, msg, "I cannot do that (yet)", username="sadbot")
elif kong_pattern.match(text):
print("Received kong from " + user['user']['name'])
handleKongMsg(slack, msg)
elif killswitch == msg['text'].lower():
reply(slack, msg, "as you wish...", username="rip bot")
break
else:
print("Message skipped. Reason: no command found")
#run main
main()

View File

@ -1,138 +0,0 @@
import SheetUtil #For read from waiton sheet
from tabulate import tabulate
from SlackUtil import reply
"""
This file contains util for waiton polling.
Only really kept separate for neatness sake.
"""
#ID of waiton sheet on drive
#WAITON_SHEET_ID = "1J3WDe-OI7YjtDv6mMlM1PN3UlfZo8_y9GBVNBEPwOhE" #A term 2016
#WAITON_SHEET_ID = "1I4keh9cIt0x-WwZAdnBsZefSZV-tMIAy37r2NLUOLh4" #First week b term
WAITON_SHEET_ID = "1jnLXo_QhZWId84gAVC3rzzo26HmTo_cs2FqviyU6_mw" #All of b term
"""
Pulls waiton data from the spreadsheet.
Returns waitons as list of objects, each of form
{
name: <brothername>,
date: <date as string>,
meal: <meal as string>
}
"""
def getWaitons(sheet_service):
#Propogate dates to each row
def fixDates(values):
curr_date = None
curr_dow = None
last_row = None
for row in values:
date_col = row[0]
#Update date if it is more or less a date
if "/" in date_col:
curr_date = date_col
#Update previous row
if curr_date is not None:
last_row[0] = curr_dow + " - " + curr_date
#Update DOW now that previous row will not be affected "" != date_col:
if "/" not in date_col and "" != date_col:
curr_dow = date_col
#Cycle last_row
last_row = row
#Fix the last row
if last_row is not None:
last_row[0] = curr_dow + " - " + curr_date
#Propogate meal data to each row
def fixMeals(values):
curr_meal = None
for row in values:
#Update curr meal:
if row[1] != "" and row[1] not in ["starts at 4:30p", "Midnight"]:
curr_meal = row[1]
if curr_meal is not None:
row[1] = curr_meal
#Helper to remove steward rows
def filterStewards(values):
return [row for row in values if len(row) > 2]
#Helper to remove empty rows (IE no assignees)
def filterUnset(values):
return [row for row in values if "---" not in row[2]]
pageContents = SheetUtil.getAllPageValues(sheet_service, WAITON_SHEET_ID)
#Filter junk rows
pageContents = [filterStewards(x) for x in pageContents]
pageContents = [filterUnset(x) for x in pageContents]
#Fix stuff (make each have full info)
for pc in pageContents:
fixDates(pc)
fixMeals(pc)
#Merge, using property of python list concatenation via add (+)
allWaitons = sum(pageContents, [])
#Parse to objects
waitonObjects = [{
"name": row[2],
"date": row[0],
"meal": row[1]
} for row in allWaitons]
return waitonObjects
"""
Takes a slack context, a message to reply to, and a user to look up waitons for.
Additionally, takes a for_user flag for message formatting
IE "user" asked for "for_user" waitons, so blah blah blah
"""
def handleWaitonMsg(slack, sheet_service, msg, user, for_user=None):
"""
Filters to waitons with the given name
"""
def filterWaitons(waitons, first, last):
def valid(waiton):
return first in waiton["name"] or last in waiton["name"]
return [w for w in waitons if valid(w)]
"""
Formats waitons for output
"""
def formatWaitons(waitons):
waitonRows = [(w["name"], w["date"], w["meal"]) for w in waitons]
return tabulate(waitonRows)
#Create format string
response = ( "{0} asked for waiton information{1}, so here are his waitons:\n"
"{2}")
#Get names of caller
requestor_first = user['user']['profile']['first_name']
requestor_last = user['user']['profile']['last_name']
#Get names of target user
for_first = (for_user or user)['user']['profile']['first_name']
for_last = (for_user or user)['user']['profile']['last_name']
#Get waitons for target user
waitons = getWaitons(sheet_service)
waiton_string = formatWaitons( filterWaitons(waitons, for_first, for_last))
f_response = response.format(requestor_first, " for {0}".format(for_first) if for_user else "", waiton_string)
reply(slack, msg, f_response, username="mealbot")

View File

@ -1,3 +1,4 @@
slackclient
python-Levenshtein
fuzzywuzzy
httplib2

69
google_api.py Normal file
View File

@ -0,0 +1,69 @@
"""
Examples provided by google for using their api.
Very slightly modified by me to easily just get credentials
"""
import os
from googleapiclient.discovery import build
from httplib2 import Http
from oauth2client import file, client, tools
# If modifying these scopes, delete your previously saved credentials
# at ~/.credentials/sheets.googleapis.com-python-quickstart.json
SCOPES = 'https://www.googleapis.com/auth/spreadsheets.readonly'
APPLICATION_NAME = 'My Project'
def _init_sheets_service():
store = file.Storage('token.json')
creds = store.get()
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('sheets_credentials.json', SCOPES)
creds = tools.run_flow(flow, store)
service = build('sheets', 'v4', http=creds.authorize(Http()))
return service
_global_sheet_service = _init_sheets_service()
# range should be of format 'SHEET NAME!A1:Z9'
def get_sheet_range(spreadsheet_id, range):
"""
Gets an array of the desired table
"""
result = _global_sheet_service.spreadsheets().values().get(spreadsheetId=spreadsheet_id,
range=range).execute()
values = result.get('values', [])
if not values:
return None
else:
return values
def get_calendar_credentials():
"""Gets valid user credentials from storage.
If nothing has been stored, or if the stored credentials are invalid,
the OAuth2 flow is completed to obtain the new credentials.
Returns:
Credentials, the obtained credential.
"""
home_dir = os.path.expanduser('~')
credential_dir = os.path.join(home_dir, '.credentials')
if not os.path.exists(credential_dir):
os.makedirs(credential_dir)
credential_path = os.path.join(credential_dir, 'calendar-python-quickstart.json')
store = oauth2client.file.Storage(credential_path)
credentials = store.get()
if not credentials or credentials.invalid:
flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
flow.user_agent = APPLICATION_NAME
if flags:
credentials = tools.run_flow(flow, store, flags)
else: # Needed only for compatibility with Python 2.6
credentials = tools.run(flow, store)
print('Storing credentials to ' + credential_path)
return credentials

111
kong.py
View File

@ -1,111 +0,0 @@
import threading
import re
from SlackUtil import reply
from time import sleep
DEFAULT_SONG = "dk_rap_classic"
DEFAULT_DELAY = 2000
#Patterns
patterns = {}
patterns['start'] = re.compile(r"#STARTSONG\s+(.*?)$")
patterns['time'] = re.compile(r"#TIMEPERLINE\s+(\d*)$")
patterns['rest'] = re.compile(r"#REST")
patterns['end'] = re.compile(r"#ENDSONG")
#SongContents
lyric_lines = []
f = open("lyrics.txt")
for line in f:
lyric_lines += [line]
f.close()
class SongThread(threading.Thread):
def __init__(self, callback, song_name):
super().__init__()
self.song_name = song_name
self.callback = callback
self.delay = DEFAULT_DELAY
self.daemon = True
def run(self):
playing = False
for line in lyric_lines:
line = line.strip()
if line == "":
continue
#Navigate to song start
if not playing:
m = patterns['start'].match(line)
print(line)
if m:
if m.group(1) == self.song_name:
playing = True
continue
#Play loop
else:
#Config
m = patterns['time'].match(line)
if m:
self.delay = int(m.group(1))
continue
#Rest line
m = patterns['rest'].match(line)
if m:
sleep(self.delay / 1000)
continue
#End song
m = patterns['end'].match(line)
if m:
return
#"sing" line
self.callback(line)
sleep(self.delay / 1000)
if not playing:
self.callback("Could not find song")
def getAllTitles():
titles = []
for line in lyric_lines:
m = patterns['start'].match(line)
if m:
titles += [m.group(1)]
return titles
request_pattern = re.compile(r"kong\s*(.*?)$")
def handleKongMsg(slack, msg):
#Get text
text = msg['text']
match = request_pattern.match(text)
#Make callback function
reply_callback = lambda response: reply(slack, msg, response, username="jukebot")
if match:
if match.group(1) == "list":
response = ""
for title in getAllTitles():
response = response + title
reply_callback(response)
elif match.group(1) != "":
st = SongThread(reply_callback, match.group(1))
st.start()
else:
st = SongThread(reply_callback, DEFAULT_SONG)
st.start()
else:
response = "Invoke as kong <songtitle:list>"
reply_callback(response)

View File

@ -1,69 +0,0 @@
#STARTSONG dk_rap_classic
#TIMEPERLINE 750
So they're finally here, performing for you
If you know the words, you can join in too
Put your hands together, if you want to clap
As we take you through this monkey rap
DK
Donkey Kong
He's the leader of the bunch, you know him well
He's finally back to kick some tail
His coconut gun can fire in spurts
If he shoots ya, it's gonna hurt
He's bigger, faster, and stronger too
He's the first member of the DK crew
DK
Donkey Kong
DK
Donkey Kong is here
This Kong's got style, so listen up dudes
She can shrink in size, to suit her mood
She's quick and nimble when she needs to be
She can float through the air and climb up trees
If you choose her, you'll not choose wrong
With a skip and a hop, she's one cool Kong
DK
Donkey Kong
He has no style, he has no grace
This Kong has a funny face
He can handstand when he needs to
And stretch his arms out, just for you
Inflate himself just like a balloon
This crazy Kong just digs this tune
DK
Donkey Kong
DK
Donkey Kong is here
He's back again and about time too
And this time he's in the mood
He can fly real high with his jetpack on
With his pistols out, he's one tough Kong
He'll make you smile when he plays his tune
But Kremlings beware cause he's after you
DK
Donkey Kong
Finally, he's here for you
It's the last member of the DK crew
This Kong's so strong, it isn't funny
Can make a Kremling cry out for mummy
Can pick up a boulder with relative ease
Makes crushing rocks seem such a breeze
He may move slow, he can't jump high
But this Kong's one hell of a guy
C'mon Cranky, take it to the fridge!
Walnuts, peanuts, pineapple smells
Grapes, melons, oranges and coconut shells
#ENDSONG

82
main.py Normal file
View File

@ -0,0 +1,82 @@
from collections import OrderedDict
import google_api as google # For read drive
from slackclient import SlackClient # Obvious
from slack_util import *
import scroll_util
import re
# Read api token from file
api_file = open("apitoken.txt", 'r')
SLACK_API = next(api_file).strip()
api_file.close()
# Read kill switch from file
kill_switch_file = open("killswitch.txt", 'r')
kill_switch = next(kill_switch_file).strip()
kill_switch_file.close()
# Authenticate, get sheets service. Done globally so we dont have to do this
# every fucking time, which is probably a bad idea
sheet_credentials = google.get_sheets_credentials()
sheet_service = google._init_sheets_service(sheet_credentials)
def main():
wrapper = ClientWrapper()
# DEBUG: Add blanked handling
# wrapper.add_hook(".*", print)
# Add scroll handling
wrapper.add_hook(scroll_util.command_pattern, scroll_util.callback)
# Add kill switch
wrapper.add_hook(kill_switch, die)
wrapper.listen()
def die(*args):
print("Got kill switch")
exit()
class ClientWrapper(object):
def __init__(self):
# Init slack
self._slack = SlackClient(SLACK_API)
self._slack.rtm_send_message(channel="@jacob henry", message="I'm back baby!")
# Hooks go regex -> callback on (slack, msg, match)
self._hooks = OrderedDict()
def add_hook(self, pattern, callback):
self._hooks[re.compile(pattern)] = callback
def listen(self):
feed = message_stream(self._slack)
for msg in feed:
# We only care about standard messages, not subtypes, as those usually just channel activity
if msg.get("subtype"):
continue
# Handle Message
text = msg['text'].strip()
success = False
for regex, callback in self._hooks.items():
match = regex.match(text)
if match:
success = True
print("Matched on callback {}".format(callback))
callback(self._slack, msg, match)
break
if not success:
print("No hit on {}".format(text))
# run main
if __name__ == '__main__':
main()

View File

@ -9,7 +9,6 @@ do
echo "Press [CTRL+C] to stop..."
sleep 1
python3 WaitonBot.py
sleep 3
python3 CallOfTheVoid.py
sleep 1
echo "Died. Updating and restarting..."
done

62
scroll_util.py Normal file
View File

@ -0,0 +1,62 @@
"""
This file contains util for scroll polling
Only really kept separate for neatness sake.
"""
import re
from slack_util import reply
from fuzzywuzzy import fuzz
from fuzzywuzzy import process
# load the family tree
familyfile = open("sortedfamilytree.txt", 'r')
command_pattern = r"scroll\s+(.*)"
# Parse out
brother_match = re.compile(r"([0-9]*)~(.*)")
brothers = [brother_match.match(line) for line in familyfile]
brothers = [m for m in brothers if m]
brothers = [{
"scroll": int(m.group(1)),
"name": m.group(2)
} for m in brothers]
"""
Attempts to look up a user by scroll
"""
def callback(slack, msg, match):
# Get the query
query = match.group(1).strip()
# Try to get as int or by name
try:
sn = int(query)
result = find_by_scroll(sn)
except ValueError:
result = find_by_name(query)
if result:
result = "Brother {} has scroll {}".format(result["name"], result["scroll"])
else:
result = "Couldn't find brother {}".format(query)
# Respond
reply(slack, msg, result)
def find_by_scroll(scroll):
for b in brothers:
if b["scroll"] == scroll:
return b
return None
def find_by_name(name):
# coerce name into dict form
name = {"name": name}
# Do fuzzy match
return process.extractOne(name, brothers, processor=lambda b: b["name"])[0]

35
slack_util.py Normal file
View File

@ -0,0 +1,35 @@
from time import sleep
"""
Slack helpers. Separated for compartmentalization
"""
def reply(slack, msg, text):
"""
Sends message with "text" as its content to the channel that message came from
"""
channel = msg['channel']
thread_id = msg['ts']
slack.rtm_send_message(channel=channel, message=text, thread=thread_id)
def message_stream(slack):
"""
Generator that yields messages from slack.
Messages are in standard api format, look it up.
Checks on 2 second intervals (may be changed)
"""
# Do forever
while True:
if slack.rtm_connect(with_team_state=False, auto_reconnect=True):
print("Waiting for messages")
while True:
sleep(2)
update = slack.rtm_read()
for item in update:
if item.get('type') == 'message':
yield item
sleep(15)
print("Connection failed - retrying")

File diff suppressed because it is too large Load Diff