From 0fba7e8d3f0709945926324aa0ad959cd4b628d7 Mon Sep 17 00:00:00 2001 From: Jonas Schatz Date: Tue, 7 Mar 2023 20:35:35 +0100 Subject: [PATCH] Added dockerization and refactored core code --- .dockerignore | 8 + .env.sample | 6 + .gitignore | 3 + .vscode/settings.json | 4 + CHANGELOG.md | 27 +++ Dockerfile | 10 + MaxGoldtBot.ini.sample | 6 - MaxGoldtBot.py | 468 +++++++++++++++++++---------------------- MaxGoldtBot@.service | 9 - README.md | 115 ++-------- docker-compose.yml | 8 + requirements.txt | 2 + 12 files changed, 309 insertions(+), 357 deletions(-) create mode 100644 .dockerignore create mode 100644 .env.sample create mode 100644 .vscode/settings.json create mode 100644 Dockerfile delete mode 100644 MaxGoldtBot.ini.sample delete mode 100644 MaxGoldtBot@.service create mode 100644 docker-compose.yml create mode 100644 requirements.txt diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c89f5e8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.env.sample +.git +.gitignore +.vscode +venv +CHANGELOG.md +LICENSE +README.md \ No newline at end of file diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..1c22a25 --- /dev/null +++ b/.env.sample @@ -0,0 +1,6 @@ +client_id="" +client_secret="" +user_agent="" +username="" +password="" +subreddit="" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2b7f679..fd1adbf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ *.ini +.DS_Store +.env processed_comments_*.txt processed_submissions_*.txt +venv \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..43bcba9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python.linting.enabled": false, + "python.linting.flake8Enabled": true +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 52c77db..28b251e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,28 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.5.0] - 2023-03-07 + +### Added + +- Configuration through `.env` files +- Dockerization +- `requirements.txt` and `.vscode/settings.json` for smoother development + +### Changed + +- Refactoring to use an abstract base class +- Code formatting according to black-standards + +### Removed + +- Setup using `.ini`-files +- Ability to run the bot as a service, using docker instead + ## [0.4.0] - 2018-09-19 + ### Changed + - The bot will no longer respond to Bild Plus links. A Bild Plus link is any URL where the path starts with `/bild-plus/`. - The bot will no longer respond to Internet Archive links. These links usually @@ -15,7 +35,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. are found immediately following a slash (/). ## [0.3.0] - 2018-03-29 + ### Added + - The bot now also handles submissions. - Added an option `--prosfile` to control where the bot stores the IDs of processed submissions. The default is `processed_submissions_SUBREDDIT.txt` in @@ -23,7 +45,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. of 500 processed submissions. ## [0.2.0] - 2017-10-31 + ### Added + - Added an option `--procfile` to control where the bot stores the IDs of processed comments. The default is `processed_comments_SUBREDDIT.txt` in the bot's working directory. @@ -31,14 +55,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. in case there is an API exception (default: 15 minutes). ### Changed + - The bot will now keep a maximum of 600 processed comments in storage. When more than 600 processed comment IDs are stored, the procfile is pruned down to 500 comments. - The bot will now log a message when it is quit via keyboard interrupt (Ctrl-C). ## [0.1.1] - 2017-10-28 + - Fixed a bug where the bot would spontaneously halt due to a RequestException. This exception is now handled. ## [0.1.0] - 2017-10-27 + This is the initial release. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..86945be --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.8-slim-buster + +WORKDIR /app + +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt + +COPY . . + +CMD [ "python3", "MaxGoldtBot.py"] diff --git a/MaxGoldtBot.ini.sample b/MaxGoldtBot.ini.sample deleted file mode 100644 index aee569e..0000000 --- a/MaxGoldtBot.ini.sample +++ /dev/null @@ -1,6 +0,0 @@ -[MaxGoldtBot] -client_id= -client_secret= -user_agent=python:de.erixpage.maxgoldtbot:v0.4.0 (by /u/pille1842) -username= -password= diff --git a/MaxGoldtBot.py b/MaxGoldtBot.py index 6e1f9a2..f09a441 100755 --- a/MaxGoldtBot.py +++ b/MaxGoldtBot.py @@ -3,14 +3,16 @@ # with a quote from writer Max Goldt and an archive.is version of the linked # bild.de article(s) # -# Version: 0.4.0 -# Author: Eric Haberstroh +# Version: 0.5.0 +# Authors: Eric Haberstroh , +# Jonas Schatz # License: MIT +from abc import ABC, abstractmethod +from dataclasses import dataclass +from typing import Any, List import archiveis -from urllib.parse import urlparse -import argparse -import configparser +from urllib.parse import ParseResult, urlparse import logging import os import praw @@ -19,267 +21,235 @@ import sys import time -class MaxGoldtBotCommentParser: - processed_comments = [] - processed_comments_file = "" - config = None - reddit = None - subreddit = "" - sleeptime = 0 - regex = '(? 600: - logging.info('[comments] Pruning %s to 500 comments', self.processed_comments_file) - try: - with open(self.processed_comments_file, 'w') as file: - for comment in self.processed_comments[-500:]: - file.write(comment + '\n') - self.processed_comments = self.processed_comments[-500:] - except IOError as e: - logging.error('[comments] IO error while writing to %s: %s', self.processed_comments_file, e) - except (praw.exceptions.APIException, - praw.exceptions.ClientException, - prawcore.exceptions.RequestException) as e: - logging.warning('[comments] Got an exception: %s', e) - logging.warning('[comments] Will go to sleep for %d seconds', self.sleeptime) + for entity in self.entity_provider: + if entity.id in self.processed_entities: + continue + self.handle_entity(entity) + self.processed_entities.append(entity.id) + + try: + with open(self.processed_entites_file, "a") as file: + file.write(entity.id + "\n") + except IOError as e: + logging.error( + f"[{self.entity_type}] IO error while writing to {self.processed_entities_file}: {e}" + ) + if len(self.processed_entities) > 600: + self.processed_entities = self.prune_logfile() + + except ( + praw.exceptions.APIException, + praw.exceptions.ClientException, + prawcore.exceptions.RequestException, + ) as e: + logging.warning(f"[{self.entity_type}] Got an exception: {e}") + logging.warning( + f"[{self.entity_type}] Will go to sleep for {self.sleeptime} seconds" + ) time.sleep(self.sleeptime) except KeyboardInterrupt: - logging.critical('[comments] Bot has been killed by keyboard interrupt. Exiting') + logging.critical( + f"[{self.entity_type}] Bot has been killed by keyboard interrupt. Exiting" + ) sys.exit(0) - def handle_comment(self, comment): - logging.debug('[comments] Processing new comment %s', comment.id) - urls = re.findall(self.regex, comment.body) + def handle_entity(self, entity): + logging.debug("[comments] Processing new comment %s", entity.id) + urls: List[str] = self.extract_urls if urls: - logging.info('[comments] New comment %s with bild.de URLs found', comment.id) - archive_urls = [] - bildplus = 0 - for url in urls: - parsed_url = urlparse(url) - if parsed_url.path.startswith('/bild-plus/'): - logging.info('[comments] Skipping %s because it is probably a BILD+ link', url) - bildplus += 1 - continue - logging.info('[comments] Capturing %s', url) - archive_url = archiveis.capture(url) - if archive_url: - archive_urls.append(archive_url) - logging.info('[comments] Captured: %s', archive_url) - else: - logging.warning('[comments] Got an empty archive.is URL back. Something is wrong') - if len(urls) != len(archive_urls) + bildplus: - logging.warning('[comments] Found %d bild.de URLs, but got only %d archive.is links', len(urls), len(archive_urls)) + logging.info( + f"[{self.entity_type}] New {self.entity_type} with bild.de URLs found" + ) + archive_urls: List[str] = self.search_and_archive_bild_urls(urls) + if archive_urls: - links = "\n- ".join(archive_urls) - body = ("> Diese Zeitung ist ein Organ der Niedertracht. Es ist falsch, sie zu lesen.\n" - "> Jemand, der zu dieser Zeitung beiträgt, ist gesellschaftlich absolut inakzeptabel.\n" - "> Es wäre verfehlt, zu einem ihrer Redakteure freundlich oder auch nur höflich zu sein.\n" - "> Man muß so unfreundlich zu ihnen sein, wie es das Gesetz gerade noch zuläßt.\n" - "> Es sind schlechte Menschen, die Falsches tun.\n\n" - "[Max Goldt](https://de.wikipedia.org/wiki/Max_Goldt), deutscher Schriftsteller\n\n" - "Du kannst diesen Artikel auf archive.is lesen, wenn du nicht auf bild.de gehen willst:\n\n- " \ - + links + \ - "\n\n" - "----\n\n" - "^^[Info](https://www.reddit.com/r/MaxGoldtBot) | " - "[Autor](https://www.reddit.com/u/pille1842) | " - "[GitHub](https://github.com/pille1842/MaxGoldtBot) | " - "[Warum die Bild schlecht ist]" - "(http://www.bildblog.de/62600/warum-wir-gegen-die-bild-zeitung-kaempfen/)") - comment.reply(body) - logging.info('[comments] Replied to %s with %d links', comment.id, len(archive_urls)) + body = self.create_submission_body(archive_urls) + entity.reply(body) + logging.info( + "[comments] Replied to %s with %d links", + entity.id, + len(archive_urls), + ) else: - logging.warning('[comments] No reply to %s: %d bild.de links found, none archived', comment.id, len(urls)) - else: - logging.debug('[comments] No relevant URLs found in %s', comment.id) - -class MaxGoldtBotSubmissionParser: - processed_submissions = [] - processed_submissions_file = "" - config = None - reddit = None - subreddit = "" - sleeptime = 0 - regex = '(? 600: - logging.info('[submissions] Pruning %s to 500 submissions', self.processed_submissions_file) - try: - with open(self.processed_submissions_file, 'w') as file: - for submission in self.processed_submissions[-500:]: - file.write(submission + '\n') - self.processed_submissions = self.processed_submissions[-500:] - except IOError as e: - logging.error('[submissions] IO error while writing to %s: %s', self.processed_submissions_file, e) - except (praw.exceptions.APIException, - praw.exceptions.ClientException, - prawcore.exceptions.RequestException) as e: - logging.warning('[submissions] Got an exception: %s', e) - logging.warning('[submissions] Will go to sleep for %d seconds', self.sleeptime) - time.sleep(self.sleeptime) - except KeyboardInterrupt: - logging.critical('[submissions] Bot has been killed by keyboard interrupt. Exiting') - sys.exit(0) + @abstractmethod + def extract_urls(self, entity) -> List[str]: + pass - def handle_submission(self, submission): - logging.debug('[submissions] Processing new submission %s', submission.id) - if submission.selftext == '': - urls = re.findall(self.regex, submission.url) - else: - urls = re.findall(self.regex, submission.selftext) - if urls: - logging.info('[submissions] New submission %s with bild.de URLs found', submission.id) - archive_urls = [] - bildplus = 0 - for url in urls: - parsed_url = urlparse(url) - if parsed_url.path.startswith('/bild-plus/'): - logging.info('[submissions] Skipping %s because it is probably a BILD+ link', url) - bildplus += 1 - continue - logging.info('[submissions] Capturing %s', url) - archive_url = archiveis.capture(url) - if archive_url: - archive_urls.append(archive_url) - logging.info('[submissions] Captured: %s', archive_url) - else: - logging.warning('[submissions] Got an empty archive.is URL back. Something is wrong') - if len(urls) != len(archive_urls) + bildplus: - logging.warning('[submissions] Found %d bild.de URLs, but got only %d archive.is links', len(urls), len(archive_urls)) - if archive_urls: - links = "\n- ".join(archive_urls) - body = ("> Diese Zeitung ist ein Organ der Niedertracht. Es ist falsch, sie zu lesen.\n" - "> Jemand, der zu dieser Zeitung beiträgt, ist gesellschaftlich absolut inakzeptabel.\n" - "> Es wäre verfehlt, zu einem ihrer Redakteure freundlich oder auch nur höflich zu sein.\n" - "> Man muß so unfreundlich zu ihnen sein, wie es das Gesetz gerade noch zuläßt.\n" - "> Es sind schlechte Menschen, die Falsches tun.\n\n" - "[Max Goldt](https://de.wikipedia.org/wiki/Max_Goldt), deutscher Schriftsteller\n\n" - "Du kannst diesen Artikel auf archive.is lesen, wenn du nicht auf bild.de gehen willst:\n\n- " \ - + links + \ - "\n\n" - "----\n\n" - "^^[Info](https://www.reddit.com/r/MaxGoldtBot) | " - "[Autor](https://www.reddit.com/u/pille1842) | " - "[GitHub](https://github.com/pille1842/MaxGoldtBot) | " - "[Warum die Bild schlecht ist]" - "(http://www.bildblog.de/62600/warum-wir-gegen-die-bild-zeitung-kaempfen/)") - submission.reply(body) - logging.info('[submissions] Replied to %s with %d links', submission.id, len(archive_urls)) + def search_and_archive_bild_urls(self, urls: List[str]) -> List[str]: + archive_urls: List[str] = [] + bildplus: int = 0 + + for url in urls: + parsed_url: ParseResult = urlparse(url) + if parsed_url.path.startswith("/bild-plus/"): + logging.info( + f"[{self.entity_type}] Skipping {url} because it is probably a BILD+ link" + ) + bildplus += 1 + continue + logging.info(f"[{self.entity_type}] Capturing url") + archive_url: str = archiveis.capture(url) + if archive_url: + archive_urls.append(archive_url) + logging.info(f"[{self.entity_type}] Captured: {archive_url}") else: - logging.warning('[submissions] No reply to %s: %d bild.de links found, none archived', submission.id, len(urls)) + logging.warning( + f"[{self.entity_type}] Got an empty archive.is URL back. Something is wrong" + ) + if len(urls) != len(archive_urls) + bildplus: + logging.warning( + f"[{self.entity_type}] Found {len(urls)} bild.de URLs, but got only {len(archive_urls)} archive.is links" + ) + + return archive_urls + + def create_submission_body(archive_urls: List[str]) -> str: + links: str = "\n- ".join(archive_urls) + body: str = ( + "> Diese Zeitung ist ein Organ der Niedertracht. Es ist falsch, sie zu lesen.\n" + "> Jemand, der zu dieser Zeitung beiträgt, ist gesellschaftlich absolut inakzeptabel.\n" + "> Es wäre verfehlt, zu einem ihrer Redakteure freundlich oder auch nur höflich zu sein.\n" + "> Man muß so unfreundlich zu ihnen sein, wie es das Gesetz gerade noch zuläßt.\n" + "> Es sind schlechte Menschen, die Falsches tun.\n\n" + "[Max Goldt](https://de.wikipedia.org/wiki/Max_Goldt), deutscher Schriftsteller\n\n" + "Du kannst diesen Artikel auf archive.is lesen, wenn du nicht auf bild.de gehen willst:\n\n- " + + links + + "\n\n" + "----\n\n" + "^^[Info](https://www.reddit.com/r/MaxGoldtBot) | " + "[Autor](https://www.reddit.com/u/joni_corazon) | " + "[GitHub](https://github.com/jonasschatz/MaxGoldtBot) | " + "[Warum die Bild schlecht ist]" + "(http://www.bildblog.de/62600/warum-wir-gegen-die-bild-zeitung-kaempfen/)" + ) + return body + + def prune_logfile(self): + logging.info( + f"[{self.entity_type}] Pruning {self.processed_entities_file} to 500 {self.entity_type}" + ) + try: + with open(self.processed_entities, "w") as file: + for entity in self.processed_entities[-500:]: + file.write(entity + "\n") + return self.processed_entities[-500:] + except IOError as e: + logging.error( + f"{[self.entity_type]} IO error while writing to {self.processed_entities_file}: {e}" + ) + + +class MaxGoldtBotCommentParser(MaxGoldtBotEntityParser): + def __init__(self, config: Config): + self.entity_type: str = "comments" + super().__init__(config) + self.entity_provider = self.subreddit.stream.comments(skip_existing=True) + + def extract_urls(self, entity) -> List[str]: + return re.findall(self.regex, entity.body) + + +class MaxGoldtBotSubmissionParser(MaxGoldtBotEntityParser): + def __init__(self, config: Config): + self.entity_type: str = "submissions" + super().__init__(config) + self.entity_provider = self.subreddit.stream.submissions(skip_existing=True) + + def extract_urls(self, entity) -> List[str]: + if entity.selftext == "": + return re.findall(self.regex, entity.url) else: - logging.debug('[submissions] No relevant URLs found in %s', submission.id) - -parser = argparse.ArgumentParser() -parser.add_argument('--config', action='store', dest='config_file', default='MaxGoldtBot.ini', - help='a configuration file to read from (default: MaxGoldtBot.ini)') -parser.add_argument('--logfile', action='store', dest='logfile', - help='a logfile to write to (default: stdout)') -parser.add_argument('--loglevel', action='store', dest='loglevel', default='WARNING', - help='a loglevel (default: WARNING)') -parser.add_argument('--procfile', action='store', dest='procfile', - help='a file to store processed comment IDs in') -parser.add_argument('--prosfile', action='store', dest='prosfile', - help='a file to store processed submission IDs in') -parser.add_argument('--sleeptime', action='store', dest='sleeptime', default=15 * 60, type=int, - help='number of seconds to sleep in case of an API exception') -parser.add_argument('subreddit', action='store', - help='subreddit to process comments from') -arguments = parser.parse_args() -numeric_level = getattr(logging, arguments.loglevel.upper(), None) -if not isinstance(numeric_level, int): - raise ValueError('Invalid log level: %s' % arguments.loglevel) -logformat = '[%(asctime)s] %(levelname)s: %(message)s' -if arguments.logfile: - logging.basicConfig(level=numeric_level, format=logformat, filename=arguments.logfile) - logging.debug('Logging configuration: loglevel %s, logfile %s', arguments.loglevel, arguments.logfile) -else: - logging.basicConfig(level=numeric_level, format=logformat) - logging.debug('Logging configuration: loglevel %s, display output', arguments.loglevel) - -logging.debug('Forking to let the child care about submissions') -newpid = os.fork() -if newpid == 0: - logging.info('Started handling submissions') - submission_parser = MaxGoldtBotSubmissionParser(arguments) - submission_parser.run() -else: - logging.info('Started handling comments (submissions given to PID %d)', newpid) - comment_parser = MaxGoldtBotCommentParser(arguments) - comment_parser.run() + return re.findall(self.regex, entity.selftext) + + +if __name__ == "__main__": + config: Config = Config() + + newpid: int = os.fork() + if newpid == 0: + logging.info("Started handling submissions") + submission_parser = MaxGoldtBotSubmissionParser(config) + submission_parser.run() + else: + logging.info("Started handling comments (submissions given to PID %d)", newpid) + comment_parser = MaxGoldtBotCommentParser(config) + comment_parser.run() diff --git a/MaxGoldtBot@.service b/MaxGoldtBot@.service deleted file mode 100644 index f86c4eb..0000000 --- a/MaxGoldtBot@.service +++ /dev/null @@ -1,9 +0,0 @@ -[Unit] -Description=Max Goldt Bot - -[Service] -ExecStart=/path/to/MaxGoldtBot.py --config=/path/to/config.ini --procfile=/path/to/procfile_%I.txt --prosfile=/path/to/prosfile_%I.txt --logfile=/path/to/logfile_%I.log %I -Restart=always - -[Install] -WantedBy=default.target diff --git a/README.md b/README.md index 45b867d..7eec867 100644 --- a/README.md +++ b/README.md @@ -16,118 +16,47 @@ to read the article. This program is licensed under the MIT license, see the LICENSE file. To see what has changed over time, have a look at -[CHANGELOG.md](https://github.com/pille1842/MaxGoldtBot/blob/master/CHANGELOG.md). +[CHANGELOG.md](https://github.com/jonasschatz/MaxGoldtBot/blob/master/CHANGELOG.md). ## Prerequisites To run this bot, you will need: -- Python 3 (tested with 3.5.2 and 3.4.2 on Ubuntu and Debian) +- Python 3 (tested with 3.9 on MacOS) - The following packages (install via `pip` / `pip3`): - - `archiveis` (a simple wrapper for archive.is) - - `praw` (the Python Reddit wrapper) - -## Setup - -Rename the `MaxGoldtBot.ini.sample` file to `MaxGoldtBot.ini`. In this file, -store the configuration values for your bot. See section [Configuration](#configuration) -for more information. - -## Running the Bot - -To run the bot, simply call it with the name of a subreddit as an argument: - -``` -$ ./MaxGoldtBot.py MySubreddit -``` - -### Running the bot as a systemd service - -If you wish to run this bot as a systemd user service, modify -`MaxGoldtBot@.service` and fill in the relevant paths. Then copy the file to -`~/.config/systemd/user/`: - -``` -$ mkdir -pv ~/.config/systemd/user && cp MaxGoldtBot@.service ~/.config/systemd/user/ -``` - -If you wish to run this service on boot (e.g. not only when you are logged in), -you need to enable lingering (see [loginctl man page](https://www.freedesktop.org/software/systemd/man/loginctl.html)): - -``` -# loginctl enable-linger YOURUSERNAME -``` - -After that, you can enable the bot service for any subreddit with the following -command: - -``` -$ systemctl --user enable MaxGoldtBot@SUBREDDIT.service -``` - -To start the bot immediately, run: - -``` -$ systemctl --user start MaxGoldtBot@SUBREDDIT.service -``` - -## Command-line options - -### Configuration file - -By default, the bot reads its configuration from `MaxGoldtBot.ini` in the -current directory. However, you can pass any configuration file name you like to -the `--config` option. - -### Logging - -By default, the bot will log to standard output and only display messages with -a loglevel of WARNING or above. Use `--loglevel` to change the minimum loglevel -(possible values are DEBUG, INFO, WARNING, ERROR, CRITICAL). - -If you wish to log messages into a file instead of standard output, you can pass -a filename to the `--logfile` option. - -### Processed comments - -The bot keeps a list of already processed comments to avert responding to any -comments twice. By default, this list is stored in a file called -`processed_comments_SUBREDDIT.txt` in the current directory. You can override -the path to this file with the `--procfile` option. - -### Processed submissions - -The bot also keeps a list of already processed submissions to avert responding -to any submissions twice. By default, this list is stored in a file called -`processed_submissions_SUBREDDIT.txt` in the current directory. You can override -the path to this file with the `--prosfile` option. - -### Sleep time - -When the bot gets an API exception from Reddit (e.g. because the bot is trying -too hard or because there are connectivity issues), it will go to sleep for -a while. By default, this sleep time is 15 minutes. However, you can override -this time with `--sleeptime SECONDS`. + - `archiveis` (a simple wrapper for archive.is) + - `praw` (the Python Reddit wrapper) +- Docker / docker-compose. While the latter might seem like overkill, it is a very + convenient way to set up the bot with the correct configuration. ## Configuration -The sample configuration file (`MaxGoldtBot.ini.sample`) should give you a good -idea of what you need to configure to make this bot work. The configuration file -should contain the following items in a section called `[MaxGoldtBot]`: +The sample configuration file (`.env.sample`) should give you a good +idea of what you need to configure to make this bot work. Rename it to `.env` to +make it work. The configuration file should contain the following items: - **`client_id`** -- this is the ID of your Reddit application. To obtain one, - go to reddit.com > preferences > apps and create a new app of type "script". - The client ID is displayed beneath your application's name. + go to reddit.com > preferences > apps (old reddit) or User Settings > Safety & + Privacy > Manage third-party app authorization (new reddit) and create a new + app of type "script". The client ID is displayed beneath your application's name. - **`client_secret`** -- this is the secret key of your Reddit application. Never let anyone see this! You can find it in the details of your app under reddit.com > preferences > apps. - **`user_agent`** -- this is a User-Agent string that the bot will provide to - Reddit when making requests. The default sample is a good idea. Generally, the - User-Agent string should have the following format: + Reddit when making requests. Generally, the user agent string should have the + following format: `platform:tld.yourdomain.yourapp:vX.Y.Z (by /u/YourUsername)` - **`username`** -- this is the username of your Reddit bot. - **`password`** -- this is your Reddit bot's password. Without it, the bot can read Reddit comments, but cannot reply to them. +- **`subreddit`** -- The subreddit the bot should be active on, e.g. `"test"`. + Make sure to ask the Mod's permission before you let it loose. On + [r/de](www.reddit.com/r/de) it is not allowed due to source derailment. + +## Deployment + +After entering your details in the `.env` file, you can run the bot by simply +running `docker compose up` in your terminal. ## Subreddit diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c7d4ad5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +version: "3.5" +services: + max-goldt-bot: + env_file: .env + container_name: MaxGoldtBot + build: + context: . + dockerfile: Dockerfile \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1fc3f0c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +archiveis==0.0.9 +praw==7.6.1