Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 98 additions & 81 deletions src/reddit/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import sys
import threading
import time
from typing import Optional

# lib imports
import praw
Expand Down Expand Up @@ -43,8 +42,14 @@ def __init__(self, **kwargs):

# directories
self.data_dir = common.data_dir
self.commands_dir = os.path.join(self.data_dir, "support-bot-commands", "docs")

# files
self.db = os.path.join(self.data_dir, 'reddit_bot_database')

# locks
self.lock = threading.Lock()

self.last_online_file = os.path.join(self.data_dir, 'last_online')
self.reddit = praw.Reddit(
client_id=os.environ['PRAW_CLIENT_ID'],
client_secret=os.environ['PRAW_CLIENT_SECRET'],
Expand All @@ -55,6 +60,9 @@ def __init__(self, **kwargs):
)
self.subreddit = self.reddit.subreddit(self.subreddit_name) # "AskReddit" for faster testing of submission loop

self.migrate_shelve()
self.migrate_last_online()

@staticmethod
def validate_env() -> bool:
required_env = [
Expand All @@ -70,9 +78,43 @@ def validate_env() -> bool:
return False
return True

def migrate_last_online(self):
if os.path.isfile(os.path.join(self.data_dir, 'last_online')):
os.remove(os.path.join(self.data_dir, 'last_online'))

def migrate_shelve(self):
with self.lock, shelve.open(self.db) as db:
if 'submissions' not in db and 'comments' not in db:
db['comments'] = {}
db['submissions'] = {}
submissions = db['submissions']
for k, v in db.items():
if k not in ['comments', 'submissions']:
submissions[k] = v
assert submissions[k] == v
db['submissions'] = submissions
keys_to_delete = [k for k in db if k not in ['comments', 'submissions']]
for k in keys_to_delete:
del db[k]
assert k not in db

def process_comment(self, comment: models.Comment):
# todo
pass
with self.lock, shelve.open(self.db) as db:
comments = db.get('comments', {})
if comment.id in comments and comments[comment.id].get('processed', False):
return

comments[comment.id] = {
'author': str(comment.author),
'body': comment.body,
'created_utc': comment.created_utc,
'processed': True,
'slash_command': {'project': None, 'command': None},
}
# the shelve doesn't update unless we recreate the main key
db['comments'] = comments

self.slash_commands(comment=comment)

def process_submission(self, submission: models.Submission):
"""
Expand All @@ -83,44 +125,28 @@ def process_submission(self, submission: models.Submission):
submission : praw.models.Submission
The submission to process.
"""
last_online = self.get_last_online()

if last_online < submission.created_utc:
with self.lock, shelve.open(self.db) as db:
submissions = db.get('submissions', {})
if submission.id not in submissions:
submissions[submission.id] = {}
submission_exists = False
else:
submission_exists = True

# the shelve doesn't update unless we recreate the main key
submissions[submission.id].update(vars(submission))
db['submissions'] = submissions

if not submission_exists:
print(f'submission id: {submission.id}')
print(f'submission title: {submission.title}')
print('---------')
if os.getenv('DISCORD_WEBHOOK'):
self.discord(submission=submission)
self.flair(submission=submission)
self.karma(submission=submission)

with shelve.open(os.path.join(self.data_dir, 'reddit_bot_database')) as db:
try:
db[submission.id]
except KeyError:
submission_exists = False
db[submission.id] = vars(submission)
else:
submission_exists = True

if submission_exists:
for k, v in vars(submission).items(): # update the database with current values
try:
if db[submission.id][k] != v:
db[submission.id][k] = v
except KeyError:
db[submission.id][k] = v

else:
try:
os.environ['DISCORD_WEBHOOK']
except KeyError:
pass
else:
db = self.discord(db=db, submission=submission)
db = self.flair(db=db, submission=submission)
db = self.karma(db=db, submission=submission)

# re-write the last online time
self.last_online_writer()

def discord(self, db: shelve.Shelf, submission: models.Submission) -> Optional[shelve.Shelf]:
def discord(self, submission: models.Submission):
"""
Send a discord message.

Expand Down Expand Up @@ -180,54 +206,45 @@ def discord(self, db: shelve.Shelf, submission: models.Submission) -> Optional[s
r = requests.post(os.environ['DISCORD_WEBHOOK'], json=discord_webhook)

if r.status_code == 204: # successful completion of request, no additional content
# update the database
db[submission.id]['bot_discord'] = {'sent': True, 'sent_utc': int(time.time())}
with self.lock, shelve.open(self.db) as db:
# the shelve doesn't update unless we recreate the main key
submissions = db['submissions']
submissions[submission.id]['bot_discord'] = {'sent': True, 'sent_utc': int(time.time())}
db['submissions'] = submissions

return db

def flair(self, db: shelve.Shelf, submission: models.Submission) -> shelve.Shelf:
# todo
return db

def karma(self, db: shelve.Shelf, submission: models.Submission) -> shelve.Shelf:
def flair(self, submission: models.Submission):
# todo
return db
pass

def commands(self, db: shelve.Shelf, submission: models.Submission) -> shelve.Shelf:
def karma(self, submission: models.Submission):
# todo
return db

def last_online_writer(self) -> int:
"""
Write the current time to the last online file.

Returns
-------
int
The current time.
"""
last_online = int(time.time())
with open(self.last_online_file, 'w') as f:
f.write(str(last_online))

return last_online

def get_last_online(self) -> int:
"""
Get the last online time.

Returns
-------
int
The last online time.
"""
try:
with open(self.last_online_file, 'r') as f:
last_online = int(f.read())
except FileNotFoundError:
last_online = self.last_online_writer()
pass

return last_online
def slash_commands(self, comment: models.Comment):
if comment.body.startswith("/"):
print(f"Processing slash command: {comment.body}")
# Split the comment into project and command
parts = comment.body[1:].split()
project = parts[0]
command = parts[1] if len(parts) > 1 else None

# Check if the command file exists in self.commands_dir
command_file = os.path.join(self.commands_dir, project, f"{command}.md") if command else None
if command_file and os.path.isfile(command_file):
# Open the markdown file and read its contents
with open(command_file, 'r', encoding='utf-8') as file:
file_contents = file.read()

# Reply to the comment with the contents of the file
comment.reply(file_contents)
else:
# Log error message
print(f"Unknown command: {command} in project: {project}")
with self.lock, shelve.open(self.db) as db:
# the shelve doesn't update unless we recreate the main key
comments = db['comments']
comments[comment.id]['slash_command'] = {'project': project, 'command': command}
db['comments'] = comments

def _comment_loop(self, test: bool = False):
# process comments and then keep monitoring
Expand Down
302 changes: 302 additions & 0 deletions tests/fixtures/cassettes/fixture__submission.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"http_interactions": [
{
"recorded_at": "2024-04-27T01:29:37",
"recorded_at": "2024-05-01T02:35:44",
"request": {
"body": {
"encoding": "utf-8",
Expand Down Expand Up @@ -52,7 +52,7 @@
"848"
],
"Date": [
"Sat, 27 Apr 2024 01:29:36 GMT"
"Wed, 01 May 2024 02:35:44 GMT"
],
"NEL": [
"{\"report_to\": \"w3-reporting-nel\", \"max_age\": 14400, \"include_subdomains\": false, \"success_fraction\": 1.0, \"failure_fraction\": 1.0}"
Expand All @@ -64,7 +64,7 @@
"snooserv"
],
"Set-Cookie": [
"edgebucket=DwBNm1g2fDYDJH5bDV; Domain=reddit.com; Max-Age=63071999; Path=/; secure"
"edgebucket=ELOnVL1mGMbsRlea17; Domain=reddit.com; Max-Age=63071999; Path=/; secure"
],
"Strict-Transport-Security": [
"max-age=31536000; includeSubdomains"
Expand Down Expand Up @@ -96,7 +96,7 @@
}
},
{
"recorded_at": "2024-04-27T01:29:37",
"recorded_at": "2024-05-01T02:35:44",
"request": {
"body": {
"encoding": "utf-8",
Expand All @@ -116,19 +116,19 @@
"keep-alive"
],
"Cookie": [
"edgebucket=DwBNm1g2fDYDJH5bDV"
"edgebucket=ELOnVL1mGMbsRlea17"
],
"User-Agent": [
"Test suite PRAW/7.7.1 prawcore/2.4.0"
]
},
"method": "GET",
"uri": "https://oauth.reddit.com/comments/w03cku/?limit=2048&sort=confidence&raw_json=1"
"uri": "https://oauth.reddit.com/api/info/?id=t1_l20s21b&raw_json=1"
},
"response": {
"body": {
"encoding": "UTF-8",
"string": "[{\"kind\": \"Listing\", \"data\": {\"after\": null, \"dist\": 1, \"modhash\": null, \"geo_filter\": \"\", \"children\": [{\"kind\": \"t3\", \"data\": {\"author_flair_background_color\": \"#94e044\", \"approved_at_utc\": null, \"subreddit\": \"LizardByte\", \"selftext\": \"Thank you for joining our LizardByte subreddit! We are still in process of updating our projects on GitHub!\\n\\nLook out for additional updates!\", \"user_reports\": [], \"saved\": false, \"mod_reason_title\": null, \"gilded\": 0, \"clicked\": false, \"title\": \"Welcome to LizardByte!\", \"link_flair_richtext\": [], \"subreddit_name_prefixed\": \"r/LizardByte\", \"hidden\": false, \"pwls\": null, \"link_flair_css_class\": \"\", \"downs\": 0, \"thumbnail_height\": null, \"top_awarded_type\": null, \"parent_whitelist_status\": null, \"hide_score\": false, \"name\": \"t3_w03cku\", \"quarantine\": false, \"link_flair_text_color\": \"dark\", \"upvote_ratio\": 0.82, \"ignore_reports\": false, \"ups\": 7, \"domain\": \"self.LizardByte\", \"media_embed\": {}, \"thumbnail_width\": null, \"author_flair_template_id\": \"5d220538-ff87-11ec-a9c4-56c680cbb67e\", \"is_original_content\": false, \"author_fullname\": \"t2_jwdeap93\", \"secure_media\": null, \"is_reddit_media_domain\": false, \"is_meta\": false, \"category\": null, \"secure_media_embed\": {}, \"link_flair_text\": \"Announcement\", \"can_mod_post\": true, \"score\": 7, \"approved_by\": null, \"is_created_from_ads_ui\": false, \"author_premium\": false, \"thumbnail\": \"self\", \"edited\": false, \"author_flair_css_class\": null, \"previous_visits\": [1714058632.0, 1714062595.0, 1714068499.0, 1714069806.0, 1714079940.0, 1714081381.0, 1714082461.0, 1714084081.0, 1714085322.0, 1714180262.0], \"author_flair_richtext\": [], \"gildings\": {}, \"content_categories\": null, \"is_self\": true, \"subreddit_type\": \"public\", \"created\": 1657930958.0, \"link_flair_type\": \"text\", \"wls\": null, \"removed_by_category\": null, \"banned_by\": null, \"author_flair_type\": \"text\", \"total_awards_received\": 0, \"allow_live_comments\": false, \"selftext_html\": \"\\u003C!-- SC_OFF --\\u003E\\u003Cdiv class=\\\"md\\\"\\u003E\\u003Cp\\u003EThank you for joining our LizardByte subreddit! We are still in process of updating our projects on GitHub!\\u003C/p\\u003E\\n\\n\\u003Cp\\u003ELook out for additional updates!\\u003C/p\\u003E\\n\\u003C/div\\u003E\\u003C!-- SC_ON --\\u003E\", \"likes\": null, \"suggested_sort\": \"new\", \"banned_at_utc\": null, \"view_count\": null, \"archived\": false, \"no_follow\": false, \"spam\": false, \"is_crosspostable\": true, \"pinned\": false, \"over_18\": false, \"all_awardings\": [], \"awarders\": [], \"media_only\": false, \"link_flair_template_id\": \"1c411bfc-ff88-11ec-a3ea-969cbc5b3148\", \"can_gild\": false, \"removed\": false, \"spoiler\": false, \"locked\": false, \"author_flair_text\": \"Developer\", \"treatment_tags\": [], \"visited\": false, \"removed_by\": null, \"mod_note\": null, \"distinguished\": null, \"subreddit_id\": \"t5_6o778z\", \"author_is_blocked\": false, \"mod_reason_by\": null, \"num_reports\": 0, \"removal_reason\": null, \"link_flair_background_color\": \"#ff4500\", \"id\": \"w03cku\", \"is_robot_indexable\": true, \"num_duplicates\": 0, \"report_reasons\": [], \"author\": \"tata_contreras\", \"discussion_type\": null, \"num_comments\": 0, \"send_replies\": true, \"media\": null, \"contest_mode\": false, \"author_patreon_flair\": false, \"approved\": false, \"author_flair_text_color\": \"dark\", \"permalink\": \"/r/LizardByte/comments/w03cku/welcome_to_lizardbyte/\", \"whitelist_status\": null, \"stickied\": false, \"url\": \"https://www.reddit.com/r/LizardByte/comments/w03cku/welcome_to_lizardbyte/\", \"subreddit_subscribers\": 876, \"created_utc\": 1657930958.0, \"num_crossposts\": 0, \"mod_reports\": [], \"is_video\": false}}], \"before\": null}}, {\"kind\": \"Listing\", \"data\": {\"after\": null, \"dist\": null, \"modhash\": null, \"geo_filter\": \"\", \"children\": [], \"before\": null}}]"
"string": "{\"kind\": \"Listing\", \"data\": {\"after\": null, \"dist\": 1, \"modhash\": null, \"geo_filter\": \"\", \"children\": [{\"kind\": \"t1\", \"data\": {\"subreddit_id\": \"t5_6o778z\", \"approved_at_utc\": null, \"author_is_blocked\": false, \"comment_type\": null, \"edited\": false, \"mod_reason_by\": null, \"banned_by\": null, \"ups\": 1, \"num_reports\": 0, \"author_flair_type\": \"text\", \"total_awards_received\": 0, \"subreddit\": \"LizardByte\", \"author_flair_template_id\": \"5d220538-ff87-11ec-a9c4-56c680cbb67e\", \"likes\": null, \"replies\": \"\", \"user_reports\": [], \"saved\": false, \"id\": \"l20s21b\", \"banned_at_utc\": null, \"mod_reason_title\": null, \"gilded\": 0, \"archived\": false, \"collapsed_reason_code\": null, \"no_follow\": true, \"spam\": false, \"can_mod_post\": true, \"gildings\": {}, \"send_replies\": true, \"parent_id\": \"t3_w03cku\", \"score\": 1, \"author_fullname\": \"t2_393wfkmy\", \"created_utc\": 1714522546.0, \"report_reasons\": [], \"approved_by\": null, \"all_awardings\": [], \"ignore_reports\": false, \"body\": \"/sunshine vban\", \"awarders\": [], \"top_awarded_type\": null, \"downs\": 0, \"author_flair_css_class\": null, \"author_patreon_flair\": false, \"collapsed\": false, \"author_flair_richtext\": [], \"is_submitter\": false, \"body_html\": \"\\u003Cdiv class=\\\"md\\\"\\u003E\\u003Cp\\u003E/sunshine vban\\u003C/p\\u003E\\n\\u003C/div\\u003E\", \"removal_reason\": null, \"collapsed_reason\": null, \"associated_award\": null, \"stickied\": false, \"author_premium\": false, \"can_gild\": false, \"removed\": false, \"unrepliable_reason\": null, \"approved\": false, \"author_flair_text_color\": \"dark\", \"score_hidden\": false, \"permalink\": \"/r/LizardByte/comments/w03cku/welcome_to_lizardbyte/l20s21b/\", \"subreddit_type\": \"public\", \"locked\": false, \"name\": \"t1_l20s21b\", \"created\": 1714522546.0, \"author_flair_text\": \"Developer\", \"treatment_tags\": [], \"author\": \"ReenigneArcher\", \"link_id\": \"t3_w03cku\", \"subreddit_name_prefixed\": \"r/LizardByte\", \"controversiality\": 0, \"author_flair_background_color\": \"#94e044\", \"collapsed_because_crowd_control\": null, \"mod_reports\": [], \"mod_note\": null, \"distinguished\": null}}], \"before\": null}}"
},
"headers": {
"Accept-Ranges": [
Expand All @@ -138,10 +138,10 @@
"keep-alive"
],
"Content-Length": [
"3683"
"2037"
],
"Date": [
"Sat, 27 Apr 2024 01:29:36 GMT"
"Wed, 01 May 2024 02:35:44 GMT"
],
"NEL": [
"{\"report_to\": \"w3-reporting-nel\", \"max_age\": 14400, \"include_subdomains\": false, \"success_fraction\": 1.0, \"failure_fraction\": 1.0}"
Expand All @@ -153,8 +153,8 @@
"snooserv"
],
"Set-Cookie": [
"loid=0000000000ps86k4yd.2.1657372729000.Z0FBQUFBQm1MRlVBMXE0Z2MySTNhSmxhZTBCYzlDcEtFTG5nM1RhM3E5bW1Mckg5T3dmYlhic2JqOHFKUzlXQWRRM3BvZG9kczM3bzJYWU1RZ1lRaXZjNlhaTl9LYVNjcDVnQmdiV2hVVTdNTEMxY0F2V0dudW1HVXdOSURCWEFqd3Voamg5X19uWG0; Domain=reddit.com; Max-Age=63071999; Path=/; expires=Mon, 27-Apr-2026 01:29:36 GMT; secure; SameSite=None; Secure",
"session_tracker=gnralljneqorkibncg.0.1714181376469.Z0FBQUFBQm1MRlVBZEk4cHVFREhtYlZOZVNWYUlLTjRpeUlnZzhiODBLT25HMzFSTE5JZ3o0MndERTc5SlJ4V1NQVEdGSW00akpzMEpVbGJqSGRHSFdxbWVoa1d2VmhIRVp1aTIyaEdTbGgzVENoaG5YVEFJZlg5WXBpWXpsMDJQZmJ4Y1hpbEFFUWU; Domain=reddit.com; Max-Age=7199; Path=/; expires=Sat, 27-Apr-2024 03:29:36 GMT; secure; SameSite=None; Secure",
"loid=0000000000ps86k4yd.2.1657372729000.Z0FBQUFBQm1NYXFBTjF6QmZOUlBkTVRNQUNPdVZNUkxPQkMwY0VRakJfZ09pMVBjNkdVdTRTVVl2YURRanFFQUJXcklpVnZEeU1NXzlNRS03Y1ppOXQxMkpOckhMa1ozem5IMkc5SnZxWjVrV0lwSmZKUDFFSm55MXo5STM3MDNTVDQzdU1ZazRQZVM; Domain=reddit.com; Max-Age=63071999; Path=/; expires=Fri, 01-May-2026 02:35:44 GMT; secure; SameSite=None; Secure",
"session_tracker=goeleipgnjpgmqklcp.0.1714530944660.Z0FBQUFBQm1NYXFBcFRER1ZtRGdHYkVrSUpZQ1JmVTUwNkVWWDNzNzBKSUQtQnVsZ2JHTVladHNsLU5xSFB4SG9hVlZ4YTNQTWZHcDFBek00QlY0Q1V5WTY3cUpfV2ZvU0lBMEZLaXItbGFnTkItVzBMcEtoX2l1eUQyS1BtdS02d19tS1N4WVVKbzI; Domain=reddit.com; Max-Age=7199; Path=/; expires=Wed, 01-May-2024 04:35:44 GMT; secure; SameSite=None; Secure",
"csv=2; Max-Age=63072000; Domain=.reddit.com; Path=/; Secure; SameSite=None"
],
"Strict-Transport-Security": [
Expand Down Expand Up @@ -185,13 +185,13 @@
"-1"
],
"x-ratelimit-remaining": [
"989"
"954"
],
"x-ratelimit-reset": [
"24"
"256"
],
"x-ratelimit-used": [
"7"
"42"
],
"x-ua-compatible": [
"IE=edge"
Expand All @@ -201,7 +201,7 @@
"code": 200,
"message": "OK"
},
"url": "https://oauth.reddit.com/comments/w03cku/?limit=2048&sort=confidence&raw_json=1"
"url": "https://oauth.reddit.com/api/info/?id=t1_l20s21b&raw_json=1"
}
}
],
Expand Down
Loading