Initial commit
This commit is contained in:
44
CHANGELOG.md
44
CHANGELOG.md
@@ -1,44 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## Version 0.2.1, 2021-04-24
|
||||
|
||||
Features: N/A
|
||||
|
||||
Fixes:
|
||||
1. Bot no longer crashes when a mod marks a deleted submission as solved
|
||||
2. Bot no longer crashes for solutions whose comment has already been stored in the db
|
||||
|
||||
Miscellaneous: N/A
|
||||
|
||||
## Version 0.2.0, 2021-03-07
|
||||
|
||||
Features:
|
||||
1. Points can now be awarded to multiple comments on the same post
|
||||
2. The bot summoning keyword has been changed from "solved" to "helped"
|
||||
|
||||
Fixes: N/A
|
||||
|
||||
Miscellaneous:
|
||||
1. Introduced versioning for the bot and database
|
||||
|
||||
## 2020-08-21
|
||||
|
||||
Features: N/A
|
||||
|
||||
Fixes:
|
||||
1. Removed freezing to comply with r/RequestABot guidelines
|
||||
|
||||
Miscellaneous: N/A
|
||||
|
||||
## 2020-05-10
|
||||
|
||||
Features:
|
||||
1. Adding basic initial logging
|
||||
* Logs both to a file and to the command prompt
|
||||
|
||||
Fixes: N/A
|
||||
|
||||
Miscellaneous:
|
||||
1. Moved feedback & scoreboard links for bot reply into configuration
|
||||
2. Changed program entry point to `PointsBot.py`
|
||||
3. Added ability to freeze the app as a simple executable
|
||||
20
Dockerfile
Normal file
20
Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
FROM python:3.7-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
# Install dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt && \
|
||||
rm requirements.txt
|
||||
|
||||
# Copy application code
|
||||
COPY pointsbot ./pointsbot
|
||||
COPY pointsbot.py .
|
||||
|
||||
|
||||
# Set environment to use unbuffered Python output
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
# Run the bot
|
||||
CMD ["python", "pointsbot.py"]
|
||||
14
Pipfile
14
Pipfile
@@ -1,14 +0,0 @@
|
||||
[[source]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
pylint = "*"
|
||||
|
||||
[packages]
|
||||
toml = "*"
|
||||
praw = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
||||
217
Pipfile.lock
generated
217
Pipfile.lock
generated
@@ -1,217 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "eb0083fecb64a3387eba2a1da2fb9fd3600b34c11989342104f415e9c2568676"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.7"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
|
||||
"sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
|
||||
],
|
||||
"version": "==2020.12.5"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
|
||||
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
|
||||
],
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
||||
],
|
||||
"version": "==2.10"
|
||||
},
|
||||
"praw": {
|
||||
"hashes": [
|
||||
"sha256:068a01c19834e1f748a8c220c6aa62ae9f0f8e211504ab8ef19883d09bed3430",
|
||||
"sha256:87166a77ec31a1d9686ccdac97b5b72ba277ce436976eb3baf467c593f15bb26"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==7.1.4"
|
||||
},
|
||||
"prawcore": {
|
||||
"hashes": [
|
||||
"sha256:1f1eafc8a65d671f9892354f73142014fbb5d3a9ee621568c662d0a354e0578b",
|
||||
"sha256:672d8a2faa12b44307874b2acfdd27f9cbaa5fa37bada4b34e36277224e6d8ed"
|
||||
],
|
||||
"version": "==1.5.0"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
|
||||
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
|
||||
],
|
||||
"version": "==2.25.1"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||
],
|
||||
"version": "==1.15.0"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.10.2"
|
||||
},
|
||||
"update-checker": {
|
||||
"hashes": [
|
||||
"sha256:6a2d45bb4ac585884a6b03f9eade9161cedd9e8111545141e9aa9058932acb13",
|
||||
"sha256:cbba64760a36fe2640d80d85306e8fe82b6816659190993b7bdabadee4d4bbfd"
|
||||
],
|
||||
"version": "==0.18.0"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80",
|
||||
"sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"
|
||||
],
|
||||
"version": "==1.26.3"
|
||||
},
|
||||
"websocket-client": {
|
||||
"hashes": [
|
||||
"sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549",
|
||||
"sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010"
|
||||
],
|
||||
"version": "==0.57.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"astroid": {
|
||||
"hashes": [
|
||||
"sha256:87ae7f2398b8a0ae5638ddecf9987f081b756e0e9fc071aeebdca525671fc4dc",
|
||||
"sha256:b31c92f545517dcc452f284bc9c044050862fbe6d93d2b3de4a215a6b384bf0d"
|
||||
],
|
||||
"version": "==2.5"
|
||||
},
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
|
||||
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
|
||||
],
|
||||
"markers": "sys_platform == 'win32'",
|
||||
"version": "==0.4.4"
|
||||
},
|
||||
"isort": {
|
||||
"hashes": [
|
||||
"sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e",
|
||||
"sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"
|
||||
],
|
||||
"version": "==5.7.0"
|
||||
},
|
||||
"lazy-object-proxy": {
|
||||
"hashes": [
|
||||
"sha256:1d33d6f789697f401b75ce08e73b1de567b947740f768376631079290118ad39",
|
||||
"sha256:2f2de8f8ac0be3e40d17730e0600619d35c78c13a099ea91ef7fb4ad944ce694",
|
||||
"sha256:3782931963dc89e0e9a0ae4348b44762e868ea280e4f8c233b537852a8996ab9",
|
||||
"sha256:37d9c34b96cca6787fe014aeb651217944a967a5b165e2cacb6b858d2997ab84",
|
||||
"sha256:38c3865bd220bd983fcaa9aa11462619e84a71233bafd9c880f7b1cb753ca7fa",
|
||||
"sha256:429c4d1862f3fc37cd56304d880f2eae5bd0da83bdef889f3bd66458aac49128",
|
||||
"sha256:522b7c94b524389f4a4094c4bf04c2b02228454ddd17c1a9b2801fac1d754871",
|
||||
"sha256:57fb5c5504ddd45ed420b5b6461a78f58cbb0c1b0cbd9cd5a43ad30a4a3ee4d0",
|
||||
"sha256:5944a9b95e97de1980c65f03b79b356f30a43de48682b8bdd90aa5089f0ec1f4",
|
||||
"sha256:6f4e5e68b7af950ed7fdb594b3f19a0014a3ace0fedb86acb896e140ffb24302",
|
||||
"sha256:71a1ef23f22fa8437974b2d60fedb947c99a957ad625f83f43fd3de70f77f458",
|
||||
"sha256:8a44e9901c0555f95ac401377032f6e6af66d8fc1fbfad77a7a8b1a826e0b93c",
|
||||
"sha256:b6577f15d5516d7d209c1a8cde23062c0f10625f19e8dc9fb59268859778d7d7",
|
||||
"sha256:c8fe2d6ff0ff583784039d0255ea7da076efd08507f2be6f68583b0da32e3afb",
|
||||
"sha256:cadfa2c2cf54d35d13dc8d231253b7985b97d629ab9ca6e7d672c35539d38163",
|
||||
"sha256:cd1bdace1a8762534e9a36c073cd54e97d517a17d69a17985961265be6d22847",
|
||||
"sha256:ddbdcd10eb999d7ab292677f588b658372aadb9a52790f82484a37127a390108",
|
||||
"sha256:e7273c64bccfd9310e9601b8f4511d84730239516bada26a0c9846c9697617ef",
|
||||
"sha256:e7428977763150b4cf83255625a80a23dfdc94d43be7791ce90799d446b4e26f",
|
||||
"sha256:e960e8be509e8d6d618300a6c189555c24efde63e85acaf0b14b2cd1ac743315",
|
||||
"sha256:ecb5dd5990cec6e7f5c9c1124a37cb2c710c6d69b0c1a5c4aa4b35eba0ada068",
|
||||
"sha256:ef3f5e288aa57b73b034ce9c1f1ac753d968f9069cd0742d1d69c698a0167166",
|
||||
"sha256:fa5b2dee0e231fa4ad117be114251bdfe6afe39213bd629d43deb117b6a6c40a",
|
||||
"sha256:fa7fb7973c622b9e725bee1db569d2c2ee64d2f9a089201c5e8185d482c7352d"
|
||||
],
|
||||
"version": "==1.5.2"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
||||
],
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"pylint": {
|
||||
"hashes": [
|
||||
"sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210",
|
||||
"sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.10.2"
|
||||
},
|
||||
"typed-ast": {
|
||||
"hashes": [
|
||||
"sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1",
|
||||
"sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d",
|
||||
"sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6",
|
||||
"sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd",
|
||||
"sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37",
|
||||
"sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151",
|
||||
"sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07",
|
||||
"sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440",
|
||||
"sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70",
|
||||
"sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496",
|
||||
"sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea",
|
||||
"sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400",
|
||||
"sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc",
|
||||
"sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606",
|
||||
"sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc",
|
||||
"sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581",
|
||||
"sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412",
|
||||
"sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a",
|
||||
"sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2",
|
||||
"sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787",
|
||||
"sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f",
|
||||
"sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937",
|
||||
"sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64",
|
||||
"sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487",
|
||||
"sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b",
|
||||
"sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41",
|
||||
"sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a",
|
||||
"sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3",
|
||||
"sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166",
|
||||
"sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"
|
||||
],
|
||||
"markers": "implementation_name == 'cpython' and python_version < '3.8'",
|
||||
"version": "==1.4.2"
|
||||
},
|
||||
"wrapt": {
|
||||
"hashes": [
|
||||
"sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"
|
||||
],
|
||||
"version": "==1.12.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
66
docs/DONE.md
66
docs/DONE.md
@@ -1,66 +0,0 @@
|
||||
# DONE
|
||||
|
||||
## General
|
||||
|
||||
* [X] Initial/Basic Logging
|
||||
* Especially when unable to handle a comment
|
||||
* [X] Change commands from !solved and /solved to !helped and /helped
|
||||
* [X] Allow multiple users to be awarded points on a single post
|
||||
* So just check whether each user is already awarded a point for a given post
|
||||
|
||||
## Bugs
|
||||
* [X] mod /helped command doesn't work on one post (some posts?)
|
||||
* the problem here seems to be the fact that when the bot adds a point for a solution comment, it
|
||||
also adds the comment to the comments table without first checking whether it already exists
|
||||
* while it is the case that we should only have one one `solution` row for each comment, it is potentially
|
||||
possible that a comment could already be in the db when we add a solution for it, e.g. if the solution was
|
||||
deleted (meaning the point was removed) and then we want to add it back, or something??
|
||||
* [X] bot crashes when a mod is trying to award a point to a solution on a deleted post
|
||||
* the problem is that the new schema had the submission_id as a required foreign key, but a
|
||||
deleted post will not have one
|
||||
* according to them, since only mods will award points on deleted posts, it's fine if the fix is limited to mods
|
||||
|
||||
## File-Specific
|
||||
|
||||
### bot.py
|
||||
|
||||
* [X] Allow mods to mark a post as solved with "/[Ss]olved"
|
||||
* [X] Allow mods to use "/[Ss]olved" in any context
|
||||
* [X] When replying to solution, the bot should...
|
||||
- [X] Reply to the comment containing the "![Ss]olved" string
|
||||
- [X] Tag the solver so they will be notified
|
||||
- ~~Delete the automod message~~
|
||||
* ~~Add a star to the user flair for every 100 points~~
|
||||
* ~~Add a way to look up user points~~
|
||||
- i.e. summon bot by tagging it and provide a username to look up
|
||||
- the bot will reply with the last message that the user received
|
||||
|
||||
### config.py
|
||||
|
||||
* [X] Change from `ini` to `toml `
|
||||
* [X] Consolidate `pointsbot.ini` and `praw.ini` into a single config file.
|
||||
* [X] Add option for flair_template_id
|
||||
* [X] Check for config file in a well-known location, e.g. `~` directory
|
||||
- Probably named `~/.pointsbot` or something similar
|
||||
- Could do something similar to packages like PRAW:
|
||||
1. Look in current working directory first.
|
||||
2. Look in OS-dependent location next.
|
||||
3. (Maybe) Finally, could look in the directory containing the source files
|
||||
(using `__file__`).
|
||||
* [X] Add interactive config scipt
|
||||
* ~~Fix titlecase problem with roman numerals~~
|
||||
- Only an issue with `ini` format
|
||||
* ~~Separate the development-handy config items into separate section~~
|
||||
- Don't really have any right now
|
||||
|
||||
### database.py
|
||||
|
||||
### reply.py
|
||||
|
||||
* [X] Fix progress bar
|
||||
* ~~For the footer section of the reply comment regarding the bot, could have the
|
||||
bot make a post on its account explaining itself, and link to that (and then
|
||||
also link to the source code separately, and perhaps in that post, too).
|
||||
Could even have the bot make this post automatically if it doesn't have a
|
||||
link to the post in its config, and then store the link for future use.~~
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
# IDEAS
|
||||
|
||||
## Determining when to award points
|
||||
|
||||
To ensure that points are only awarded for the first comment marked as a
|
||||
solution:
|
||||
|
||||
* This approach could also make it simpler to check whether a solution comment
|
||||
has been edited. Instead of having to do a daily search for edits, it could
|
||||
just check the original solution comment to ensure that it still contains
|
||||
the "!solved" string. If not, it can remove points from that author and
|
||||
award points to the new author.
|
||||
|
||||
To ensure that a point is awarded to the correct user:
|
||||
|
||||
* We could expand the "!solved" bot summons to include an optional username
|
||||
argument. Without the argument, the bot will award the point to either the
|
||||
top-level comment or the parent comment of the "!solved" comment, whichever
|
||||
is decided upon. However, if the username argument is provided, the bot
|
||||
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
|
||||
@@ -1,4 +0,0 @@
|
||||
# TESTS
|
||||
|
||||
## End-to-end Testing Scenarios
|
||||
|
||||
91
docs/TODO.md
91
docs/TODO.md
@@ -1,91 +0,0 @@
|
||||
# TODO
|
||||
|
||||
File-specific lists are in loose descending order of priority.
|
||||
|
||||
## Current
|
||||
|
||||
N/A
|
||||
|
||||
## Bugs
|
||||
|
||||
* [ ] For some posts, the bot adds a point to the database, but crashes before being able to reply
|
||||
|
||||
## General
|
||||
|
||||
* [ ] Make "!Solved" command text (or all command texts?) configurable by user
|
||||
* [ ] Make flair usage configurable
|
||||
* New user suggested that it could be helpful to have point totals in flair
|
||||
* [ ] Notifications
|
||||
* Let admins know if a comment can't be properly handled
|
||||
* Email preferable; could do Reddit message, too
|
||||
* Maybe have both and make into a config option
|
||||
* [ ] 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
|
||||
- [ ] Check for updates in the Github repo and prompt user to update
|
||||
- Github API overview:
|
||||
- https://developer.github.com/v3/
|
||||
- https://developer.github.com/v4/
|
||||
- It turns out that v4 might only be possible with a Github user
|
||||
account to authenticate with the API.
|
||||
- Useful links:
|
||||
- https://developer.github.com/v3/repos/branches/#get-branch
|
||||
- https://developer.github.com/v3/repos/contents/#get-archive-link
|
||||
- https://developer.github.com/v3/repos/commits/
|
||||
- https://developer.github.com/v3/repos/releases/
|
||||
- https://developer.github.com/v3/#timezones
|
||||
- Webhooks:
|
||||
- https://developer.github.com/webhooks/
|
||||
- https://developer.github.com/v3/repos/hooks/
|
||||
- https://developer.github.com/v3/activity/events/types/#pushevent
|
||||
* [ ] Determine whether and how to check "![Ss]olved" comments have been later
|
||||
edited to remove the "![Ss]olved" string, and whether and how to remove or
|
||||
reassign points
|
||||
- If so, it could do that daily or something.
|
||||
- This will be especially important if a "recovery mode" is implemented that
|
||||
crawls through the whole subreddit to rebuild the database, since the
|
||||
bot would only be able to see comments that haven't been removed, or
|
||||
the newest version of edited comments.
|
||||
- This could also just be encouraged through sub rules; e.g. "don't mark as
|
||||
solved until you've actually tried the proposed solution"
|
||||
* [ ] As mentioned in the previous section, implement a recovery mode
|
||||
|
||||
## File-Specific
|
||||
|
||||
### 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
|
||||
* [ ] Allow mods and/or bot owner to add or remove points from specific users
|
||||
* [ ] Make the algorithm for determining the problem solver more sophisticated
|
||||
- e.g. check entire comment tree instead of just ignoring if the OP also
|
||||
authored the parent comment
|
||||
- Ask OP for clarification only if solver cannot be sufficiently determined
|
||||
- Again, this behavior could also perhaps be better enforced through
|
||||
subreddit rules rather than algorithmically
|
||||
|
||||
### config.py
|
||||
|
||||
* [ ] Add list of emails to which notifications should be sent
|
||||
* [ ] Switch from `toml` package to `tomlkit` package
|
||||
- Preserves style, comments, etc.
|
||||
|
||||
### database.py
|
||||
|
||||
* [ ] For each solved submission, store:
|
||||
- submission id
|
||||
- solution comment id
|
||||
- solved comment id
|
||||
- date
|
||||
- solver id
|
||||
* [ ] Possibly refactor for a datastore type thing instead of database
|
||||
- Maybe even make models like Redditor to combine data storage/access with
|
||||
logic, e.g. determining current level
|
||||
- Seems like overkill, though
|
||||
|
||||
17
docs/run.cmd
17
docs/run.cmd
@@ -1,17 +0,0 @@
|
||||
@echo off
|
||||
|
||||
REM wsl run.sh
|
||||
REM start "" chrome.exe TODO.html
|
||||
|
||||
REM pandoc.exe -s -f gfm -t html TODO.md -o TODO.html --metadata pagetitle="TODO.html"
|
||||
REM .\TODO.html
|
||||
|
||||
call :makedoc DONE
|
||||
call :makedoc IDEAS
|
||||
call :makedoc TODO
|
||||
call :makedoc TESTS
|
||||
goto :eof
|
||||
|
||||
:makedoc
|
||||
pandoc.exe -s -f gfm -t html %1.md -o %1.html --metadata pagetitle="%1.html"
|
||||
goto :eof
|
||||
15
docs/run.sh
15
docs/run.sh
@@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# If files specified, only make them; otherwise, make all
|
||||
if [[ $# -gt 0 ]]; then
|
||||
docfiles=( "$@" )
|
||||
else
|
||||
docfiles=( $(ls *.md) )
|
||||
fi
|
||||
|
||||
for fname in "${docfiles[@]}"; do
|
||||
outname="_${fname/%md/html}"
|
||||
echo $fname "->" $outname
|
||||
pandoc -s -f gfm -t html $fname -o $outname --metadata pagetitle="$outname"
|
||||
open $outname
|
||||
done
|
||||
@@ -1,93 +0,0 @@
|
||||
################################################################################
|
||||
# Core
|
||||
#
|
||||
# These are the primary fields needed to run the bot.
|
||||
################################################################################
|
||||
|
||||
[core]
|
||||
# The name of the subreddit to monitor, without the "r/" prefix.
|
||||
subreddit = ""
|
||||
valid_tags = "Java,Bedrock,Dungeons,Earth,Education,Legacy"
|
||||
|
||||
|
||||
################################################################################
|
||||
# Reddit Credentials
|
||||
#
|
||||
# See the bot documentation for more information about these fields.
|
||||
|
||||
# Some of these fields are sensitive, so once these fields are provided, take
|
||||
# care not to upload or distribute this file through insecure channels, or to
|
||||
# public sites.
|
||||
################################################################################
|
||||
|
||||
[credentials]
|
||||
client_id = "REDACTED"
|
||||
client_secret = "REDACTED"
|
||||
username = "REDACTED"
|
||||
password = "REDACTED"
|
||||
|
||||
|
||||
################################################################################
|
||||
# Filepaths
|
||||
#
|
||||
# Any filepaths needed by the bot. Can be relative or absolute paths, or just
|
||||
# filenames.
|
||||
################################################################################
|
||||
|
||||
[filepaths]
|
||||
# The name/path of the SQLite database file to use. This value is optional; if not
|
||||
# specified, a default filepath will be used.
|
||||
database = ""
|
||||
# The name/path of the log file to use. This value is optional; if not specified,
|
||||
# a default filepath will be used.
|
||||
log = ""
|
||||
|
||||
|
||||
################################################################################
|
||||
# Links
|
||||
#
|
||||
# Any links used in the bot's reply. If you do not need to use these links,
|
||||
# leave these fields blank.
|
||||
################################################################################
|
||||
|
||||
[links]
|
||||
feedback = ""
|
||||
scoreboard = ""
|
||||
|
||||
|
||||
################################################################################
|
||||
# User Levels
|
||||
#
|
||||
# These items represent the levels that contributors to the subreddit can
|
||||
# attain. To add a level, add another section with the format:
|
||||
#
|
||||
# [[levels]]
|
||||
# name = "<name inside quotes>"
|
||||
# points = <integer>
|
||||
# flair_template_id = "<id inside quotes>"
|
||||
#
|
||||
# These fields are case-sensitive, so enter the level name exactly as you want
|
||||
# it to appear.
|
||||
#
|
||||
# The flair_template_id field is optional, so it may be omitted or left as the
|
||||
# empty string, "".
|
||||
#
|
||||
# Furthermore, the order of these levels does not matter: they will be sorted by
|
||||
# point values when the bot is run. However, it may be preferable to order them
|
||||
# by point values for readability.
|
||||
################################################################################
|
||||
|
||||
[[levels]]
|
||||
name = "Level One"
|
||||
points = 1
|
||||
flair_template_id = ""
|
||||
|
||||
[[levels]]
|
||||
name = "Level Two"
|
||||
points = 25
|
||||
flair_template_id = ""
|
||||
|
||||
[[levels]]
|
||||
name = "Level Three"
|
||||
points = 100
|
||||
flair_template_id = ""
|
||||
@@ -151,14 +151,14 @@ def divider():
|
||||
|
||||
|
||||
def footer(feedback_url=None, scoreboard_url=None):
|
||||
footer_sections = ['^(Bot maintained by GlipGlorp7)']
|
||||
footer_sections = ['^(Official MinecraftHelp Bot)']
|
||||
if scoreboard_url:
|
||||
# https://points.minecrafthelp.co.uk
|
||||
footer_sections.append(f'[^Scoreboard]({scoreboard_url})')
|
||||
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)')
|
||||
footer_sections.append(f'^[Feedback]({feedback_url})')
|
||||
footer_sections.append('^[Source](https://github.com/cur33/PointsBot)')
|
||||
|
||||
return ' ^| '.join(footer_sections)
|
||||
|
||||
|
||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
toml==0.10.2
|
||||
praw==7.7.0
|
||||
@@ -1,6 +0,0 @@
|
||||
import sys
|
||||
from os.path import abspath, dirname, join
|
||||
|
||||
sys.path.insert(0, abspath(join(dirname(__file__), '..')))
|
||||
|
||||
import pointsbot
|
||||
@@ -1,40 +0,0 @@
|
||||
import level
|
||||
|
||||
levels = [
|
||||
('Helper', 5),
|
||||
('Trusted Helper', 15),
|
||||
('Super Helper', 45),
|
||||
]
|
||||
|
||||
|
||||
### Test user_level_info ###
|
||||
|
||||
|
||||
pastlvls, curlvl, nextlvl = user_level_info(1, levels)
|
||||
assert (pastlevels == [] and curlvl is None and nextlvl == levels[0])
|
||||
|
||||
pastlvls, curlvl, nextlvl = user_level_info(5, levels)
|
||||
assert (pastlevels == [] and curlvl == levels[0] and nextlvl == levels[1])
|
||||
|
||||
pastlvls, curlvl, nextlvl = user_level_info(15, levels)
|
||||
assert (pastlvls == levels[:1] and curlvl == levels[1] and nextlvl == levels[2])
|
||||
|
||||
pastlvls, curlvl, nextlvl = user_level_info(45, levels)
|
||||
assert (pastlvls == levels[:2] and curlvl == levels[2] and nextlvl is None)
|
||||
|
||||
|
||||
### Test is_max_level ###
|
||||
|
||||
|
||||
# TODO I mean, this could be tested exhaustively with positive numbers, even if
|
||||
# the number of points for the max level is decently large
|
||||
assert not level.is_max_level(-1, levels)
|
||||
assert not level.is_max_level(0, levels)
|
||||
assert not level.is_max_level(4, levels)
|
||||
assert not level.is_max_level(5, levels)
|
||||
assert not level.is_max_level(14, levels)
|
||||
assert not level.is_max_level(15, levels)
|
||||
assert not level.is_max_level(16, levels)
|
||||
assert not level.is_max_level(44, levels)
|
||||
assert level.is_max_level(45, levels)
|
||||
assert level.is_max_level(46, levels)
|
||||
@@ -1,63 +0,0 @@
|
||||
from collections import namedtuple
|
||||
|
||||
from context import pointsbot
|
||||
|
||||
### Data Structures ###
|
||||
|
||||
MockRedditor = namedtuple('MockRedditor', 'id name')
|
||||
|
||||
### Functions ###
|
||||
|
||||
|
||||
def leftpad(msg, num_indents=1):
|
||||
return '\n'.join([('\t' * num_indents + l) for l in msg.split('\n')])
|
||||
|
||||
|
||||
def make_comments(subreddit, levels):
|
||||
testpoints = [1, 3, 5, 10, 15, 30, 45, 75] + list(range(100, 551, 50))
|
||||
|
||||
for sub in subreddit.new():
|
||||
if sub.title == 'Testing comment scenarios':
|
||||
redditor = sub.author
|
||||
for points in testpoints:
|
||||
body = f'Solver: {redditor}\n\nTotal points after solving: {points}'
|
||||
print_level(0, body)
|
||||
comm = sub.reply(body)
|
||||
if comm:
|
||||
level_info = level.user_level_info(points, levels)
|
||||
body = reply.make(redditor, points, level_info)
|
||||
comm.reply(body)
|
||||
else:
|
||||
print_level(1, 'ERROR: Unable to comment')
|
||||
break
|
||||
|
||||
|
||||
### Tests ###
|
||||
|
||||
levels = [
|
||||
pointsbot.level.Level('Novice', 1, ''),
|
||||
pointsbot.level.Level('Apprentice', 5, ''),
|
||||
pointsbot.level.Level('Journeyman', 15, ''),
|
||||
pointsbot.level.Level('Expert', 45, ''),
|
||||
pointsbot.level.Level('Master I', 100, ''),
|
||||
pointsbot.level.Level('Master II', 200, ''),
|
||||
pointsbot.level.Level('Master III', 300, ''),
|
||||
pointsbot.level.Level('Master IV', 400, ''),
|
||||
pointsbot.level.Level('Master V', 500, ''),
|
||||
]
|
||||
|
||||
testredditors = [MockRedditor('1', 'Tim_the_Sorcerer')]
|
||||
testpoints = [1, 3, 5, 10, 15, 30, 45, 75] + list(range(100, 551, 50))
|
||||
|
||||
for redditor in testredditors:
|
||||
for points in testpoints:
|
||||
level_info = pointsbot.level.user_level_info(points, levels)
|
||||
body = pointsbot.reply.make(redditor, points, level_info)
|
||||
print('*' * 80)
|
||||
print()
|
||||
print(f'Name: {redditor.name}')
|
||||
print(f'Points: {points}')
|
||||
print(f'Body:')
|
||||
print(leftpad(body, num_indents=1))
|
||||
print()
|
||||
print('*' * 80)
|
||||
Reference in New Issue
Block a user