diff --git a/CallOfTheVoid.py b/CallOfTheVoid.py deleted file mode 100644 index e4b119b..0000000 --- a/CallOfTheVoid.py +++ /dev/null @@ -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?") - diff --git a/GoogleApi.py b/GoogleApi.py deleted file mode 100755 index cfef4eb..0000000 --- a/GoogleApi.py +++ /dev/null @@ -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() diff --git a/ScrollUtil.py b/ScrollUtil.py deleted file mode 100644 index 7004019..0000000 --- a/ScrollUtil.py +++ /dev/null @@ -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 \" or \"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] - diff --git a/SheetUtil.py b/SheetUtil.py deleted file mode 100644 index 8eeefa4..0000000 --- a/SheetUtil.py +++ /dev/null @@ -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 - diff --git a/SlackUtil.py b/SlackUtil.py deleted file mode 100644 index 0c22349..0000000 --- a/SlackUtil.py +++ /dev/null @@ -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 - diff --git a/TrueScrollUtil.py b/TrueScrollUtil.py deleted file mode 100644 index c5b1ee9..0000000 --- a/TrueScrollUtil.py +++ /dev/null @@ -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 \"" - 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) diff --git a/WaitonBot.py b/WaitonBot.py deleted file mode 100755 index f13e990..0000000 --- a/WaitonBot.py +++ /dev/null @@ -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() diff --git a/WaitonUtil.py b/WaitonUtil.py deleted file mode 100644 index 658d4cc..0000000 --- a/WaitonUtil.py +++ /dev/null @@ -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: , - date: , - meal: -} -""" -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") - diff --git a/dependencies.txt b/dependencies.txt index 6263111..e922d4d 100644 --- a/dependencies.txt +++ b/dependencies.txt @@ -1,3 +1,4 @@ slackclient python-Levenshtein fuzzywuzzy +httplib2 \ No newline at end of file diff --git a/google_api.py b/google_api.py new file mode 100644 index 0000000..176f069 --- /dev/null +++ b/google_api.py @@ -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 diff --git a/kong.py b/kong.py deleted file mode 100644 index 9b067b6..0000000 --- a/kong.py +++ /dev/null @@ -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 " - reply_callback(response) - - diff --git a/lyrics.txt b/lyrics.txt deleted file mode 100644 index bba4e77..0000000 --- a/lyrics.txt +++ /dev/null @@ -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 diff --git a/main.py b/main.py new file mode 100644 index 0000000..720f072 --- /dev/null +++ b/main.py @@ -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() diff --git a/runloop.sh b/runloop.sh index 9fa5346..a975552 100755 --- a/runloop.sh +++ b/runloop.sh @@ -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 diff --git a/scroll_util.py b/scroll_util.py new file mode 100644 index 0000000..9008f31 --- /dev/null +++ b/scroll_util.py @@ -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] diff --git a/slack_util.py b/slack_util.py new file mode 100644 index 0000000..7dead1a --- /dev/null +++ b/slack_util.py @@ -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") \ No newline at end of file diff --git a/sortedfamilytree.txt b/sortedfamilytree.txt index d37af68..2aa3ff7 100644 --- a/sortedfamilytree.txt +++ b/sortedfamilytree.txt @@ -1,752 +1,787 @@ -1: Richard Brodeur -2: Charles T. Kleman -4: Warren Bentley -5: John P. Gahagan -6: Robert G. Bertrand -7: Jeffrey Semmel -8: Steve Schwarm -9: Robert D. Hickey -10: Joseph G. Ferrara -11: Robert C. Bell -12: Bruce Tuttle -14: Robert Stessel -15: Steve Leece -16: William D. Smith -17: Alan D. Randall -18: Richard M. King -19: Henry S. Sweet -20: John Czajkowski -21: Robert Orenberg -22: Bill Belisle -24: Steve Erikson -25: Walter J. Miska -26: David C. Johnson -27: Peter Miner -28: Peter Blackford -29: Jamon Chan -40: James F. Sinnamon -41: Daniel B. Bentley -42: Paul J. Pakus -44: James A. Metzler -45: Mark Saviet -46: Bruce Bosserman -47: Ray W. Spalding -48: John A. Giordano -49: Capt. Michael Hughes -50: Robert N Ahern -51: Dr. Patric V. Romano -52: Kenneth R. Perkins -54: David L. Titterington, Jr. -55: Ronald C. Strand -56: Arthur E. Kreymer -57: John L. Boursy, Jr. -58: Mitchell Soivenski -59: William H. Weed -60: Paul Grady -61: Steven Chan -62: Thomas Seiferman -64: Dave Hayhurst -65: Michael W. Thayer -66: Jeffrey N. Clark -67: John D. Powers -68: Chuck Green -69: Charles R. Cain, Jr. -70: Timothy F. Laskowski -71: Gary Rand -72: Walter J. Smith, III -74: Raymond A. Moulaison -75: Joe Harkins -76: Ray Scanlon -77: Walter R. McIlveen -78: Richard L. Logan -79: Laurence Dzaugis -80: John Kulig -81: Steve Greenberg -82: Paul Melnick -84: Benjamin R. Thompson -85: Douglas A. Baird -86: John Homko -87: Bertrand A. Stanfield-Pinel -88: John W. Watkins -89: Marston Duffy -90: Bernard O. Bachenheimer -91: Daniel Prior -92: Andrew Wemple -94: Stephen A. Swisher -95: Barry M. Hynds -96: Gary Bellinger -97: Charles Jutras -98: Hunt Sutherland -99: Robert Parnass -100: Davis Balestracci, Jr. -101: George Clark -102: David R. Lyons -104: Chester A. Kokoszka -105: William H. Murwin -106: Rich Ludorf -107: Peter J. Joyce -108: Richard B. Keeler -109: Richard Norton -110: Ed Prentice -111: Bruce MacWilliam -112: Jeffrey R. Tacconi -114: Kent Berwick -115: Robert Hart -116: Bob Andren -117: Robert Byron -118: Robert Pinolehto -119: Jonathan S. Kardell -120: James F. Lane -121: Jeff Birkner -122: Walter Hoenig -124: Jay Pulli -125: John G. Yamashita -126: James Roach -127: Don R. Lounsbury -128: Stewart D. King, Jr. -129: John Kowalonek -140: Charles R. Lauzon -141: John Kowalchuk -142: David Altieri -144: John Kuklewicz -145: Edwin L. Knight -146: Robert W. Horton -147: Mark J. Deutsch -148: Timothy R. Ascani -149: Robert Howard -150: Edward J. Smith -151: James Craffey -152: Vincent - Rucinski -154: David C Jones -155: Stephen D'allessandro -156: Mark Sturtevart -157: Martin Grossman -158: Randall C. LaPlante -159: Alan Turniansky -160: John P. Heslin -161: John F. Downes -162: Hector Cabrera -164: John Haponik -165: Timothy F. Bamford -166: Dan Gauvin -167: Glenn R. Baylis -168: Alfred J. Marotta -169: Adrian C. McIlveen -170: Iginio Capaldo -171: Glenn Cooley -172: Paul E. Craffey -174: James J. Collins -175: Raymond Elveson -176: Michael Bosowski -177: Dave Konieczny -178: John Pardo -179: David B. Fox -180: Conrad From -181: Robert T. Picchione -182: Jason P. Tuell -184: David Giusto -185: George F. Tobin, Jr. -186: Brian L. Stoffers -187: Steve Stocking -188: Jeffrey Bedell -189: Jim Conoby -190: Steven Duso -191: Halvard H. Solberg -192: Keith Duclos -194: Chester C. Przybylowicz, Jr. -194: Chester C. Przyb ylowicz, Jr. -195: Stephen Knight -196: Joseph A. Bischof -197: Fausto Garcia -198: Eric Standish -199: Eric Fischer -200: Robert Reinfurt, Jr. -201: John Giguere -202: Edward Hines -204: Robert B. Frazier -205: Joseph Zayonc -206: John Power -207: Mark J. Ramberg -208: Michael T. Treglia -209: Matt Goldman -210: Peter F. Young -211: John Cameron -212: Robert Overman -214: Chris Duggan -215: Daniel Alcombright -215: Daniel Alcomb right -216: Brian McLaughlin -217: Richard Thomas -218: Bruce Black -219: Andrew J. Krassowski -220: Stephen Fiorelli -221: Thomas Casale -222: Mark N. Ramburg -224: Keith R. Burkes -225: John F. Paladino -226: Dariusz Laskowski -227: John G. Antoniou -228: Robert P. Sawyer -229: John M. Summers -240: Mitch Wright -241: Steven Gardner -242: Robert E. Power -244: Robert J. Morteu -245: William E. Flynn, Jr. -246: Richard S. Hilow -247: William G. Andrews -248: David Sauerbrey -249: John R. Iannarone -250: Nicholas J. Barber -251: Daniel E. Holden -252: Steven Baturin -254: Mark Stanley -255: James A. Duncan -256: Ralph A. Casale -257: Bill Baron -258: Andy Cott -259: John E. Bozenhard -260: Brian J. Keogh -261: Don Waltman -262: Jeff Remillard -264: Arra Yeghiayan -265: John M. Scannell -266: Christopher J. Stakuitis -267: Richard A. Rydant -268: Michael Palmer -269: Edward J. Devin -270: George S. Graw -271: Quentin J. Brown -272: Jim Nichols -274: Robert G. Bergland -274: Rob ert G. Bergland -275: Steve Mann -276: John J. Niedzielski -277: Mike Salk -278: John Whyte -279: Curt Duffy -280: John Sieganthaler -281: Dennis Nagle -282: Gordon C. Griffin -284: Marc A. Viera -285: Elliot Scott -287: Chris MacDonnel -288: Jim Hannon -289: Tom Peterson -290: Dan Farkas -291: Jim Myran -294: A. Patrick Tormey -295: George Carey, Jr. -296: John McEnaney -297: Jeff Graves -298: Ara Barmakian -299: Matt Mooney -400: Mike Basmajian -401: Aswin Pinsuvana -402: Christopher Mastriani -404: Paul Amons -405: Thomas Tashjian -406: Gregory C. Thomson -407: Bill Iannacci -408: Vinay Kundaje -409: James Forbes -410: Frederick Zacharias -411: Gary Goodell -414: Dave Padley -415: Kyoyul Oh -416: Jay S. Nigen -417: Perter P. Polit -418: Dave Cubanski -419: David Smith -420: Sean P. Sweeney -421: Anthony Thomas Beville -422: Andy Siegel -424: James McDermott -425: Charles Cooleridge, Jr -426: Wayne C. Jarrett -427: Paul Lavigne -428: John Q. Bagdasarian -429: Mike Iannacci -440: Paul LoPiccolo -441: Mark Siciliano -442: Peter P. Perrotti -444: David M Beaudin -445: Mikey Pomerleau -446: Sergio Rivas -447: Christopher Charles Blume -448: James M. Mach -449: Kenneth James Carpenter -450: Keith Pflieger -451: Sean Hunt -452: Todd A Parker -454: Ken Hamilton -455: Rick Rogers -456: David A. DiBattista -457: Albert G. Prescot, 2nd -458: Kevin A. Bowen -459: Fred Gold -460: Brian Murphy -461: Jay Larrow -462: Douglas Lenox -464: John W. Boyle -465: Dave Stec -466: Michael Bowen -467: Andrew A. Aberdale -468: Timothy Tripoli -469: Keary J. Griffin -470: Scott MacKenzie -471: Jeff McConnell -472: Steve Bullied -474: Walter F. Daly III -475: Jonas Dedinas -476: Paul Cotellesso -477: Robert H. Elden -478: John P. Gasstrom -479: Michael J. Vinskus -480: Kevin E. Duprey -481: Christopher Caforio -482: John Wodziak -484: Daniel Falla -485: Mike Allen -486: Michael J Lemberger -487: Kevin M. Daniels -488: Daniel Whelan -489: Walden Leverich -490: Peter J. Anamasi -491: Daniel Falk -492: Chad Stein -494: James F. Sheehan -495: Jay McGaffigan -496: Corydon Shimer -497: Michael E. Henry -498: Matthew B. Teague -499: Patrick Cambell -500: Dan Levine -501: Eric J. Rosentel -502: Brian Malone -504: Christopher L. Savina -505: Mark Gibelli -506: Jeffrey S. Leiner -507: Christopher H. Patstone -508: Michael Schiller -509: Tim O'Connor -510: Ron Passerini -511: Daniel R. P. Spencer -512: Steven John Becker -514: Christopher Trotta -515: Brian Treece -516: David Joseph Colombo -517: Nate York -518: Kevin Geoffroy -519: Alfred J. Costa -520: John Swindell -521: Robert Tracy Moore -524: William H. Barry -525: Suppasak Collins -526: Jeremy Delorey -527: Kevin Waid -528: Thomas A. Pane -529: Lenard Soto -540: Adnan Mirza -541: Dinis Pimentel -542: Rob Stacy -544: Jeff Haye -546: Chris Kmiec -547: Greg H. Lichniak -548: Don Cournouyer -549: Michael A. Kimack -550: Brian Gerry -551: Chris Pisz -552: Chris Greatens -554: Aaron Eckstrom -555: Oral Allen -556: Ian Cote -557: Chris Roe -559: Jeffrey Modderno -560: Christopher R. Labossiere -561: Glen Gaebe -562: Michael E. Miller -564: Brian Karnes -565: Jason Paradis -566: Rob Jackson -567: Mark Tucker -568: Michael Frederick Teliszewski -569: Robert F. Tonning -570: Leo Gestetner -571: Richard Heidebrecht -572: Francis I. Bronco, Jr. -574: Jeffry Michael Proulx -575: John Kedziora -576: Jim Metzler -577: Jose Orbegozo -578: Jeff White -579: David LaCarubba, Jr -580: Brian Fuller -581: Luke Demoracski -582: Alex Patrao -584: Keith Thornley -585: Chris Kuiawa -586: Thomas E. Forcier -587: Joe Magnotti -588: Andrew Feld -589: Rob Foster -591: Michael L. Andrus -594: William Randal Gilbert -595: Nathan Hendrix -596: John Brian Stewart -597: Matt Tricomi -598: John B. Pieper -599: Brad Michaelis -600: Eric James Gusek -601: Arthur Rossomando -602: Doug Sullivan -604: Luke Poppish -605: Andre Orbegozo -606: Ken Knowles -607: Pete Wysocki -608: Jay Bourgeois -609: Brian Carey -610: Antonio Oses -611: Lino Brosco -612: Radoslav Viktorin -614: Theodoric Panton -615: Brian Bresnahan -616: Sean Gregory -617: Mark Forget -618: Istifan Ghanem -620: Joseph Maraia -621: Gerard C. Mangenot -622: Keven Carpenter -624: Mario Tongol -625: Jon Tanner -626: Matt Anselm -627: Randy McMahon -628: Bob Parette -629: Steve Milburn -640: John Stevens -641: David Wyman -642: Matt Craig -644: John Casill -645: Jamie Karl -646: Emar Tongol -647: Nick Amaral -648: Pete Tessier -649: Ken Ozeni -650: Nathan Campoli -651: Ravi Misra -652: Sam Girgis -654: Larry Ryan Coffren -655: David Silva -656: Brian Felouzis -657: Brendan Casey -658: Thomas Collins -659: Andy Kern -660: Ralph Thompson -661: Mike Bozzi -662: Pete Gonsalves -664: Tim Pedro -665: Ken Fountain -667: Deb Banerjee -668: Luke Smith -669: Jaseem Hasib -670: Sean Wickman -671: Will Alter -672: Andy Stone -674: Nate Thompson -675: Mark Sadberry -676: Jose Magararu -677: Rich Kuzsma -678: Andrew Houde -679: Jon Stambaugh -680: Andy DeMars -681: Scott Bowden -682: Josh Allor -684: Jeff Cusato -685: Chris Hill -686: Chandra M.V.S. -687: Edwin Martin -688: Joseph Ho -689: Jeff Camara -690: Adam Berman -691: Jordan Maddok -692: Jon Hurst -694: Mark Anderson -695: Patrick Pastecki -696: Brian Conway -697: Kevin Harsip -698: Jeffrey A. Fortin -699: Thomas F. Maguire -700: Jarred Gallagher -701: Jason Li -702: Anthony Snyder -704: Luis Costas -705: Steve Sprowson -706: Michael Andren -707: Shawn Purcell -708: David Belliveau -709: Brian Weindling -710: Adam Siegal -711: Luiz Santos -712: Kevin Derwin -714: Christopher Coy -715: Andre Quina -716: Chris Vigneau -717: Steven Kaelin -718: Tony Maietta -719: Gary Madirosian -720: Vincent Fusca -721: Jordan Weigler -722: Cory Melemed -724: Joel Gottshalk -725: Sid Rupani -726: Andy Kim -727: Domenic Giancola -728: William Espinola -729: Jesse Tippett -740: Eric Hall -741: Mike Carbonello -742: Joe Hsu -744: Jason Gronlund -745: Mike Frysinger -746: Mike Fortier -747: Matt Benvenuti -748: Edwin Nieves -749: Andrew Fowler -750: Rob Trotte -751: Dan Boothe -752: William Hebert -754: Jimmy Wang -755: Jordan Cormier -756: Jake Varney -757: Paul Marchetti -758: Mike Robinson -759: Dave Voutila -760: Jon Mulla -761: Brian Berk -762: Brian O'Donnell -764: Derek Chen -765: Tony Fortunato -766: Jeremy Cote -767: Yury Alkhazov -768: Nate Chin -769: Matt Densmore -770: Garrett Ebersole -771: Fred Vanston -772: Tofer Carlson -774: Greg Salvati -775: Ian Phillips -776: Jon Hurst -777: Marc Cyr -778: Colin Marker -779: Tim Krawiec -780: Marc DiNino -781: Doug Fritz -782: Genesis Quemuel -784: John Parretti -785: Sam Gambrell -786: Freddy Jervis -787: Pat Canny -788: John Rogers -789: Pratap Rao -790: Craig Gallot -791: Pat Kelly -792: Ryan Tully -794: Dan Bartl -795: Chris Donoghue -796: Pace Ricciardelli -797: Stuart Webster -798: John Folliard -799: Mike Padden -800: Justin Clouthier -801: Eric Koethe -802: Andy Kipp -804: Matthew Goon -805: Cameron Dunaj -806: Kenneth Dawe -807: Todd Bitner -808: Adam Tracy -809: Joe Bailey -810: Michael Fecteau -811: Noah Pendleton -812: Matthew Fuhrmeister -814: Kyle Kappmeyer -815: Anthony DiStefano -816: Barry Kosherick -817: Naren Nayak -818: Ryan Pagano -819: Dimosthenis Nikitas -820: Bernie Lis -821: Pat Sheehan -822: Justin Perron -824: Garrett Cavanaugh -825: Mike Moscardini -826: Brandon Patchel -827: Alex Demers -828: Skyler Clark -829: James Repass -840: Brendan McMasters -841: Gerard Dwan -842: Ben St. James -844: Peter Eliopoulos -845: Ben Buck -846: Albert Lo -847: John Durst -848: Matt Pasceri -849: Brian Barnhill -850: Kevin O'Brien -851: William Hnath -852: Stephen Jakubowski -854: Shaun Tirrell -855: Sam Kaplan -856: Tim McMath -857: Dimitris Saragas -858: Lawson Glidden -859: Sean Joyce -860: Andrew Labak -861: Hunter Senft-Grupp -862: Chris Szlatenyi -864: Patrick Benson -865: Charlie Fancher -866: Nathan Webb -867: Dylan Sweeney -868: Greg Walls -869: Jarrett Arredondo -870: Pat DeSantis -871: Alan Lazaros -872: Udit Adhikari -874: Alex Muir -875: Eric Murphy -876: Garret Doe -877: Rob Banahan -878: Nathan Kristoff -879: Andrew Yee -880: Brian Grabowski -881: Sean Beck -882: Meshal Alausfour -884: Manu Bhalla -885: Brendan Harris -886: Jared Brown -887: Dan Hartman -888: Michael Jenkins -889: John Wilder -890: Nicholas Allen -891: Isaac Barbour -892: Sean Crepeau -894: Ian Lonergan -895: Shane Daley -896: Patrick Knight -897: Jeffrey Laun -898: Migdoel Alvarado -899: John William Hatzis -900: Wyatt Brewster Gray -901: Alexander Jacob Misch -902: Edward Eric Allison -904: Keegan James Leitz -905: James Kirk Arsenault -906: Richard James Speranza -907: Alexander Alan Flores Padilla -908: Jeffrey Michael Perron -909: Nicholas Adams Silvia -910: Benjamin Scott Timms -911: Evangelos Konstantinos Koumbaros -912: Robert Steven Allen -914: Otilio Depina -915: Shane William Waterman -916: Zachary A. Duca -917: Robert Edward Puishys III -918: Michael Alexander Pelissari -919: Jonathan Boeglin -920: Brandon Amir Okray -921: Anthony Joseph DeCicco -922: William Henry Wright -924: Derek Allen Carey -925: Justin Mathews -927: Alex William Rebh -928: Ned Shelton -929: Shrid Ambady -940: Ryan Santos -941: Mark Mantell -942: Cy Ketchum -944: Pat Sheppard -945: TJ Watson -946: Jake Nutting -947: Matt Bailey -948: Will Bermender -949: Chris Dillon -950: Jon Zee -951: Mike Murillo -952: Connor Pugliese -954: Matt Roy -955: Eric Corriveau -956: Brian Eccles -957: Ryan Casey -958: Nate Sweet -959: Andy Larsen -960: Philip Radder -961: Christian Gonzalez -962: Nicholas Hewgley -964: William Thomas Frankian - 926 Eric Zandrow -965: Mark Swanson -966: Jeffrey Collard -967: John Scarborough -968: Nathan Ferron -969: Alex Henning -970: Zhi Hao "Ed" Li -972: Eric Willcox, III -974: David Kelly -975: Nathan Curtis -976: Jordan Brauer -977: Nicholas Rowles -978: Gavin Hayes -979: Andrew Davis -980: William Spurgeon -981: Pete Guarino -982: Robert Flemming - 971 Nicholas King -984: David Moore -985: Tommy Trieu -986: Christopher Connor -987: Tung Truong -988: Timothy Whitworth -989: Evan King -990: Matthew Carnein -991: Matthew Jackman -992: Jon Metzger -994: Benjamin Sarkis -995: Colin Maki -996: Brandon Coll -997: Timothy Jones -998: Andrew Aberdale II -999: Alexander Horton -1000: Daniel Lemire -1001: Drew Tisdelle -1002: Daniel Pongratz -1004: Luis Fernandez -1005: Mervyn Larrier -1006: Jeff Robinson -1007: Joey Perez -1008: Andrew Brunelle -1009: Ziyang "Gavin" Yu -1010: Tucker Martin -1011: Tejas Rao -1012: Matthew Collins -1014: Andrew Gregory -1015: John Bonina -1016: Sean McCluskey -1017: Jacob Rems -1018: James McAleese -1019: Jacob Henry -1020: James Taylor -1021: Ryan St. Hilaire -1022: Jimmy Tran -1024: Brent Reissman -1025: David Cadilek -1026: James Owens -1027: Diego Infanzon -1028: Trevor Alexander -1029: Nikolas Gamarra -1040: Ryan O'Brien -1041: Chenggu Wang -1041: Johnny Wang -1042: Howard Vance -1044: Evan Hasenfeld -1045: Kien Nhan +scroll~name +1~Richard Brodeur +2~Charles T. Kleman +4~Warren Bentley +5~John P. Gahagan +6~Robert G. Bertrand +7~Jeffrey Semmel +8~Steve Schwarm +9~Robert D. Hickey +10~Joseph G. Ferrara +11~Robert C. Bell +12~Bruce Tuttle +14~Robert Stessel +15~Steve Leece +16~William D. Smith +17~Alan D. Randall +18~Richard M. King +19~Henry S. Sweet +20~John Czajkowski +21~Robert Orenberg +22~Bill Belisle +24~Steve Erikson +25~Walter J. Miska +26~David C. Johnson +27~Peter Miner +28~Peter Blackford +29~Jamon Chan +40~James F. Sinnamon +41~Daniel B. Bentley +42~Paul J. Pakus +44~James A. Metzler +45~Mark Saviet +46~Bruce Bosserman +47~Ray W. Spalding +48~John A. Giordano +49~Capt. Michael Hughes +50~Robert N Ahern +51~Dr. Patric V. Romano +52~Kenneth R. Perkins +54~David L. Titterington, Jr. +55~Ronald C. Strand +56~Arthur E. Kreymer +57~John L. Boursy, Jr. +58~Mitchell Soivenski +59~William H. Weed +60~Paul Grady +61~Steven Chan +62~Thomas Seiferman +64~Dave Hayhurst +65~Michael W. Thayer +66~Jeffrey N. Clark +67~John D. Powers +68~Chuck Green +69~Charles R. Cain, Jr. +70~Timothy F. Laskowski +71~Gary Rand +72~Walter J. Smith, III +74~Raymond A. Moulaison +75~Joe Harkins +76~Ray Scanlon +77~Walter R. McIlveen +78~Richard L. Logan +79~Laurence Dzaugis +80~John Kulig +81~Steve Greenberg +82~Paul Melnick +84~Benjamin R. Thompson +85~Douglas A. Baird +86~John Homko +87~Bertrand A. Stanfield-Pinel +88~John W. Watkins +89~Marston Duffy +90~Bernard O. Bachenheimer +91~Daniel Prior +92~Andrew Wemple +94~Stephen A. Swisher +95~Barry M. Hynds +96~Gary Bellinger +97~Charles Jutras +98~Hunt Sutherland +99~Robert Parnass +100~Davis Balestracci, Jr. +101~George Clark +102~David R. Lyons +104~Chester A. Kokoszka +105~William H. Murwin +106~Rich Ludorf +107~Peter J. Joyce +108~Richard B. Keeler +109~Richard Norton +110~Ed Prentice +111~Bruce MacWilliam +112~Jeffrey R. Tacconi +114~Kent Berwick +115~Robert Hart +116~Bob Andren +117~Robert Byron +118~Robert Pinolehto +119~Jonathan S. Kardell +120~James F. Lane +121~Jeff Birkner +122~Walter Hoenig +124~Jay Pulli +125~John G. Yamashita +126~James Roach +127~Don R. Lounsbury +128~Stewart D. King, Jr. +129~John Kowalonek +140~Charles R. Lauzon +141~John Kowalchuk +142~David Altieri +144~John Kuklewicz +145~Edwin L. Knight +146~Robert W. Horton +147~Mark J. Deutsch +148~Timothy R. Ascani +149~Robert Howard +150~Edward J. Smith +151~James Craffey +152~Vincent - Rucinski +154~David C Jones +155~Stephen D'allessandro +156~Mark Sturtevart +157~Martin Grossman +158~Randall C. LaPlante +159~Alan Turniansky +160~John P. Heslin +161~John F. Downes +162~Hector Cabrera +164~John Haponik +165~Timothy F. Bamford +166~Dan Gauvin +167~Glenn R. Baylis +168~Alfred J. Marotta +169~Adrian C. McIlveen +170~Iginio Capaldo +171~Glenn Cooley +172~Paul E. Craffey +174~James J. Collins +175~Raymond Elveson +176~Michael Bosowski +177~Dave Konieczny +178~John Pardo +179~David B. Fox +180~Conrad From +181~Robert T. Picchione +182~Jason P. Tuell +184~David Giusto +185~George F. Tobin, Jr. +186~Brian L. Stoffers +187~Steve Stocking +188~Jeffrey Bedell +189~Jim Conoby +190~Steven Duso +191~Halvard H. Solberg +192~Keith Duclos +194~Chester C. Przybylowicz, Jr. +194~Chester C. Przyb ylowicz, Jr. +195~Stephen Knight +196~Joseph A. Bischof +197~Fausto Garcia +198~Eric Standish +199~Eric Fischer +200~Robert Reinfurt, Jr. +201~John Giguere +202~Edward Hines +204~Robert B. Frazier +205~Joseph Zayonc +206~John Power +207~Mark J. Ramberg +208~Michael T. Treglia +209~Matt Goldman +210~Peter F. Young +211~John Cameron +212~Robert Overman +214~Chris Duggan +215~Daniel Alcombright +215~Daniel Alcomb right +216~Brian McLaughlin +217~Richard Thomas +218~Bruce Black +219~Andrew J. Krassowski +220~Stephen Fiorelli +221~Thomas Casale +222~Mark N. Ramburg +224~Keith R. Burkes +225~John F. Paladino +226~Dariusz Laskowski +227~John G. Antoniou +228~Robert P. Sawyer +229~John M. Summers +240~Mitch Wright +241~Steven Gardner +242~Robert E. Power +244~Robert J. Morteu +245~William E. Flynn, Jr. +246~Richard S. Hilow +247~William G. Andrews +248~David Sauerbrey +249~John R. Iannarone +250~Nicholas J. Barber +251~Daniel E. Holden +252~Steven Baturin +254~Mark Stanley +255~James A. Duncan +256~Ralph A. Casale +257~Bill Baron +258~Andy Cott +259~John E. Bozenhard +260~Brian J. Keogh +261~Don Waltman +262~Jeff Remillard +264~Arra Yeghiayan +265~John M. Scannell +266~Christopher J. Stakuitis +267~Richard A. Rydant +268~Michael Palmer +269~Edward J. Devin +270~George S. Graw +271~Quentin J. Brown +272~Jim Nichols +274~Robert G. Bergland +274~Rob ert G. Bergland +275~Steve Mann +276~John J. Niedzielski +277~Mike Salk +278~John Whyte +279~Curt Duffy +280~John Sieganthaler +281~Dennis Nagle +282~Gordon C. Griffin +284~Marc A. Viera +285~Elliot Scott +287~Chris MacDonnel +288~Jim Hannon +289~Tom Peterson +290~Dan Farkas +291~Jim Myran +294~A. Patrick Tormey +295~George Carey, Jr. +296~John McEnaney +297~Jeff Graves +298~Ara Barmakian +299~Matt Mooney +400~Mike Basmajian +401~Aswin Pinsuvana +402~Christopher Mastriani +404~Paul Amons +405~Thomas Tashjian +406~Gregory C. Thomson +407~Bill Iannacci +408~Vinay Kundaje +409~James Forbes +410~Frederick Zacharias +411~Gary Goodell +414~Dave Padley +415~Kyoyul Oh +416~Jay S. Nigen +417~Perter P. Polit +418~Dave Cubanski +419~David Smith +420~Sean P. Sweeney +421~Anthony Thomas Beville +422~Andy Siegel +424~James McDermott +425~Charles Cooleridge, Jr +426~Wayne C. Jarrett +427~Paul Lavigne +428~John Q. Bagdasarian +429~Mike Iannacci +440~Paul LoPiccolo +441~Mark Siciliano +442~Peter P. Perrotti +444~David M Beaudin +445~Mikey Pomerleau +446~Sergio Rivas +447~Christopher Charles Blume +448~James M. Mach +449~Kenneth James Carpenter +450~Keith Pflieger +451~Sean Hunt +452~Todd A Parker +454~Ken Hamilton +455~Rick Rogers +456~David A. DiBattista +457~Albert G. Prescot, 2nd +458~Kevin A. Bowen +459~Fred Gold +460~Brian Murphy +461~Jay Larrow +462~Douglas Lenox +464~John W. Boyle +465~Dave Stec +466~Michael Bowen +467~Andrew A. Aberdale +468~Timothy Tripoli +469~Keary J. Griffin +470~Scott MacKenzie +471~Jeff McConnell +472~Steve Bullied +474~Walter F. Daly III +475~Jonas Dedinas +476~Paul Cotellesso +477~Robert H. Elden +478~John P. Gasstrom +479~Michael J. Vinskus +480~Kevin E. Duprey +481~Christopher Caforio +482~John Wodziak +484~Daniel Falla +485~Mike Allen +486~Michael J Lemberger +487~Kevin M. Daniels +488~Daniel Whelan +489~Walden Leverich +490~Peter J. Anamasi +491~Daniel Falk +492~Chad Stein +494~James F. Sheehan +495~Jay McGaffigan +496~Corydon Shimer +497~Michael E. Henry +498~Matthew B. Teague +499~Patrick Cambell +500~Dan Levine +501~Eric J. Rosentel +502~Brian Malone +504~Christopher L. Savina +505~Mark Gibelli +506~Jeffrey S. Leiner +507~Christopher H. Patstone +508~Michael Schiller +509~Tim O'Connor +510~Ron Passerini +511~Daniel R. P. Spencer +512~Steven John Becker +514~Christopher Trotta +515~Brian Treece +516~David Joseph Colombo +517~Nate York +518~Kevin Geoffroy +519~Alfred J. Costa +520~John Swindell +521~Robert Tracy Moore +524~William H. Barry +525~Suppasak Collins +526~Jeremy Delorey +527~Kevin Waid +528~Thomas A. Pane +529~Lenard Soto +540~Adnan Mirza +541~Dinis Pimentel +542~Rob Stacy +544~Jeff Haye +546~Chris Kmiec +547~Greg H. Lichniak +548~Don Cournouyer +549~Michael A. Kimack +550~Brian Gerry +551~Chris Pisz +552~Chris Greatens +554~Aaron Eckstrom +555~Oral Allen +556~Ian Cote +557~Chris Roe +559~Jeffrey Modderno +560~Christopher R. Labossiere +561~Glen Gaebe +562~Michael E. Miller +564~Brian Karnes +565~Jason Paradis +566~Rob Jackson +567~Mark Tucker +568~Michael Frederick Teliszewski +569~Robert F. Tonning +570~Leo Gestetner +571~Richard Heidebrecht +572~Francis I. Bronco, Jr. +574~Jeffry Michael Proulx +575~John Kedziora +576~Jim Metzler +577~Jose Orbegozo +578~Jeff White +579~David LaCarubba, Jr +580~Brian Fuller +581~Luke Demoracski +582~Alex Patrao +584~Keith Thornley +585~Chris Kuiawa +586~Thomas E. Forcier +587~Joe Magnotti +588~Andrew Feld +589~Rob Foster +591~Michael L. Andrus +594~William Randal Gilbert +595~Nathan Hendrix +596~John Brian Stewart +597~Matt Tricomi +598~John B. Pieper +599~Brad Michaelis +600~Eric James Gusek +601~Arthur Rossomando +602~Doug Sullivan +604~Luke Poppish +605~Andre Orbegozo +606~Ken Knowles +607~Pete Wysocki +608~Jay Bourgeois +609~Brian Carey +610~Antonio Oses +611~Lino Brosco +612~Radoslav Viktorin +614~Theodoric Panton +615~Brian Bresnahan +616~Sean Gregory +617~Mark Forget +618~Istifan Ghanem +620~Joseph Maraia +621~Gerard C. Mangenot +622~Keven Carpenter +624~Mario Tongol +625~Jon Tanner +626~Matt Anselm +627~Randy McMahon +628~Bob Parette +629~Steve Milburn +640~John Stevens +641~David Wyman +642~Matt Craig +644~John Casill +645~Jamie Karl +646~Emar Tongol +647~Nick Amaral +648~Pete Tessier +649~Ken Ozeni +650~Nathan Campoli +651~Ravi Misra +652~Sam Girgis +654~Larry Ryan Coffren +655~David Silva +656~Brian Felouzis +657~Brendan Casey +658~Thomas Collins +659~Andy Kern +660~Ralph Thompson +661~Mike Bozzi +662~Pete Gonsalves +664~Tim Pedro +665~Ken Fountain +667~Deb Banerjee +668~Luke Smith +669~Jaseem Hasib +670~Sean Wickman +671~Will Alter +672~Andy Stone +674~Nate Thompson +675~Mark Sadberry +676~Jose Magararu +677~Rich Kuzsma +678~Andrew Houde +679~Jon Stambaugh +680~Andy DeMars +681~Scott Bowden +682~Josh Allor +684~Jeff Cusato +685~Chris Hill +686~Chandra M.V.S. +687~Edwin Martin +688~Joseph Ho +689~Jeff Camara +690~Adam Berman +691~Jordan Maddok +692~Jon Hurst +694~Mark Anderson +695~Patrick Pastecki +696~Brian Conway +697~Kevin Harsip +698~Jeffrey A. Fortin +699~Thomas F. Maguire +700~Jarred Gallagher +701~Jason Li +702~Anthony Snyder +704~Luis Costas +705~Steve Sprowson +706~Michael Andren +707~Shawn Purcell +708~David Belliveau +709~Brian Weindling +710~Adam Siegal +711~Luiz Santos +712~Kevin Derwin +714~Christopher Coy +715~Andre Quina +716~Chris Vigneau +717~Steven Kaelin +718~Tony Maietta +719~Gary Madirosian +720~Vincent Fusca +721~Jordan Weigler +722~Cory Melemed +724~Joel Gottshalk +725~Sid Rupani +726~Andy Kim +727~Domenic Giancola +728~William Espinola +729~Jesse Tippett +740~Eric Hall +741~Mike Carbonello +742~Joe Hsu +744~Jason Gronlund +745~Mike Frysinger +746~Mike Fortier +747~Matt Benvenuti +748~Edwin Nieves +749~Andrew Fowler +750~Rob Trotte +751~Dan Boothe +752~William Hebert +754~Jimmy Wang +755~Jordan Cormier +756~Jake Varney +757~Paul Marchetti +758~Mike Robinson +759~Dave Voutila +760~Jon Mulla +761~Brian Berk +762~Brian O'Donnell +764~Derek Chen +765~Tony Fortunato +766~Jeremy Cote +767~Yury Alkhazov +768~Nate Chin +769~Matt Densmore +770~Garrett Ebersole +771~Fred Vanston +772~Tofer Carlson +774~Greg Salvati +775~Ian Phillips +776~Jon Hurst +777~Marc Cyr +778~Colin Marker +779~Tim Krawiec +780~Marc DiNino +781~Doug Fritz +782~Genesis Quemuel +784~John Parretti +785~Sam Gambrell +786~Freddy Jervis +787~Pat Canny +788~John Rogers +789~Pratap Rao +790~Craig Gallot +791~Pat Kelly +792~Ryan Tully +794~Dan Bartl +795~Chris Donoghue +796~Pace Ricciardelli +797~Stuart Webster +798~John Folliard +799~Mike Padden +800~Justin Clouthier +801~Eric Koethe +802~Andy Kipp +804~Matthew Goon +805~Cameron Dunaj +806~Kenneth Dawe +807~Todd Bitner +808~Adam Tracy +809~Joe Bailey +810~Michael Fecteau +811~Noah Pendleton +812~Matthew Fuhrmeister +814~Kyle Kappmeyer +815~Anthony DiStefano +816~Barry Kosherick +817~Naren Nayak +818~Ryan Pagano +819~Dimosthenis Nikitas +820~Bernie Lis +821~Pat Sheehan +822~Justin Perron +824~Garrett Cavanaugh +825~Mike Moscardini +826~Brandon Patchel +827~Alex Demers +828~Skyler Clark +829~James Repass +840~Brendan McMasters +841~Gerard Dwan +842~Ben St. James +844~Peter Eliopoulos +845~Ben Buck +846~Albert Lo +847~John Durst +848~Matt Pasceri +849~Brian Barnhill +850~Kevin O'Brien +851~William Hnath +852~Stephen Jakubowski +854~Shaun Tirrell +855~Sam Kaplan +856~Tim McMath +857~Dimitris Saragas +858~Lawson Glidden +859~Sean Joyce +860~Andrew Labak +861~Hunter Senft-Grupp +862~Chris Szlatenyi +864~Patrick Benson +865~Charlie Fancher +866~Nathan Webb +867~Dylan Sweeney +868~Greg Walls +869~Jarrett Arredondo +870~Pat DeSantis +871~Alan Lazaros +872~Udit Adhikari +874~Alex Muir +875~Eric Murphy +876~Garret Doe +877~Rob Banahan +878~Nathan Kristoff +879~Andrew Yee +880~Brian Grabowski +881~Sean Beck +882~Meshal Alausfour +884~Manu Bhalla +885~Brendan Harris +886~Jared Brown +887~Dan Hartman +888~Michael Jenkins +889~John Wilder +890~Nicholas Allen +891~Isaac Barbour +892~Sean Crepeau +894~Ian Lonergan +895~Shane Daley +896~Patrick Knight +897~Jeffrey Laun +898~Migdoel Alvarado +899~John William Hatzis +900~Wyatt Brewster Gray +901~Alexander Jacob Misch +902~Edward Eric Allison +904~Keegan James Leitz +905~James Kirk Arsenault +906~Richard James Speranza +907~Alexander Alan Flores Padilla +908~Jeffrey Michael Perron +909~Nicholas Adams Silvia +910~Benjamin Scott Timms +911~Evangelos Konstantinos Koumbaros +912~Robert Steven Allen +914~Otilio Depina +915~Shane William Waterman +916~Zachary A. Duca +917~Robert Edward Puishys III +918~Michael Alexander Pelissari +919~Jonathan Boeglin +920~Brandon Amir Okray +921~Anthony Joseph DeCicco +922~William Henry Wright +924~Derek Allen Carey +925~Justin Mathews +927~Alex William Rebh +928~Ned Shelton +929~Shrid Ambady +940~Ryan Santos +941~Mark Mantell +942~Cy Ketchum +944~Pat Sheppard +945~TJ Watson +946~Jake Nutting +947~Matt Bailey +948~Will Bermender +949~Chris Dillon +950~Jon Zee +951~Mike Murillo +952~Connor Pugliese +954~Matt Roy +955~Eric Corriveau +956~Brian Eccles +957~Ryan Casey +958~Nate Sweet +959~Andy Larsen +960~Philip Radder +961~Christian Gonzalez +962~Nicholas Hewgley +964~William Thomas Frankian - 926 Eric Zandrow +965~Mark Swanson +966~Jeffrey Collard +967~John Scarborough +968~Nathan Ferron +969~Alex Henning +970~Zhi Hao "Ed" Li +972~Eric Willcox, III +974~David Kelly +975~Nathan Curtis +976~Jordan Brauer +977~Nicholas Rowles +978~Gavin Hayes +979~Andrew Davis +980~William Spurgeon +981~Pete Guarino +982~Robert Flemming - 971 Nicholas King +984~David Moore +985~Tommy Trieu +986~Christopher Connor +987~Tung Truong +988~Timothy Whitworth +989~Evan King +990~Matthew Carnein +991~Matthew Jackman +992~Jon Metzger +994~Benjamin Sarkis +995~Colin Maki +996~Brandon Coll +997~Timothy Jones +998~Andrew Aberdale II +999~Alexander Horton +1000~Daniel Lemire +1001~Drew Tisdelle +1002~Daniel Pongratz +1004~Luis Fernandez +1005~Mervyn Larrier +1006~Jeff Robinson +1007~Joey Perez +1008~Andrew Brunelle +1009~Ziyang "Gavin" Yu +1010~Tucker Martin +1011~Tejas Rao +1012~Matthew Collins +1014~Andrew Gregory +1015~John Bonina +1016~Sean McCluskey +1017~Jacob Rems +1018~James McAleese +1019~Jacob Henry +1020~James Taylor +1021~Ryan St. Hilaire +1022~Jimmy Tran +1024~Brent Reissman +1025~David Cadilek +1026~James Owens +1027~Diego Infanzon +1028~Trevor Alexander +1029~Nikolas Gamarra +1040~Ryan O'Brien +1041~Chenggu Wang +1041~Johnny Wang +1042~Howard Vance +1044~Evan Hasenfeld +1045~Kien Nhan +1046~Sean Briggs +1047~Ian Sun +1048~Benjamin Duquette +1049~Mason Kolb +1050~Felix Sanchez +1051~Luke Gardner +1052~Jason Karlin +1054~Matthew Fortmeyer +1055~Matther Figueroa +1056~Nolan Greig +1057~Jack Riley +1058~Sean Brady +1059~James Kradjian +1060~Christopher Nelson +1061~Jonathan Ariza +1062~Matthew Scanlon +1064~Sean Cody +1065~James GoldStein +1066~Noah Baisden +1067~Daniel Corwin +1068~Phillipe Lessard +1069~Johnny Chea +1070~Jordan Gold +1071~Ryan Suarez +1072~Nate Diedrich +1074~Augustus Moseley +1075~Aidan Buffum +1076~Nick Odell +1077~Lucius Park +1078~Jacob Tutlis +1079~Alex Marzoratti +1080~Akash Shaji +1081~Suverino Frith +1082~Sam Talpey