rebase
This commit is contained in:
commit
0121086306
|
|
@ -0,0 +1,4 @@
|
|||
apitoken.txt
|
||||
killswitch.txt
|
||||
client_secret.json
|
||||
*.pyc
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
from slackclient import SlackClient
|
||||
|
||||
|
||||
#One last call from beyond the grave
|
||||
apifile = open("apitoken.txt", 'r')
|
||||
SLACK_API = next(apifile)
|
||||
apifile.close();
|
||||
|
||||
slack = SlackClient(SLACK_API)
|
||||
slack.api_call("chat.postMessage", channel="@jacobhenry", text="Alas poor yorrick. I died!")
|
||||
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
"""
|
||||
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()
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
"""
|
||||
This file contains util for scroll polling
|
||||
Only really kept separate for neatness sake.
|
||||
"""
|
||||
|
||||
"""
|
||||
Tells a user their scroll, or a for_users scrolls.
|
||||
Is limited to telling people
|
||||
"""
|
||||
def hanleScrollMsg(slack, sheet_service, msg):
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
"""
|
||||
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
|
||||
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
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
|
||||
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
import GoogleApi as google#For read drive
|
||||
from slackclient import SlackClient#Obvious
|
||||
from SlackUtil import *
|
||||
|
||||
from WaitonUtil import handleWaitonMsg #For waitons
|
||||
|
||||
#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)
|
||||
|
||||
|
||||
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()
|
||||
if not isValidProfile(user):#invalid profile
|
||||
handleProfilelessScum(slack, msg, user)
|
||||
elif for_user and not isValidProfile(for_user):#invalid for_user profile
|
||||
handleProfilelessScum(slack, msg, user, for_user)
|
||||
|
||||
elif "waiton" in text:
|
||||
handleWaitonMsg(slack, sheet_service, msg, user, for_user)
|
||||
|
||||
elif "scroll" in text:
|
||||
handleScrollMsg(slack, sheet_service, msg)
|
||||
|
||||
elif "housejob" in text:
|
||||
reply(slack, msg, "I cannot do that (yet)", username="sadbot")
|
||||
|
||||
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()
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
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"
|
||||
|
||||
|
||||
|
||||
"""
|
||||
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] != "":
|
||||
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")
|
||||
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash
|
||||
|
||||
#This is just a basic loop to run in the background on my raspi.
|
||||
#Restarts the bot if it dies, and notifies me
|
||||
|
||||
while :
|
||||
do
|
||||
echo "Press [CTRL+C] to stop..."
|
||||
sleep 5
|
||||
python3 WaitonBot.py
|
||||
sleep 1
|
||||
python3 CallOfTheVoid.py
|
||||
done
|
||||
Loading…
Reference in New Issue