From c48bf54dc138c7fe3b051020ed3619a83d607de2 Mon Sep 17 00:00:00 2001 From: retoor Date: Wed, 27 Nov 2024 02:59:42 +0100 Subject: [PATCH] Initial commit --- .gitea/workflows/build.yaml | 22 ++++++ .gitignore | 4 + Makefile | 18 +++++ README.md | 28 +++++++ dist/Ragnar-1.3.37-py3-none-any.whl | Bin 0 -> 4687 bytes dist/ragnar-1.3.37.tar.gz | Bin 0 -> 3690 bytes pyproject.toml | 3 + setup.cfg | 24 ++++++ src/Ragnar.egg-info/PKG-INFO | 12 +++ src/Ragnar.egg-info/SOURCES.txt | 14 ++++ src/Ragnar.egg-info/dependency_links.txt | 1 + src/Ragnar.egg-info/entry_points.txt | 2 + src/Ragnar.egg-info/requires.txt | 3 + src/Ragnar.egg-info/top_level.txt | 1 + src/ragnar/__init__.py | 0 src/ragnar/__main__.py | 0 src/ragnar/api.py | 96 +++++++++++++++++++++++ src/ragnar/bot.py | 93 ++++++++++++++++++++++ src/ragnar/cache.py | 14 ++++ src/ragnar/cli.py | 42 ++++++++++ 20 files changed, 377 insertions(+) create mode 100644 .gitea/workflows/build.yaml create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 dist/Ragnar-1.3.37-py3-none-any.whl create mode 100644 dist/ragnar-1.3.37.tar.gz create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100644 src/Ragnar.egg-info/PKG-INFO create mode 100644 src/Ragnar.egg-info/SOURCES.txt create mode 100644 src/Ragnar.egg-info/dependency_links.txt create mode 100644 src/Ragnar.egg-info/entry_points.txt create mode 100644 src/Ragnar.egg-info/requires.txt create mode 100644 src/Ragnar.egg-info/top_level.txt create mode 100644 src/ragnar/__init__.py create mode 100644 src/ragnar/__main__.py create mode 100644 src/ragnar/api.py create mode 100644 src/ragnar/bot.py create mode 100644 src/ragnar/cache.py create mode 100644 src/ragnar/cli.py diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..37efb31 --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,22 @@ +name: Build Ragnar anti spam bot +run-name: Build Ragnar anti spam bot +on: [push] + +jobs: + Build: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + - name: List files in the repository + run: | + ls ${{ gitea.workspace }} + - run: echo "Install dependencies." + - run: apt update + - run: apt install python3 python3-pip python3-venv make -y + - run: make + - run: git add . + - run: git config --global user.email "bot@molodetz.com" + - run: git config --global user.name "bot" + - run: git commit -a -m "Update export statistics" + - run: git push diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb26022 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +config.py +.venv +.history +src/ragnar/__pycache__ \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..96ceb6c --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +all: ensure_env format build install + +format: + ./.venv/bin/python -m pip install black + ./.venv/bin/python -m black . + +ensure_env: + -@python3 -m venv .venv + +build: + ./.venv/bin/python -m pip install build + ./.venv/bin/python -m build . + +install: + ./.venv/bin/python -m pip install -e . + +run: + python -m ragnar.run \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..aecbe8a --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# Ragnar + +This is an anti spam bot network. It is named after the viking for no obvious reason. + +I'm not happy about the quality of the source and it is not a representation of my usual work. If I would've spend more efford there would be some types and I've would use aiohttp and would've used context managers for example. Despite the source lacking a certain quality, the bots work great and are made not to be annoying to the server by not connecting all at once and caching certain things like user profile / user id and if a reand already is flaged for example to not annoy the server. + +The bots have user name no-spam[1-4] but flag under a Russian girl name, also for no obvious reason. I liked it more than some technical name. Will probably rename the bots later. Could be that devRants prevents me to do that within a half year. It doesn't matter much, if the bots do a good job, we will barely see them. + +I expect this project tomorrow to have deployed fully functional on a server. + +## In progress + +The bots work perfect in sense that they're doing what they're programmed to do. +But the programming is not finished yet: + - the criteria can be better, tips how to optimize are very welcome. + - at this moment, they can only flag, useless, but we will have indication of future content to be cancelled. Every spam message should have a flag. If not, contact @retoor. + - the downvote function doesn't work because I couldn't figure out what value I had to post. Who knows it? After this, it's kinda done. + - a decent deployment on my server. Now it runs on my laptop because it's not done yet and it got late. + +## How they work +One process starts four bots named no-spam[1-4]. These bots look at new rants. + +If there is a new rant: +1. check if user has more than five posts. If so, it will not be seen as spam. +2. it will check certain keywords like hacker / money crypto related if so continue to step 3. +3. user will be informed by the bots that his rant is flagged and what to do about it. +4. rant will be downvoted by the four bots making it disappear. + diff --git a/dist/Ragnar-1.3.37-py3-none-any.whl b/dist/Ragnar-1.3.37-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..4c7a495b99f4ed2aad784484a3786ff7905a0500 GIT binary patch literal 4687 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 literal 0 HcmV?d00001 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..07de284 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..9cf2428 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,24 @@ +[metadata] +name = Ragnar +version = 1.3.37 +description = Anti spam bot for dR +author = Retoor +author_email = retoor@molodetz.nl +license = MIT +long_description = file: README.md +long_description_content_type = text/markdown + +[options] +packages = find: +package_dir = + = src +python_requires = >=3.7 +install_requires = + requests==2.32.3 + +[options.packages.find] +where = src + +[options.entry_points] +console_scripts = + ragnar.run = ragnar.cli:run \ No newline at end of file diff --git a/src/Ragnar.egg-info/PKG-INFO b/src/Ragnar.egg-info/PKG-INFO new file mode 100644 index 0000000..1667748 --- /dev/null +++ b/src/Ragnar.egg-info/PKG-INFO @@ -0,0 +1,12 @@ +Metadata-Version: 2.1 +Name: Ragnar +Version: 1.3.37 +Summary: Anti spam bot for dR +Author: Retoor +Author-email: retoor@molodetz.nl +License: MIT +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +Requires-Dist: aiohttp==3.10.10 +Requires-Dist: dataset==1.6.2 +Requires-Dist: requests==2.32.3 diff --git a/src/Ragnar.egg-info/SOURCES.txt b/src/Ragnar.egg-info/SOURCES.txt new file mode 100644 index 0000000..16385dd --- /dev/null +++ b/src/Ragnar.egg-info/SOURCES.txt @@ -0,0 +1,14 @@ +pyproject.toml +setup.cfg +src/Ragnar.egg-info/PKG-INFO +src/Ragnar.egg-info/SOURCES.txt +src/Ragnar.egg-info/dependency_links.txt +src/Ragnar.egg-info/entry_points.txt +src/Ragnar.egg-info/requires.txt +src/Ragnar.egg-info/top_level.txt +src/ragnar/__init__.py +src/ragnar/__main__.py +src/ragnar/api.py +src/ragnar/bot.py +src/ragnar/cache.py +src/ragnar/cli.py \ No newline at end of file diff --git a/src/Ragnar.egg-info/dependency_links.txt b/src/Ragnar.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/Ragnar.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/Ragnar.egg-info/entry_points.txt b/src/Ragnar.egg-info/entry_points.txt new file mode 100644 index 0000000..3a5bae1 --- /dev/null +++ b/src/Ragnar.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +ragnar.run = ragnar.cli:run diff --git a/src/Ragnar.egg-info/requires.txt b/src/Ragnar.egg-info/requires.txt new file mode 100644 index 0000000..8cf6b22 --- /dev/null +++ b/src/Ragnar.egg-info/requires.txt @@ -0,0 +1,3 @@ +aiohttp==3.10.10 +dataset==1.6.2 +requests==2.32.3 diff --git a/src/Ragnar.egg-info/top_level.txt b/src/Ragnar.egg-info/top_level.txt new file mode 100644 index 0000000..aed9676 --- /dev/null +++ b/src/Ragnar.egg-info/top_level.txt @@ -0,0 +1 @@ +ragnar diff --git a/src/ragnar/__init__.py b/src/ragnar/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/ragnar/__main__.py b/src/ragnar/__main__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/ragnar/api.py b/src/ragnar/api.py new file mode 100644 index 0000000..c16a5ec --- /dev/null +++ b/src/ragnar/api.py @@ -0,0 +1,96 @@ +import requests, json + +from ragnar.cache import method_cache + + +class Api: + + base_url = "https://www.devrant.io/api/" + + def __init__(self, username, password): + self.username = username + self.password = password + self.auth = None + + def post_comment(self, rant_id, text): + response = requests.post( + self.base_url + "devrant/rants/" + str(rant_id) + "/comments", + data={ + "app": 3, + "user_id": self.auth["user_id"], + "token_id": self.auth["token_id"], + "token_key": self.auth["token_key"], + "comment": text, + }, + ) + return response.json() + + @method_cache + def login(self): + print("New login, cache miss?") + rawdata = requests.post( + self.base_url + "users/auth-token", + data={"username": self.username, "password": self.password, "app": 3}, + ) + rawdata = rawdata.json() + if not rawdata["success"]: + self.auth = None + else: + self.auth = { + "token_id": rawdata["auth_token"]["id"], + "token_key": rawdata["auth_token"]["key"], + "user_id": rawdata["auth_token"]["user_id"], + } + return self.auth + + @method_cache + def get_profile(self, id_): + url = self.base_url + "users/" + str(id_) + params = { + "app": 3, + } + response = requests.get(url, params) + return json.loads(response.text)["profile"] + + def get_search(self, term): + url = self.base_url + "devrant/search" + params = {"app": 3, "term": term} + response = requests.get(url, params, timeout=5) + obj = json.loads(response.text) + return obj + + def post_rant_vote(self, id, vote): + response = requests.post( + self.base_url + "devrant/rants/" + str(id) + "/vote", + data={ + "app": 3, + "user_id": self.auth["user_id"], + "token_id": self.auth["token_id"], + "token_key": self.auth["token_key"], + "vote": vote, + # "plat": 3, + }, + ) + return response.json() + + def get_rant(self, rant_id): + url = self.base_url + "devrant/rants/" + str(rant_id) + params = { + "app": 3, + } + response = requests.get(url, params, timeout=5) + return json.loads(response.text) + + def get_rants(self, sort, limit, skip): + url = self.base_url + "devrant/rants" + params = {"app": 3, "sort": sort, "limit": limit, "skip": skip} + + response = requests.get(url, params, timeout=5) + return json.loads(response.text)["rants"] + + @method_cache + def get_user_id(self, name): + url = self.base_url + "get-user-id" + params = {"app": 3, "username": name} + response = requests.get(url, params) + return json.loads(response.text).get("user_id", None) diff --git a/src/ragnar/bot.py b/src/ragnar/bot.py new file mode 100644 index 0000000..32e7bed --- /dev/null +++ b/src/ragnar/bot.py @@ -0,0 +1,93 @@ +from ragnar.api import Api +import time +import random +from ragnar.cache import method_cache + + +class Bot: + + def __init__(self, username, password): + self.username = username + self.password = password + self.name = self.username.split("@")[0] + + names = { + "no-spam": "anna", + "no-spam1": "ira", + "no-spam2": "katya", + "no-spam3": "nastya", + "no-spam4": "vira", + } + self.name = names.get(self.name, "everyone") + self.mark_text = "You rant is flagged as spam by {}. Read bot source code to find out how to prevent this. Have a nice day! (Bot does not downvote yet, couldn't figure out what the downvote value should be. Upvote these bots btw so they can post a link. They're quite effective, they'll end spam.)".format( + self.name + ) + self.auth = None + self.triggers = ["$", "crypto", "hacker", "recovery"] + + self.api = Api(username=self.username, password=self.password) + + def rsleepii(self): + time.sleep(random.randint(1, 3)) + + @method_cache + def login(self): + self.rsleepii() + self.auth = self.api.login() + if not self.auth: + print("Authentication for {} failed.".format(self.username)) + raise Exception("Login error") + print("Authentication succesful for {}.".format(self.username)) + + @method_cache + def is_sus_rant(self, rant_id, rant_text): + clean_text = rant_text.replace(" ", "").lower() + for trigger in self.triggers: + if trigger in clean_text: + return True + + def is_flagged_as_sus(self, rant_id, num_comments): + if not num_comments: + return False + self.rsleepii() + rant = self.api.get_rant(rant_id) + for comment in rant.get("comments", []): + if self.mark_text in comment.get("body", ""): + return True + return False + + @method_cache + def is_user_sus(self, username): + user_id = self.api.get_user_id(username) + profile = self.api.get_profile(user_id) + score = profile["score"] + if score < 5: + print("User {} is sus with his score of only {}.".format(username, score)) + return True + else: + return False + + def mark_as_sus(self, rant): + self.rsleepii() + self.api.post_comment(rant["id"], self.mark_text) + + def fight(self): + self.rsleepii() + rants = self.api.get_rants("recent", 5, 0) + for rant in rants: + if not self.is_user_sus(rant["user_username"]): + print("User {} is trusted.".format(rant["user_username"])) + continue + if not self.is_sus_rant(rant["id"], rant["text"]): + print("Rant by {} is not sus.".format(rant["user_username"])) + continue + if self.is_flagged_as_sus(rant["id"], rant.get("num_comments")): + continue + print("Rant is not {} flagged as sus yet.".format(rant["user_username"])) + print("Flagging rant by {} as sus.".format(rant["user_username"])) + self.mark_as_sus(rant) + self.down_vote_rant(rant) + + def down_vote_rant(self, rant): + print("Downvoting rant by {}.".format(rant["user_username"])) + print(self.api.post_rant_vote(rant["id"], 4)) diff --git a/src/ragnar/cache.py b/src/ragnar/cache.py new file mode 100644 index 0000000..a6f2e66 --- /dev/null +++ b/src/ragnar/cache.py @@ -0,0 +1,14 @@ +# The functools lru_cache didn't work well on a class so +# I had to create a custom cashing method. Which is fine. + + +def method_cache(func): + cache = {} + + def wrapper(*args, **kwargs): + key = (args, tuple(sorted(kwargs.items()))) + if key not in cache: + cache[key] = func(*args, **kwargs) + return cache[key] + + return wrapper diff --git a/src/ragnar/cli.py b/src/ragnar/cli.py new file mode 100644 index 0000000..2430706 --- /dev/null +++ b/src/ragnar/cli.py @@ -0,0 +1,42 @@ +import argparse +from ragnar.bot import Bot +import random +import time +from concurrent.futures import ThreadPoolExecutor as Executor + + +def parse_args(): + parser = argparse.ArgumentParser(description="Process username and password.") + + parser.add_argument("-u", "--username", required=True, help="Your username") + parser.add_argument("-p", "--password", required=True, help="Your password") + + return parser.parse_args() + + +def bot_task(username, password): + time.sleep(random.randint(1, 20)) + bot = Bot(username=username, password=password) + bot.login() + while True: + time.sleep(random.randint(1, 20)) + try: + bot.fight() + except Exception as ex: + print(ex) + + +def main(): + args = parse_args() + with Executor(4) as executor: + for x in range(1, 5): + username = "no-spam{}@molodetz.nl".format(str(x)) + password = args.password + time.sleep(1) + print("Starting bot {}.".format(username)) + executor.submit(bot_task, username, password) + executor.shutdown(wait=True) + + +def run(): + main()