Changed license and refactored
This commit is contained in:
@@ -1,18 +1 @@
|
||||
'''
|
||||
A bot for Reddit to award points to helpful subreddit members.
|
||||
Copyright (C) 2020 Collin U. Rapp
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
from .bot import run
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
'''
|
||||
A bot for Reddit to award points to helpful subreddit members.
|
||||
Copyright (C) 2020 Collin U. Rapp
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
from . import bot
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
'''
|
||||
A bot for Reddit to award points to helpful subreddit members.
|
||||
Copyright (C) 2020 Collin U. Rapp
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
import configparser
|
||||
import re
|
||||
|
||||
import praw
|
||||
@@ -23,38 +7,57 @@ from . import comment, database
|
||||
|
||||
### Globals ###
|
||||
|
||||
CONFIGPATH = 'pointsbot.ini'
|
||||
|
||||
# SUBREDDIT_NAME = 'MinecraftHelp'
|
||||
SUBREDDIT_NAME = 'GlipGlorp7BotTests'
|
||||
PRAW_SITE_NAME = 'testbot'
|
||||
# PRAW_SITE_NAME = 'bot'
|
||||
|
||||
# TODO Make LEVELS a dict instead
|
||||
# TODO Could also make a Level class or namedtuple that contains more info, e.g.
|
||||
# flair css or template id
|
||||
"""
|
||||
LEVELS = [
|
||||
('Helper', 5),
|
||||
('Trusted Helper', 15),
|
||||
('Super Helper', 40),
|
||||
]
|
||||
"""
|
||||
|
||||
### Main Function ###
|
||||
|
||||
|
||||
def run():
|
||||
# Connect to Reddit
|
||||
reddit = praw.Reddit(site_name=PRAW_SITE_NAME)
|
||||
subreddit = reddit.subreddit(SUBREDDIT_NAME)
|
||||
config = configparser.ConfigParser()
|
||||
config.read(CONFIGPATH)
|
||||
|
||||
# xdebugx
|
||||
print(f'Connected to reddit as {reddit.user.me()}')
|
||||
# Get the user flair levels in ascending order by point value
|
||||
# TODO Make levels a dict instead
|
||||
print(config.options('Levels'))
|
||||
|
||||
levels = []
|
||||
for opt in config.options('Levels'):
|
||||
levels.append((opt, config.getint('Levels', opt)))
|
||||
levels.sort(key=lambda pair: pair[1])
|
||||
print(levels)
|
||||
|
||||
# levels = [(key, int(val)) for key, val in config.items('Levels')]
|
||||
# levels = sorted(levels, key=lambda keyval: keyval[1])
|
||||
|
||||
#levels = sorted(config.items('Levels'), key=lambda pair: pair[1])
|
||||
|
||||
# Connect to Reddit
|
||||
reddit = praw.Reddit(site_name=config['Core']['praw_site_name'])
|
||||
subreddit = reddit.subreddit(config['Core']['subreddit_name'])
|
||||
|
||||
print(f'Connected to Reddit as {reddit.user.me()}')
|
||||
print(f'Read-only? {reddit.read_only}')
|
||||
print(f'Watching subreddit {subreddit.title}')
|
||||
print(f'Mod? {bool(subreddit.moderator(redditor=reddit.user.me()))}')
|
||||
print(f'Is mod? {bool(subreddit.moderator(redditor=reddit.user.me()))}')
|
||||
|
||||
database.init()
|
||||
|
||||
# Initialize database
|
||||
#if not database.exists():
|
||||
#database.create()
|
||||
# TODO pass database path instead of setting global variable
|
||||
# database.DB_PATH = config['Core']['database_name']
|
||||
# database.init()
|
||||
db = database.Database(config['Core']['database_name'])
|
||||
|
||||
# The pattern to look for in comments when determining whether to award a point
|
||||
# solved_pat = re.compile('!solved', re.IGNORECASE)
|
||||
@@ -62,7 +65,6 @@ def run():
|
||||
|
||||
# Monitor new comments for confirmed solutions
|
||||
for comm in subreddit.stream.comments(skip_existing=True):
|
||||
# xdebugx
|
||||
print('Found comment')
|
||||
print(f'Comment text: "{comm.body}"')
|
||||
|
||||
@@ -75,11 +77,6 @@ def run():
|
||||
# Search the flattened comments tree
|
||||
is_first_solution = True
|
||||
for subcomm in submission.comments.list():
|
||||
# if (subcomm.is_submitter
|
||||
# and subcomm.id != comm.id
|
||||
# and not subcomm.is_root
|
||||
# and solved_pat.search(subcomm.body)
|
||||
# and subcomm.created_utc < comm.created_utc):
|
||||
if (subcomm.id != comm.id
|
||||
and marks_as_solved(subcomm, solved_pat)
|
||||
and subcomm.created_utc < comm.created_utc):
|
||||
@@ -90,28 +87,26 @@ def run():
|
||||
|
||||
if not is_first_solution:
|
||||
# Skip this "!solved" comment and wait for the next
|
||||
# xdebugx
|
||||
print('This is not the first solution')
|
||||
continue
|
||||
|
||||
# xdebugx
|
||||
print('This is the first solution')
|
||||
print_solution_info(comm)
|
||||
|
||||
solution = comm.parent()
|
||||
solver = solution.author
|
||||
print(f'Adding point for {solver.name}')
|
||||
database.add_point(solver)
|
||||
points = database.get_redditor_points(solver)
|
||||
db.add_point(solver)
|
||||
points = db.get_points(solver)
|
||||
print(f'Points for {solver.name}: {points}')
|
||||
|
||||
# Reply to the comment containing the solution
|
||||
reply_body = comment.make(solver, points, LEVELS)
|
||||
reply_body = comment.make(solver, points, levels)
|
||||
print(f'Replying with: "{reply_body}"')
|
||||
solution.reply(reply_body)
|
||||
|
||||
# Check if (non-mod) user flair should be updated to new level
|
||||
for levelname, levelpoints in LEVELS:
|
||||
for levelname, levelpoints in levels:
|
||||
# If the redditor's points total is equal to one of the levels,
|
||||
# that means they just reached that level
|
||||
if points == levelpoints:
|
||||
@@ -122,13 +117,11 @@ def run():
|
||||
print(f'Setting flair text to {levelname}')
|
||||
subreddit.flair.set(solver, text=levelname)
|
||||
else:
|
||||
# xdebugx
|
||||
print('Don\'t update flair b/c user is mod')
|
||||
|
||||
# Don't need to check the rest of the levels
|
||||
break
|
||||
else:
|
||||
# xdebugx
|
||||
print('Not a "!solved" comment')
|
||||
|
||||
|
||||
@@ -139,7 +132,7 @@ def marks_as_solved(comment, solved_pattern):
|
||||
'''Return True if not top-level comment, from OP, contains "!Solved"; False
|
||||
otherwise.
|
||||
'''
|
||||
#comment.refresh()
|
||||
#comment.refresh() # probably not needed, but the docs are a tad unclear
|
||||
return (not comment.is_root
|
||||
and comment.is_submitter
|
||||
and solved_pattern.search(comment.body))
|
||||
|
||||
@@ -1,21 +1,3 @@
|
||||
'''
|
||||
A bot for Reddit to award points to helpful subreddit members.
|
||||
Copyright (C) 2020 Collin U. Rapp
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
### Globals ###
|
||||
|
||||
FILLED_SYMBOL = '\u25AE' # A small filled box character
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
'''
|
||||
A bot for Reddit to award points to helpful subreddit members.
|
||||
Copyright (C) 2020 Collin U. Rapp
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
import functools
|
||||
import os.path
|
||||
import sqlite3 as sqlite
|
||||
@@ -22,9 +5,10 @@ import sqlite3 as sqlite
|
||||
### Globals ###
|
||||
|
||||
# TODO put name/path in a config file?
|
||||
DB_NAME = 'pointsbot.db'
|
||||
DB_PATH = DB_NAME
|
||||
# DB_NAME = 'pointsbot.db'
|
||||
# DB_PATH = DB_NAME
|
||||
|
||||
"""
|
||||
DB_SCHEMA = '''
|
||||
CREATE TABLE IF NOT EXISTS redditor_points (
|
||||
id TEXT UNIQUE NOT NULL,
|
||||
@@ -32,10 +16,16 @@ DB_SCHEMA = '''
|
||||
points INTEGER DEFAULT 0
|
||||
)
|
||||
'''
|
||||
"""
|
||||
|
||||
### Decorators ###
|
||||
|
||||
# TODO read config here and create a closure instead of using DB_PATH?
|
||||
# or could do it in the init function somehow?
|
||||
# It seems like the only alternatives would be an OO aproach or global variable
|
||||
|
||||
|
||||
"""
|
||||
def transaction(func):
|
||||
@functools.wraps(func)
|
||||
def newfunc(*args, **kwargs):
|
||||
@@ -45,15 +35,109 @@ def transaction(func):
|
||||
|
||||
ret = func(cursor, *args, **kwargs)
|
||||
|
||||
cursor.close()
|
||||
if conn.in_transaction:
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
return ret
|
||||
return newfunc
|
||||
"""
|
||||
|
||||
|
||||
# TODO create a method decorator for Database methods
|
||||
|
||||
def transaction(func):
|
||||
@functools.wraps(func)
|
||||
def newfunc(self, *args, **kwargs):
|
||||
created_conn = False
|
||||
if not self.conn:
|
||||
self.conn = sqlite.connect(self.path)
|
||||
self.conn.row_factory = sqlite.Row
|
||||
self.cursor = self.conn.cursor()
|
||||
created_conn = True
|
||||
|
||||
# ret = func(cursor, *args, **kwargs)
|
||||
ret = func(self, *args, **kwargs)
|
||||
|
||||
if self.conn.in_transaction:
|
||||
self.conn.commit()
|
||||
|
||||
if created_conn:
|
||||
self.cursor.close()
|
||||
self.conn.close()
|
||||
self.cursor = self.conn = None
|
||||
|
||||
return ret
|
||||
return newfunc
|
||||
|
||||
|
||||
### Classes ###
|
||||
|
||||
|
||||
class Database:
|
||||
|
||||
SCHEMA = '''
|
||||
CREATE TABLE IF NOT EXISTS redditor_points (
|
||||
id TEXT UNIQUE NOT NULL,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
points INTEGER DEFAULT 0
|
||||
)
|
||||
'''
|
||||
|
||||
def __init__(self, dbpath):
|
||||
self.path = dbpath
|
||||
self.conn = None
|
||||
self.cursor = None
|
||||
|
||||
if not os.path.exists(self.path):
|
||||
self._create()
|
||||
|
||||
@transaction
|
||||
def _create(self):
|
||||
self.cursor.execute(self.SCHEMA)
|
||||
|
||||
@transaction
|
||||
def add_redditor(self, redditor):
|
||||
insert_stmt = '''
|
||||
INSERT OR IGNORE INTO redditor_points (id, name)
|
||||
VALUES (:id, :name)
|
||||
'''
|
||||
self.cursor.execute(insert_stmt, {'id': redditor.id, 'name': redditor.name})
|
||||
return self.cursor.rowcount
|
||||
|
||||
@transaction
|
||||
def add_point(self, redditor):
|
||||
points = self.get_points(redditor, add_if_none=True)
|
||||
params = {'id': redditor.id, 'name': redditor.name, 'points': points + 1}
|
||||
update_stmt = '''
|
||||
UPDATE redditor_points
|
||||
SET points = :points
|
||||
WHERE id = :id AND name = :name
|
||||
'''
|
||||
self.cursor.execute(update_stmt, params)
|
||||
return self.cursor.rowcount
|
||||
|
||||
@transaction
|
||||
def get_points(self, redditor, add_if_none=False):
|
||||
params = {'id': redditor.id, 'name': redditor.name}
|
||||
select_stmt = '''
|
||||
SELECT points
|
||||
FROM redditor_points
|
||||
WHERE id = :id AND name = :name
|
||||
'''
|
||||
self.cursor.execute(select_stmt, params)
|
||||
row = self.cursor.fetchone() # TODO check if more than one row
|
||||
if row:
|
||||
points = row['points']
|
||||
elif add_if_none:
|
||||
points = 0
|
||||
self.add_redditor(redditor)
|
||||
|
||||
return points
|
||||
|
||||
"""
|
||||
|
||||
### Private Functions ###
|
||||
|
||||
# These functions are intended for internal use, since they need to be explicity
|
||||
@@ -119,3 +203,5 @@ add_redditor = transaction(_add_redditor)
|
||||
add_point = transaction(_add_point)
|
||||
get_redditor_points = transaction(_get_redditor_points)
|
||||
|
||||
"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user