Initial commit

This commit is contained in:
2026-02-21 22:25:47 +00:00
parent a430ef27ac
commit 567c3ace88
17 changed files with 26 additions and 715 deletions

View File

@@ -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
View 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
View File

@@ -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
View File

@@ -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"
}
}
}

View File

@@ -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.~~

View File

@@ -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

View File

@@ -1,4 +0,0 @@
# TESTS
## End-to-end Testing Scenarios

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 = ""

View File

@@ -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
View File

@@ -0,0 +1,2 @@
toml==0.10.2
praw==7.7.0

View File

@@ -1,6 +0,0 @@
import sys
from os.path import abspath, dirname, join
sys.path.insert(0, abspath(join(dirname(__file__), '..')))
import pointsbot

View File

@@ -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)

View File

@@ -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)