diff --git a/.gitignore b/.gitignore index 858f68a..99a74d8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,7 @@ *.pyc __pycache__ -build/ -dist/ -releases/ - *.html *.out *.swp +/.vscode/ \ No newline at end of file diff --git a/Pipfile b/Pipfile index 9a38135..949eca0 100644 --- a/Pipfile +++ b/Pipfile @@ -4,6 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] +pylint = "*" [packages] toml = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 8cbe51a..7e0a6d4 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c5de2f495184b77e04ef9eeabbf802ca765c0a28bc36b8e6f84da0dcac053224" + "sha256": "eb0083fecb64a3387eba2a1da2fb9fd3600b34c11989342104f415e9c2568676" }, "pipfile-spec": 6, "requires": { @@ -96,5 +96,117 @@ "version": "==0.57.0" } }, - "develop": {} + "develop": { + "astroid": { + "hashes": [ + "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703", + "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386" + ], + "version": "==2.4.2" + }, + "colorama": { + "hashes": [ + "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", + "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" + ], + "markers": "sys_platform == 'win32'", + "version": "==0.4.3" + }, + "isort": { + "hashes": [ + "sha256:60a1b97e33f61243d12647aaaa3e6cc6778f5eb9f42997650f1cc975b6008750", + "sha256:d488ba1c5a2db721669cc180180d5acf84ebdc5af7827f7aaeaa75f73cf0e2b8" + ], + "version": "==5.4.2" + }, + "lazy-object-proxy": { + "hashes": [ + "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", + "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", + "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", + "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", + "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", + "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", + "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", + "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", + "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", + "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", + "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", + "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", + "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", + "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", + "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", + "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", + "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", + "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", + "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", + "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", + "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" + ], + "version": "==1.4.3" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "pylint": { + "hashes": [ + "sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210", + "sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f" + ], + "index": "pypi", + "version": "==2.6.0" + }, + "six": { + "hashes": [ + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + ], + "version": "==1.15.0" + }, + "toml": { + "hashes": [ + "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", + "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" + ], + "index": "pypi", + "version": "==0.10.1" + }, + "typed-ast": { + "hashes": [ + "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", + "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", + "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", + "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", + "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", + "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", + "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", + "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", + "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", + "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", + "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", + "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", + "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", + "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", + "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", + "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", + "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", + "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", + "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", + "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", + "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" + ], + "markers": "implementation_name == 'cpython' and python_version < '3.8'", + "version": "==1.4.1" + }, + "wrapt": { + "hashes": [ + "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" + ], + "version": "==1.12.1" + } + } } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/DONE.md b/docs/DONE.md index 9840634..ae74c53 100644 --- a/docs/DONE.md +++ b/docs/DONE.md @@ -2,6 +2,8 @@ ## General +* [X] Initial/Basic Logging + * Especially when unable to handle a comment ## File-Specific ### bot.py diff --git a/docs/IDEAS.md b/docs/IDEAS.md index b1e242b..39fd3bb 100644 --- a/docs/IDEAS.md +++ b/docs/IDEAS.md @@ -20,3 +20,22 @@ To ensure that a point is awarded to the correct user: could simple check that one of the comments in the comment tree belongs to that user, and then award them the point. - Honestly, this is probably overcomplicated and unnecessary, though. + +## Multiple behaviors + +Implement the concept of actions, with a mapping between trigger keywords/patterns and actions. This will allow for multiple behaviors (eg mark as solved for one behavior, and just show points when summoned for another). + +Maybe allow chains of actions. For example: +1. OP comments "!solved" +2. Bot receives comment +3. Action 1: Determine whether is "!solved" comment +4. Action 2: Determine solver +5. Action 3: Add points +6. Action 4: Reply + +## Making the bot easier to configure + +Allow bot user to just specify a list of strings to recognize to trigger the bot's response. Allow user to specify whether case-sensitive. Ways to do this, with varying performance: +* Just search each text for each keyword; +* Use `re.compile()` for each keyword before searching (*may* be faster?); or +* Build a binary search tree for all keywords for each action \ No newline at end of file diff --git a/docs/TESTS.md b/docs/TESTS.md index 5a33193..51316a5 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -2,4 +2,3 @@ ## End-to-end Testing Scenarios - diff --git a/docs/TODO.md b/docs/TODO.md index ab48329..34654cd 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -6,10 +6,17 @@ File-specific lists are in loose descending order of priority. n/a +## Bugs + +* [ ] Users can get multiple points for same solution + - Scenario: + - OP comments !solved + - OP deletes !solved comment + - OP comments !solved again + - Hopefully this will be easily solved once db migrations are working + ## General -* [ ] Logging - * Especially when unable to handle a comment * [ ] Notifications * Let admins know if a comment can't be properly handled * Email preferable; could do Reddit message, too @@ -17,6 +24,7 @@ n/a * [ ] Testing - Any PRAW model that inherits from `praw.PRAWBase` has a `parse` method that could perhaps be used to make fake objects for testing + - Add regression tests for comments, etc.? * [ ] GUI - [ ] Create a GUI for configuring and running the bot, and performing other jobs like adding or subtracting points for specific redditors @@ -52,6 +60,7 @@ n/a ### bot.py +* [ ] (Maybe) sanitize input text? * [ ] Now that the date of each solution is being stored, can check for missed submissions each time the bot is run by searching subreddit history until last solution found diff --git a/pointsbot/bot.py b/pointsbot/bot.py index 8285bc5..e2ea457 100644 --- a/pointsbot/bot.py +++ b/pointsbot/bot.py @@ -47,21 +47,26 @@ def run(): password=cfg.password, user_agent=USER_AGENT) logging.info('Connected to Reddit as %s', reddit.user.me()) - if not reddit.read_only: - logging.info('Has write access to Reddit') - else: - logging.warning('Has read-only access to Reddit') + access_type = 'read-only' if reddit.read_only else 'write' + logging.info(f'Has {access_type} access to Reddit') + # if not reddit.read_only: + # logging.info('Has write access to Reddit') + # else: + # logging.warning('Has read-only access to Reddit') subreddit = reddit.subreddit(cfg.subreddit) logging.info('Watching subreddit %s', subreddit.title) - if subreddit.moderator(redditor=reddit.user.me()): - logging.info('Is moderator for monitored subreddit') - else: - logging.warning('Is NOT moderator for monitored subreddit') + is_mod = subreddit.moderator(redditor=reddit.user.me()) + logging.info(f'Is {"" if is_mod else "NOT "} moderator for subreddit') + # if subreddit.moderator(redditor=reddit.user.me()): + # logging.info('Is moderator for monitored subreddit') + # else: + # logging.warning('Is NOT moderator for monitored subreddit') monitor_comments(reddit, subreddit, db, levels, cfg) + # Ignoring other potential exceptions for now, since we may not be able - # to recover from them as well as from this one + # to recover from them as well as from these ones except prawcore.exceptions.RequestException as e: logging.error('Unable to connect to Reddit') logging.error('Error message: %s', e) @@ -75,7 +80,7 @@ def run(): def monitor_comments(reddit, subreddit, db, levels, cfg): """Monitor new comments in the subreddit, looking for confirmed solutions.""" # Passing pause_after=0 will bypass the internal exponential delay, but have - # to check if any comments are returned with each query + # to check if any comments are returned after each query for comm in subreddit.stream.comments(skip_existing=True, pause_after=0): if comm is None: continue @@ -288,4 +293,3 @@ def log_solution_info(comm): logging.debug('Author: %s', comm.parent().author.name) logging.debug('Body: %s', comm.parent().body) - diff --git a/pointsbot/config.py b/pointsbot/config.py index c8cc3cd..0a64724 100644 --- a/pointsbot/config.py +++ b/pointsbot/config.py @@ -191,4 +191,3 @@ def interactive_config(dest): toml.dump(configvals, f) print('#' * 80 + f'\nConfig settings saved to {dest}\n' + '#' * 80) - diff --git a/pointsbot/database.py b/pointsbot/database.py index 299ddca..3d28966 100644 --- a/pointsbot/database.py +++ b/pointsbot/database.py @@ -104,4 +104,3 @@ class Database: return points - diff --git a/pointsbot/level.py b/pointsbot/level.py index e4b267a..6a1ac02 100644 --- a/pointsbot/level.py +++ b/pointsbot/level.py @@ -40,4 +40,3 @@ def user_level_info(points, levels): def is_max_level(level_info): return not level_info.next - diff --git a/pointsbot/reply.py b/pointsbot/reply.py index ce54ee5..581e1da 100644 --- a/pointsbot/reply.py +++ b/pointsbot/reply.py @@ -153,4 +153,3 @@ def footer(feedback_url=None, scoreboard_url=None): # '| [Feedback](https://forms.gle/m94aGjFQwGopqQ836) ' # '| [Source Code](https://github.com/cur33/PointsBot))') -