Added config options & basic single-file executable
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
from . import bot
|
||||
|
||||
if __name__ == '__main__':
|
||||
bot.run()
|
||||
@@ -13,9 +13,9 @@ from . import config, database, level, reply
|
||||
USER_AGENT = 'PointsBot (by u/GlipGlorp7)'
|
||||
|
||||
# TODO put this in config
|
||||
LOG_FILEPATH = os.path.abspath(os.path.join(os.path.expanduser('~'),
|
||||
'.pointsbot',
|
||||
'pointsbot.log.txt'))
|
||||
# LOG_FILEPATH = os.path.abspath(os.path.join(os.path.expanduser('~'),
|
||||
# '.pointsbot',
|
||||
# 'pointsbot.log'))
|
||||
|
||||
# The pattern that determines whether a post is marked as solved
|
||||
# Could also use re.IGNORECASE flag instead
|
||||
@@ -26,14 +26,13 @@ MOD_SOLVED_PAT = re.compile('/[Ss]olved')
|
||||
|
||||
|
||||
def run():
|
||||
logging.basicConfig(filename=LOG_FILEPATH,
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s %(module)s:%(levelname)s: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S')
|
||||
|
||||
print_welcome_message()
|
||||
|
||||
cfg = config.load()
|
||||
logging.basicConfig(filename=cfg.log_path,
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s %(levelname)s:%(module)s: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S')
|
||||
levels = cfg.levels
|
||||
db = database.Database(cfg.database_path)
|
||||
|
||||
@@ -78,46 +77,43 @@ def monitor_comments(subreddit, db, levels):
|
||||
logging.info('Received comment')
|
||||
# TODO more debug info about comment, eg author
|
||||
logging.debug('Comment text: "%s"', comm.body)
|
||||
# print_level(0, '\nFound comment')
|
||||
# print_level(1, f'Comment text: "{comm.body}"')
|
||||
|
||||
if not marks_as_solved(comm):
|
||||
logging.info('Comment does not mark issue as solved')
|
||||
# print_level(1, 'Not a "![Ss]olved" comment')
|
||||
continue
|
||||
|
||||
logging.info('Comment marks issues as solved')
|
||||
|
||||
if is_mod_comment(comm):
|
||||
logging.info('Comment was submitted by mod')
|
||||
# print_level(1, 'Mod comment')
|
||||
elif not is_first_solution(comm):
|
||||
# Skip this "!solved" comment
|
||||
logging.info('Comment is NOT the first to mark the issue as solved')
|
||||
# print_level(1, 'Not the first solution')
|
||||
continue
|
||||
|
||||
logging.info('Comment is the first to mark the issue as solved')
|
||||
# print_level(1, 'This is the first solution found')
|
||||
log_solution_info(comm)
|
||||
|
||||
solver = find_solver(comm)
|
||||
db.add_point(solver)
|
||||
logging.info('Added point for user "%s"', solver.name)
|
||||
# print_level(1, f'Added point for {solver.name}')
|
||||
|
||||
points = db.get_points(solver)
|
||||
logging.info('Total points for user "%s": %d', solver.name, points)
|
||||
# print_level(1, f'Total points for {solver.name}: {points}')
|
||||
level_info = level.user_level_info(points, levels)
|
||||
|
||||
# Reply to the comment marking the submission as solved
|
||||
reply_body = reply.make(solver, points, level_info)
|
||||
reply_body = reply.make(
|
||||
solver,
|
||||
points,
|
||||
level_info,
|
||||
feedback_url=cfg.feedback_url,
|
||||
scoreboard_url=cfg.scoreboard_url
|
||||
)
|
||||
try:
|
||||
comm.reply(reply_body)
|
||||
logging.info('Replied to the comment')
|
||||
logging.debug('Reply body: %s', reply_body)
|
||||
# print_level(1, f'Replied to comment with: "{reply_body}"')
|
||||
except praw.exceptions.APIException as e:
|
||||
logging.error('Unable to reply to comment: %s', e)
|
||||
db.remove_point(solver)
|
||||
@@ -130,7 +126,6 @@ def monitor_comments(subreddit, db, levels):
|
||||
if lvl and lvl.points == points:
|
||||
logging.info('User reached level: %s', lvl.name)
|
||||
if not subreddit.moderator(redditor=solver):
|
||||
# print_level(2, 'Setting flair')
|
||||
logging.info('User is not mod; setting flair')
|
||||
logging.info('Flair text: %s', lvl.name)
|
||||
logging.info('Flair template ID: %s', lvl.flair_template_id)
|
||||
@@ -208,10 +203,7 @@ def print_welcome_message():
|
||||
'sent to the developer by reporting an issue on the Github page.')
|
||||
print('\nFuture updates will hopefully resolve these issues, but for the '
|
||||
"moment, this is what we've got to work with! :)\n")
|
||||
|
||||
|
||||
def print_level(num_levels, string):
|
||||
print('\t' * num_levels + string)
|
||||
print_separator_line()
|
||||
|
||||
|
||||
def log_solution_info(comm):
|
||||
|
||||
@@ -13,30 +13,57 @@ DATADIR = os.path.join(os.path.expanduser('~'), '.pointsbot')
|
||||
CONFIGPATH = os.path.join(DATADIR, 'pointsbot.toml')
|
||||
|
||||
# Path to the sample config file
|
||||
# Unused for now
|
||||
SAMPLEPATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
'..',
|
||||
'pointsbot.sample.toml'))
|
||||
|
||||
### Primary Functions ###
|
||||
|
||||
|
||||
def load(filepath=CONFIGPATH):
|
||||
# Prompt user for config values if file doesn't exist
|
||||
if not os.path.exists(filepath):
|
||||
datadir = os.path.dirname(filepath)
|
||||
if not os.path.exists(datadir):
|
||||
os.makedirs(datadir)
|
||||
interactive_config(filepath)
|
||||
|
||||
return Config.from_toml(filepath)
|
||||
|
||||
|
||||
### Classes ###
|
||||
|
||||
|
||||
class Config:
|
||||
|
||||
# Default config vals
|
||||
DEFAULT_DBNAME = 'pointsbot.db'
|
||||
DEFAULT_DB_NAME = 'pointsbot.db'
|
||||
DEFAULT_LOG_NAME = 'pointsbot.log'
|
||||
|
||||
def __init__(self, filepath, subreddit, client_id, client_secret, username,
|
||||
password, levels, database_path=None):
|
||||
password, levels, database_path=None, log_path=None,
|
||||
feedback_url=None, scoreboard_url=None):
|
||||
self._filepath = filepath
|
||||
self._dirname = os.path.dirname(filepath)
|
||||
|
||||
if not log_path:
|
||||
log_path = os.path.join(self._dirname, self.DEFAULT_LOG_NAME)
|
||||
elif os.path.isdir(log_path):
|
||||
log_path = os.path.join(log_path, self.DEFAULT_LOG_NAME)
|
||||
self.log_path = log_path
|
||||
|
||||
# TODO init logging here so it can be used immediately?
|
||||
|
||||
if not database_path:
|
||||
database_path = os.path.join(self._dirname, self.DEFAULT_DBNAME)
|
||||
database_path = os.path.join(self._dirname, self.DEFAULT_DB_NAME)
|
||||
elif os.path.isdir(database_path):
|
||||
database_path = os.path.join(database_path, self.DEFAULT_DBNAME)
|
||||
database_path = os.path.join(database_path, self.DEFAULT_DB_NAME)
|
||||
self.database_path = database_path
|
||||
|
||||
self.subreddit = subreddit
|
||||
self.feedback_url = feedback_url
|
||||
self.scoreboard_url = scoreboard_url
|
||||
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
@@ -62,6 +89,10 @@ class Config:
|
||||
if dbpath:
|
||||
dbpath = os.path.abspath(os.path.expandvars(os.path.expanduser(dbpath)))
|
||||
|
||||
logpath = obj['filepaths']['log']
|
||||
if logpath:
|
||||
logpath = os.path.abspath(os.path.expandvars(os.path.expanduser(logpath)))
|
||||
|
||||
return cls(
|
||||
filepath,
|
||||
obj['core']['subreddit'],
|
||||
@@ -71,12 +102,15 @@ class Config:
|
||||
obj['credentials']['password'],
|
||||
levels,
|
||||
database_path=dbpath,
|
||||
log_path=logpath,
|
||||
feedback_url=obj['links']['feedback'],
|
||||
scoreboard_url=obj['links']['scoreboard'],
|
||||
)
|
||||
|
||||
def save(self):
|
||||
obj = deepcopy(vars(self))
|
||||
orig_levels = obj['levels']
|
||||
obj['levels'] = []
|
||||
orig_levels, obj['levels'] = obj['levels'], []
|
||||
# obj['levels'] = []
|
||||
for level in orig_levels:
|
||||
obj['levels'].append({
|
||||
'name': level.name,
|
||||
@@ -88,20 +122,6 @@ class Config:
|
||||
toml.dump(obj, f)
|
||||
|
||||
|
||||
### Functions ###
|
||||
|
||||
|
||||
def load(filepath=CONFIGPATH):
|
||||
# Prompt user for config values if file doesn't exist
|
||||
if not os.path.exists(filepath):
|
||||
datadir = os.path.dirname(filepath)
|
||||
if not os.path.exists(datadir):
|
||||
os.makedirs(datadir)
|
||||
interactive_config(filepath)
|
||||
|
||||
return Config.from_toml(filepath)
|
||||
|
||||
|
||||
### Interactive Config Editing ###
|
||||
|
||||
|
||||
@@ -113,14 +133,25 @@ def interactive_config(dest):
|
||||
'levels': [],
|
||||
}
|
||||
|
||||
print('#' * 80 + '\nCONFIGURING THE BOT\n' + '#' * 80)
|
||||
print('\nType a value for each field, then press enter.')
|
||||
print('\n' + ('#' * 80) + '\nCONFIGURING THE BOT\n' + ('#' * 80) + '\n')
|
||||
print('Type a value for each field, then press enter.')
|
||||
print('\nIf a field is specified as optional, then you can skip it by just '
|
||||
'pressing enter.\n')
|
||||
'pressing enter.')
|
||||
print("\nIt is recommended that you skip any fields that you aren't sure "
|
||||
'about')
|
||||
|
||||
print('\n*** Core Configuration ***\n')
|
||||
configvals['core']['subreddit'] = input('name of subreddit to monitor? ')
|
||||
print()
|
||||
configvals['filepaths']['database'] = input('database filename? (optional) ')
|
||||
configvals['filepaths']['database'] = input('database filepath? (optional) ')
|
||||
configvals['filepaths']['log'] = input('log filepath? (optional) ')
|
||||
|
||||
print('\n*** Website Links ***\n')
|
||||
print('These values should only be provided if you have valid URLs for '
|
||||
'websites that provide these services for your subreddit.\n')
|
||||
configvals['links']['feedback'] = input('feedback webpage URL? (optional) ')
|
||||
configvals['links']['scoreboard'] = input('scoreboard webpage URL? (optional) ')
|
||||
|
||||
print('\n*** Bot account details ***\n')
|
||||
configvals['credentials']['client_id'] = input('client_id? ')
|
||||
configvals['credentials']['client_secret'] = input('client_secret? ')
|
||||
@@ -144,18 +175,16 @@ def interactive_config(dest):
|
||||
print('\nFor any more questions, please refer to the README on the Github '
|
||||
'page.')
|
||||
|
||||
# add_another_level = True
|
||||
response = 'y'
|
||||
while response.lower().startswith('y'):
|
||||
print('\n*** Adding a level ***')
|
||||
print('\n*** Adding a level ***\n')
|
||||
level = {}
|
||||
level['name'] = input('\nLevel name? ')
|
||||
level['name'] = input('Level name? ')
|
||||
level['points'] = int(input('Level points? '))
|
||||
level['flair_template_id'] = input('Flair template ID? (optional) ')
|
||||
configvals['levels'].append(level)
|
||||
|
||||
response = input('\nAdd another level? (y/n) ')
|
||||
# add_another_level = response.lower().startswith('y')
|
||||
|
||||
with open(dest, 'w') as f:
|
||||
toml.dump(configvals, f)
|
||||
|
||||
@@ -8,14 +8,14 @@ FILLED_SYMBOL = '\u25AE' # A small filled box character
|
||||
EMPTY_SYMBOL = '\u25AF' # A same-sized empty box character
|
||||
|
||||
# Number of "excess" points should be greater than max level points
|
||||
EXCESS_POINTS = 100 # TODO move this to level?
|
||||
EXCESS_POINTS = 100 # TODO move this to level and/or config?
|
||||
EXCESS_SYMBOL = '\u2605' # A star character
|
||||
EXCESS_SYMBOL_TITLE = 'a star' # Used in comment body
|
||||
|
||||
### Main Functions ###
|
||||
|
||||
|
||||
def make(redditor, points, level_info):
|
||||
def make(redditor, points, level_info, feedback_url=None, scoreboard_url=None):
|
||||
paras = [header()]
|
||||
|
||||
if points == 1:
|
||||
@@ -45,7 +45,7 @@ def make(redditor, points, level_info):
|
||||
|
||||
paras.append(points_status(redditor, points, level_info))
|
||||
paras.append(divider())
|
||||
paras.append(footer())
|
||||
paras.append(footer(feedback_url=feedback_url, scoreboard_url=scoreboard_url))
|
||||
return '\n\n'.join(paras)
|
||||
|
||||
|
||||
@@ -130,10 +130,21 @@ def divider():
|
||||
return '***'
|
||||
|
||||
|
||||
def footer():
|
||||
return ('^(Bot maintained by GlipGlorp7 '
|
||||
'| [Scoreboard](https://points.minecrafthelp.co.uk) '
|
||||
'| [Feedback](https://forms.gle/m94aGjFQwGopqQ836) '
|
||||
'| [Source Code](https://github.com/cur33/PointsBot))')
|
||||
def footer(feedback_url=None, scoreboard_url=None):
|
||||
footer_sections = ['Bot maintained by GlipGlorp7']
|
||||
if scoreboard_url:
|
||||
# https://points.minecrafthelp.co.uk
|
||||
footer_sections.append(f'[Scoreboard]({scoreboard_url})')
|
||||
if feedback_url:
|
||||
# https://forms.gle/m94aGjFQwGopqQ836
|
||||
footer_sections.append(f'[Feedback]({feedback_url})')
|
||||
footer_sections.append('[Source Code](https://github.com/cur33/PointsBot)')
|
||||
|
||||
return '^(' + ' | '.join(footer_sections) + ')'
|
||||
|
||||
# return ('^(Bot maintained by GlipGlorp7 '
|
||||
# '| [Scoreboard](https://points.minecrafthelp.co.uk) '
|
||||
# '| [Feedback](https://forms.gle/m94aGjFQwGopqQ836) '
|
||||
# '| [Source Code](https://github.com/cur33/PointsBot))')
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user