479 lines
17 KiB
Python
479 lines
17 KiB
Python
|
import json
|
||
|
import praw
|
||
|
import oauthPS2Bot
|
||
|
import os
|
||
|
import re
|
||
|
import requests
|
||
|
import shlex
|
||
|
import sqlite3
|
||
|
import sys
|
||
|
import time
|
||
|
import traceback
|
||
|
from datetime import datetime,timedelta
|
||
|
import warnings
|
||
|
warnings.filterwarnings("ignore")
|
||
|
|
||
|
|
||
|
##############################################################################
|
||
|
## CONFIG
|
||
|
URL_CENSUS_CHAR_PC = 'http://census.daybreakgames.com/s:vAPP/get/ps2:v2/character/?name.first=%s&c:case=false&c:resolve=stat_history,faction,world,outfit_member_extended'
|
||
|
URL_CENSUS_CHAR_PS4_US = 'http://census.daybreakgames.com/s:vAPP/get/ps2ps4us:v2/character/?name.first=%s&c:case=false&c:resolve=stat_history,faction,world,outfit_member_extended'
|
||
|
URL_CENSUS_CHAR_PS4_EU = 'http://census.daybreakgames.com/s:vAPP/get/ps2ps4eu:v2/character/?name.first=%s&c:case=false&c:resolve=stat_history,faction,world,outfit_member_extended'
|
||
|
|
||
|
URL_CENSUS_CHAR_STAT_PC = 'http://census.daybreakgames.com/s:vAPP/get/ps2:v2/characters_stat?character_id=%s&c:limit=5000'
|
||
|
URL_CENSUS_CHAR_STAT_PS4_US = 'http://census.daybreakgames.com/s:vAPP/get/ps2ps4us:v2/characters_stat?character_id=%s&c:limit=5000'
|
||
|
URL_CENSUS_CHAR_STAT_PS4_EU = 'http://census.daybreakgames.com/s:vAPP/get/ps2ps4eu:v2/characters_stat?character_id=%s&c:limit=5000'
|
||
|
|
||
|
URL_SERVER_STATUS = 'https://census.daybreakgames.com/json/status?game=ps2'
|
||
|
|
||
|
URL_DASANFALL = "[[dasanfall]](http://stats.dasanfall.com/ps2/player/%s)"
|
||
|
URL_FISU = "[[fisu]](http://ps2.fisu.pw/player/?name=%s)"
|
||
|
URL_FISU_PS4_US = '[[fisu]](http://ps4us.ps2.fisu.pw/player/?name=%s)'
|
||
|
URL_FISU_PS4_EU = '[[fisu]](http://ps4eu.ps2.fisu.pw/player/?name=%s)'
|
||
|
URL_PSU = "[[psu]](http://www.planetside-universe.com/character-%s.php)"
|
||
|
URL_PLAYERS = "[[players]](https://www.planetside2.com/players/#!/%s)"
|
||
|
URL_KILLBOARD = "[[killboard]](https://www.planetside2.com/players/#!/%s/killboard)"
|
||
|
|
||
|
USERNAME = "ps2bot"
|
||
|
|
||
|
SERVERS = {
|
||
|
'1': 'Connery (US West)',
|
||
|
'17': 'Emerald (US East)',
|
||
|
'10': 'Miller (EU)',
|
||
|
'13': 'Cobalt (EU)',
|
||
|
'25': 'Briggs (AU)',
|
||
|
'19': 'Jaeger',
|
||
|
'1000': 'Genudine',
|
||
|
'1001': 'Palos',
|
||
|
'1002': 'Crux',
|
||
|
'2000': 'Ceres',
|
||
|
'2001': 'Lithcorp'
|
||
|
}
|
||
|
|
||
|
REPLY_TEXT_TEMPLATE = '''
|
||
|
**Some stats about {char_name_truecase} ({game_version}).**
|
||
|
|
||
|
------
|
||
|
|
||
|
- Character created: {char_creation}
|
||
|
- Last login: {char_login}
|
||
|
- Time played: {char_playtime} ({char_logins} login{login_plural})
|
||
|
- Battle rank: {char_rank}
|
||
|
- Faction: {char_faction_en}
|
||
|
- Server: {char_server}
|
||
|
- Outfit: {char_outfit}
|
||
|
- Score: {char_score} | Captured: {char_captures} | Defended: {char_defended}
|
||
|
- Medals: {char_medals} | Ribbons: {char_ribbons} | Certs: {char_certs}
|
||
|
- Kills: {char_kills} | Assists: {char_assists} | Deaths: {char_deaths} | KDR: {char_kdr}
|
||
|
- Links: {third_party_websites}
|
||
|
|
||
|
|
||
|
'''
|
||
|
|
||
|
REPLY_TEXT_FOOTER = '''
|
||
|
|
||
|
------
|
||
|
|
||
|
^^This ^^post ^^was ^^made ^^by ^^a ^^bot.
|
||
|
^^Have ^^feedback ^^or ^^a ^^suggestion?
|
||
|
[^^\[pm ^^the ^^creator\]]
|
||
|
(https://np.reddit.com/message/compose/?to=microwavable_spoon&subject=PS2Bot%20Feedback)
|
||
|
^^| [^^\[see ^^my ^^code\]](https://github.com/plasticantifork/PS2Bot)
|
||
|
'''
|
||
|
|
||
|
#### ####
|
||
|
# For FUNCTION_MAP, see the bottom of the file #
|
||
|
#### ####
|
||
|
COMMAND_IDENTIFIERS = ['/u/' + USERNAME, 'u/' + USERNAME]
|
||
|
COMMAND_IDENTIFIERS = [c.lower() for c in COMMAND_IDENTIFIERS]
|
||
|
MULTIPLE_COMMAND_JOINER = '\n \n_____\n_____\n \n'
|
||
|
|
||
|
## END OF CONFIG
|
||
|
##############################################################################
|
||
|
|
||
|
|
||
|
sql = sqlite3.connect((os.path.join(sys.path[0],'ps2bot-sql.db')))
|
||
|
cur = sql.cursor()
|
||
|
|
||
|
cur.execute('CREATE TABLE IF NOT EXISTS oldmentions(id TEXT)')
|
||
|
cur.execute('CREATE INDEX IF NOT EXISTS mentionindex on oldmentions(id)')
|
||
|
sql.commit()
|
||
|
|
||
|
print('logging in')
|
||
|
r = oauthPS2Bot.login()
|
||
|
#import bot
|
||
|
#r = bot.oG()
|
||
|
|
||
|
def now_stamp():
|
||
|
psttime = datetime.utcnow() - timedelta(hours=7)
|
||
|
time_stamp = psttime.strftime("%m-%d-%y %I:%M:%S %p PST ::")
|
||
|
return time_stamp
|
||
|
|
||
|
|
||
|
###############################################################################
|
||
|
## FUNCTIONMAP FUNCTIONS
|
||
|
## The arguments for these functions are provided by functionmap_line().
|
||
|
## Since we're working with user-provided input, we need to be ready to accept
|
||
|
## 0 - inf arguments. Thus, each function has default values for each parameter
|
||
|
## and a *trash bin where we can dump anything extra. This allows us to return
|
||
|
## when given insufficient input, and accept unlimited trash if we need to.
|
||
|
## Your function may take advantage of *trash if you want.
|
||
|
|
||
|
def generate_report_pc(charname=None, *trash):
|
||
|
if charname is None:
|
||
|
return
|
||
|
third_parties = [
|
||
|
{'url': URL_DASANFALL, 'identifier': 'char_id'},
|
||
|
{'url': URL_FISU, 'identifier': 'char_name'},
|
||
|
{'url': URL_PSU, 'identifier': 'char_id'},
|
||
|
{'url': URL_PLAYERS, 'identifier': 'char_id'},
|
||
|
{'url': URL_KILLBOARD, 'identifier': 'char_id'}
|
||
|
]
|
||
|
return generate_report(charname, URL_CENSUS_CHAR_PC, URL_CENSUS_CHAR_STAT_PC, third_parties, 'PC')
|
||
|
|
||
|
def generate_report_ps4_us(charname=None, *trash):
|
||
|
if charname is None:
|
||
|
return
|
||
|
third_parties = [
|
||
|
{'url': URL_FISU_PS4_US, 'identifier': 'char_name'}
|
||
|
]
|
||
|
return generate_report(charname, URL_CENSUS_CHAR_PS4_US, URL_CENSUS_CHAR_STAT_PS4_US, third_parties, 'PS4 US')
|
||
|
|
||
|
def generate_report_ps4_eu(charname=None, *trash):
|
||
|
if charname is None:
|
||
|
return
|
||
|
third_parties = [
|
||
|
{'url': URL_FISU_PS4_EU, 'identifier': 'char_name'}
|
||
|
]
|
||
|
return generate_report(charname, URL_CENSUS_CHAR_PS4_EU, URL_CENSUS_CHAR_STAT_PS4_EU, third_parties, 'PS4 EU')
|
||
|
|
||
|
def report_server_status(*trash):
|
||
|
status_updown = {'low': 'UP','medium': 'UP','high': 'UP','down': 'DOWN'}
|
||
|
status_pop = {'down': ''}
|
||
|
server_regions = {'Palos': 'Palos (US)','Genudine': 'Genudine (US)','Crux': 'Crux (US)'}
|
||
|
jcontent = json.loads(requests.get(URL_SERVER_STATUS).text)
|
||
|
results = []
|
||
|
|
||
|
def status_reader(jinfo, header):
|
||
|
table = []
|
||
|
entries = []
|
||
|
table.append(header)
|
||
|
table.append('\nserver | status | population')
|
||
|
table.append(':- | :- | :-')
|
||
|
|
||
|
for server, status in jinfo.items():
|
||
|
# These servers had their players migrated to other servers
|
||
|
# https://forums.daybreakgames.com/ps2/index.php?threads/ps4-game-update-2-8-12.231243/
|
||
|
if any(nonexist in server for nonexist in ['Dahaka', 'Xelas', 'Rashnu', 'Searhus']):
|
||
|
continue
|
||
|
server = server_regions.get(server, server)
|
||
|
pop = status['status']
|
||
|
updown = status_updown[pop]
|
||
|
pop = status_pop.get(pop, pop)
|
||
|
entries.append('%s | %s | %s' % (server, updown, pop))
|
||
|
|
||
|
entries.sort(key=lambda x: ('(US' in x, '(EU' in x, '(AU' in x, x), reverse=True)
|
||
|
table += entries
|
||
|
table.append('\n\n')
|
||
|
return table
|
||
|
|
||
|
results += status_reader(jcontent['ps2']['Live'], '**PC**')
|
||
|
results += status_reader(jcontent['ps2']['Live PS4'], '**PS4**')
|
||
|
results = '\n'.join(results)
|
||
|
return results
|
||
|
|
||
|
## END OF FUNCTIONMAP FUNCTIONS
|
||
|
###############################################################################
|
||
|
|
||
|
|
||
|
def generate_report(charname, url_census, url_statistics, third_parties, game_version):
|
||
|
try:
|
||
|
census_char = requests.get(url_census % charname)
|
||
|
census_char = census_char.text
|
||
|
census_char = json.loads(census_char)
|
||
|
except (IndexError, KeyError, requests.exceptions.HTTPError):
|
||
|
return None
|
||
|
|
||
|
if census_char['returned'] != 1:
|
||
|
# no player with this name was found
|
||
|
return
|
||
|
|
||
|
census_char = census_char['character_list'][0]
|
||
|
char_name_truecase = census_char['name']['first']
|
||
|
char_id = census_char['character_id']
|
||
|
try:
|
||
|
census_stat = requests.get(url_statistics % char_id)
|
||
|
census_stat = census_stat.text
|
||
|
census_stat = json.loads(census_stat)
|
||
|
if census_stat['returned'] == 0:
|
||
|
# When a player has an account, but never logged in / played,
|
||
|
# his stats page is empty and broken, so just return
|
||
|
return
|
||
|
except (IndexError, KeyError, requests.exceptions.HTTPError):
|
||
|
return
|
||
|
|
||
|
time_format = "%a, %b %d, %Y (%m/%d/%y), %I:%M:%S %p PST"
|
||
|
char_creation = time.strftime(time_format, time.localtime(float(census_char['times']['creation'])))
|
||
|
char_login = time.strftime(time_format, time.localtime(float(census_char['times']['last_login'])))
|
||
|
char_login_count = int(float(census_char['times']['login_count']))
|
||
|
char_hours, char_minutes = divmod(int(census_char['times']['minutes_played']), 60)
|
||
|
|
||
|
char_playtime = "{:,} hour{s}".format(char_hours, s='' if char_hours == 1 else 's')
|
||
|
char_playtime += " {:,} minute{s}".format(char_minutes, s='' if char_minutes == 1 else 's')
|
||
|
|
||
|
try:
|
||
|
char_score = int(census_char['stats']['stat_history'][8]['all_time'])
|
||
|
char_capture = int(census_char['stats']['stat_history'][3]['all_time'])
|
||
|
char_defend = int(census_char['stats']['stat_history'][4]['all_time'])
|
||
|
char_medal = int(census_char['stats']['stat_history'][6]['all_time'])
|
||
|
char_ribbon = int(census_char['stats']['stat_history'][7]['all_time'])
|
||
|
char_certs = int(census_char['stats']['stat_history'][1]['all_time'])
|
||
|
except (IndexError, KeyError, ValueError):
|
||
|
char_score = 0
|
||
|
char_capture = 0
|
||
|
char_defend = 0
|
||
|
char_medal = 0
|
||
|
char_ribbon = 0
|
||
|
char_certs = 0
|
||
|
|
||
|
char_rank = '%s' % census_char['battle_rank']['value']
|
||
|
char_rank_next = census_char['battle_rank']['percent_to_next']
|
||
|
if char_rank_next != "0":
|
||
|
char_rank += " (%s%% to next)" % char_rank_next
|
||
|
|
||
|
char_faction = census_char['faction']
|
||
|
try:
|
||
|
char_outfit = census_char['outfit_member']
|
||
|
if char_outfit['member_count'] != "1":
|
||
|
members = '{:,}'.format(int(char_outfit['member_count']))
|
||
|
char_outfit = '[%s] %s (%s members)' % (char_outfit['alias'], char_outfit['name'], members)
|
||
|
else:
|
||
|
char_outfit = '[%s] %s (1 member)' % (char_outfit['alias'], char_outfit['name'])
|
||
|
except KeyError:
|
||
|
char_outfit = "None"
|
||
|
|
||
|
try:
|
||
|
char_kills = int(census_char['stats']['stat_history'][5]['all_time'])
|
||
|
char_deaths = int(census_char['stats']['stat_history'][2]['all_time'])
|
||
|
if char_deaths != 0:
|
||
|
char_kdr = round(char_kills/char_deaths,3)
|
||
|
else:
|
||
|
char_kdr = char_kills
|
||
|
except (KeyError, ZeroDivisionError):
|
||
|
char_kills = 0
|
||
|
char_deaths = 0
|
||
|
char_kdr = 0
|
||
|
|
||
|
char_stat = census_stat['characters_stat_list']
|
||
|
#print(char_stat)
|
||
|
char_assists = 0
|
||
|
try:
|
||
|
for stat in char_stat:
|
||
|
if stat['stat_name'] == 'assist_count':
|
||
|
char_assists = int(stat['value_forever'])
|
||
|
break
|
||
|
except (IndexError, KeyError, ValueError):
|
||
|
char_assists = 0
|
||
|
|
||
|
third_parties_filled = []
|
||
|
for website in third_parties:
|
||
|
url = website['url']
|
||
|
if website['identifier'] == 'char_id':
|
||
|
url = url % char_id
|
||
|
elif website['identifier'] == 'char_name':
|
||
|
url = url % char_name_truecase
|
||
|
third_parties_filled.append(url)
|
||
|
third_parties_filled = ' '.join(third_parties_filled)
|
||
|
|
||
|
reply_text = REPLY_TEXT_TEMPLATE.format(
|
||
|
char_name_truecase = char_name_truecase,
|
||
|
game_version = game_version,
|
||
|
char_creation = char_creation,
|
||
|
char_login = char_login,
|
||
|
char_playtime = char_playtime,
|
||
|
char_logins = '{:,}'.format(char_login_count),
|
||
|
login_plural = 's' if char_login_count != 1 else '',
|
||
|
char_rank = char_rank,
|
||
|
char_faction_en = char_faction['name']['en'],
|
||
|
char_server = SERVERS[census_char['world_id']],
|
||
|
char_outfit = char_outfit,
|
||
|
char_score = '{:,}'.format(char_score),
|
||
|
char_captures ='{:,}'.format(char_capture),
|
||
|
char_defended = '{:,}'.format(char_defend),
|
||
|
char_medals = '{:,}'.format(char_medal),
|
||
|
char_ribbons = '{:,}'.format(char_ribbon),
|
||
|
char_certs = '{:,}'.format(char_certs),
|
||
|
char_kills = '{:,}'.format(char_kills),
|
||
|
char_assists = '{:,}'.format(char_assists),
|
||
|
char_deaths = '{:,}'.format(char_deaths),
|
||
|
char_kdr = '{:,}'.format(char_kdr),
|
||
|
third_party_websites = third_parties_filled
|
||
|
)
|
||
|
return reply_text
|
||
|
|
||
|
def handle_username_mention(mention, *trash):
|
||
|
#print('handling username mention', mention.id)
|
||
|
mention.mark_as_read()
|
||
|
|
||
|
try:
|
||
|
pauthor = mention.author.name
|
||
|
except AttributeError:
|
||
|
# Don't respond to deleted accounts
|
||
|
return
|
||
|
|
||
|
if pauthor.lower() == USERNAME.lower():
|
||
|
# Don't respond to yourself
|
||
|
return
|
||
|
|
||
|
cur.execute('SELECT * FROM oldmentions WHERE ID=?', [mention.id])
|
||
|
if cur.fetchone():
|
||
|
# Item is already in database
|
||
|
return
|
||
|
|
||
|
cur.execute('INSERT INTO oldmentions VALUES(?)', [mention.id])
|
||
|
sql.commit()
|
||
|
|
||
|
reply_text = functionmap_comment(mention.body)
|
||
|
|
||
|
if reply_text in [[], None]:
|
||
|
return
|
||
|
|
||
|
reply_text = MULTIPLE_COMMAND_JOINER.join(reply_text)
|
||
|
reply_text += REPLY_TEXT_FOOTER
|
||
|
#print('Generated reply text:', reply_text[:10])
|
||
|
|
||
|
print('%s Replying to %s by %s' % (now_stamp(), mention.id, pauthor))
|
||
|
try:
|
||
|
mention.reply(reply_text)
|
||
|
except praw.errors.PRAWException:
|
||
|
return
|
||
|
|
||
|
def functionmap_line(text):
|
||
|
#print('User said:', text)
|
||
|
elements = shlex.split(text)
|
||
|
#print('Broken into:', elements)
|
||
|
results = []
|
||
|
for element_index, element in enumerate(elements):
|
||
|
if element.lower() not in COMMAND_IDENTIFIERS:
|
||
|
continue
|
||
|
|
||
|
arguments = elements[element_index:]
|
||
|
assert arguments.pop(0).lower() in COMMAND_IDENTIFIERS
|
||
|
|
||
|
# If the user has multiple command calls on one line
|
||
|
# (Which is stupid but they might do it anyway)
|
||
|
# Let's only process one at a time please.
|
||
|
for argument_index, argument in enumerate(arguments):
|
||
|
if argument.lower() in COMMAND_IDENTIFIERS:
|
||
|
arguments = arguments[:argument_index]
|
||
|
break
|
||
|
|
||
|
#print('Found command:', arguments)
|
||
|
if len(arguments) == 0:
|
||
|
#print('Did nothing')
|
||
|
continue
|
||
|
|
||
|
command = arguments[0].lower()
|
||
|
actual_function = command in FUNCTION_MAP
|
||
|
function = FUNCTION_MAP.get(command, DEFAULT_FUNCTION)
|
||
|
#print('Using function:', function.__name__)
|
||
|
|
||
|
if actual_function:
|
||
|
# Currently, the first argument is the name of the command
|
||
|
# If we found an actual function, we can remove that
|
||
|
# (because add() doesn't need "add" as the first arg)
|
||
|
# If we're using the default, let's keep that first arg
|
||
|
# because it might be important.
|
||
|
arguments = arguments[1:]
|
||
|
result = function(*arguments)
|
||
|
#print('Output: %s' % result)
|
||
|
results.append(result)
|
||
|
return results
|
||
|
|
||
|
def functionmap_comment(comment):
|
||
|
lines = comment.split('\n')
|
||
|
results = []
|
||
|
for line in lines:
|
||
|
result = functionmap_line(line)
|
||
|
if result is None:
|
||
|
continue
|
||
|
result = list(filter(None, result))
|
||
|
if result is []:
|
||
|
continue
|
||
|
results += result
|
||
|
|
||
|
# If the user inputs the same command multiple times
|
||
|
# lets delete the duplicates
|
||
|
# We flip the list before and after so that dupes are removed
|
||
|
# from the back instead of front (because list.remove takes the
|
||
|
# first match)
|
||
|
results.reverse()
|
||
|
for item in results[:]:
|
||
|
if results.count(item) > 1:
|
||
|
results.remove(item)
|
||
|
results.reverse()
|
||
|
|
||
|
return results
|
||
|
|
||
|
def ps2bot():
|
||
|
print('checking unreads')
|
||
|
unreads = list(r.get_unread(limit=None))
|
||
|
mention_identifier = 'u/' + USERNAME.lower()
|
||
|
for message in unreads:
|
||
|
if mention_identifier in message.body.lower():
|
||
|
handle_username_mention(message)
|
||
|
else:
|
||
|
message.mark_as_read()
|
||
|
|
||
|
|
||
|
|
||
|
# This must be defined down here because it can't come before
|
||
|
# the function definitions (or else NameError)
|
||
|
DEFAULT_FUNCTION = generate_report_pc
|
||
|
FUNCTION_MAP = {
|
||
|
'!player': generate_report_pc,
|
||
|
'!p': generate_report_pc,
|
||
|
|
||
|
'!playerps4us': generate_report_ps4_us,
|
||
|
'!ps4us': generate_report_ps4_us,
|
||
|
'!p4us': generate_report_ps4_us,
|
||
|
|
||
|
'!playerps4eu': generate_report_ps4_eu,
|
||
|
'!ps4eu': generate_report_ps4_eu,
|
||
|
'!p4eu': generate_report_ps4_eu,
|
||
|
|
||
|
'!status': report_server_status,
|
||
|
'!s': report_server_status
|
||
|
}
|
||
|
# lowercase it baby
|
||
|
FUNCTION_MAP = {c.lower():FUNCTION_MAP[c] for c in FUNCTION_MAP}
|
||
|
|
||
|
|
||
|
|
||
|
try:
|
||
|
ps2bot()
|
||
|
except requests.exceptions.HTTPError:
|
||
|
print(now_stamp(), 'A site/service is down. Probably Reddit.')
|
||
|
except Exception:
|
||
|
traceback.print_exc()
|
||
|
|
||
|
#SAMPLES = [
|
||
|
#'u/ps2bot higby',
|
||
|
#'/u/ps2bot higby',
|
||
|
#'/u/PS2BOT higby',
|
||
|
#'/u/ps2bot !player higby',
|
||
|
#'/u/ps2bot !PLAYER higby',
|
||
|
#'/u/ps2bot higby /u/ps2bot !player higby',
|
||
|
#'/u/ps2bot',
|
||
|
#'/u/ps2bot !s !s !s !s',
|
||
|
#'/u/ps2bot !p4us bloodwolf\n/u/ps2bot !p bloodwolf\n/u/ps2bot !s',
|
||
|
#]
|
||
|
#for sample in SAMPLES:
|
||
|
# result = functionmap_comment(sample)
|
||
|
# #print(result)
|
||
|
# if result in [[], None]:
|
||
|
# continue
|
||
|
# message = MULTIPLE_COMMAND_JOINER.join(result)
|
||
|
# message += REPLY_TEXT_FOOTER
|
||
|
# print(message)
|