Compare commits
1 Commits
ac851d76c3
...
49ea9d1359
Author | SHA1 | Date | |
---|---|---|---|
|
49ea9d1359 |
BIN
dist/Ragnar-1.3.37-py3-none-any.whl
vendored
BIN
dist/Ragnar-1.3.37-py3-none-any.whl
vendored
Binary file not shown.
BIN
dist/ragnar-1.3.37.tar.gz
vendored
BIN
dist/ragnar-1.3.37.tar.gz
vendored
Binary file not shown.
@ -7,6 +7,33 @@ Author-email: retoor@molodetz.nl
|
|||||||
License: MIT
|
License: MIT
|
||||||
Requires-Python: >=3.7
|
Requires-Python: >=3.7
|
||||||
Description-Content-Type: text/markdown
|
Description-Content-Type: text/markdown
|
||||||
Requires-Dist: aiohttp==3.10.10
|
|
||||||
Requires-Dist: dataset==1.6.2
|
|
||||||
Requires-Dist: requests==2.32.3
|
Requires-Dist: requests==2.32.3
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
README.md
|
||||||
pyproject.toml
|
pyproject.toml
|
||||||
setup.cfg
|
setup.cfg
|
||||||
src/Ragnar.egg-info/PKG-INFO
|
src/Ragnar.egg-info/PKG-INFO
|
||||||
@ -12,3 +13,5 @@ src/ragnar/api.py
|
|||||||
src/ragnar/bot.py
|
src/ragnar/bot.py
|
||||||
src/ragnar/cache.py
|
src/ragnar/cache.py
|
||||||
src/ragnar/cli.py
|
src/ragnar/cli.py
|
||||||
|
src/ragnar/tests/__init__.py
|
||||||
|
src/ragnar/tests/bot.py
|
@ -1,3 +1 @@
|
|||||||
aiohttp==3.10.10
|
|
||||||
dataset==1.6.2
|
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
import logging
|
|
||||||
import sys
|
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.DEBUG,
|
|
||||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
||||||
handlers=[
|
|
||||||
logging.StreamHandler(sys.stdout),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
@ -1,7 +1,7 @@
|
|||||||
import requests, json
|
import requests, json
|
||||||
|
|
||||||
from ragnar.cache import method_cache
|
from ragnar.cache import method_cache
|
||||||
from ragnar import log
|
|
||||||
|
|
||||||
class Api:
|
class Api:
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ class Api:
|
|||||||
|
|
||||||
@method_cache
|
@method_cache
|
||||||
def login(self):
|
def login(self):
|
||||||
log.info("Logged in as {}".format(self.username))
|
print("New login, cache miss?")
|
||||||
rawdata = requests.post(
|
rawdata = requests.post(
|
||||||
self.base_url + "users/auth-token",
|
self.base_url + "users/auth-token",
|
||||||
data={"username": self.username, "password": self.password, "app": 3},
|
data={"username": self.username, "password": self.password, "app": 3},
|
||||||
|
@ -3,7 +3,7 @@ import time
|
|||||||
import random
|
import random
|
||||||
from ragnar.cache import method_cache
|
from ragnar.cache import method_cache
|
||||||
import re
|
import re
|
||||||
from ragnar import log
|
|
||||||
|
|
||||||
class Bot:
|
class Bot:
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ class Bot:
|
|||||||
self.username = username
|
self.username = username
|
||||||
self.password = password
|
self.password = password
|
||||||
self.name = self.username.split("@")[0]
|
self.name = self.username.split("@")[0]
|
||||||
self.rant_history = []
|
|
||||||
names = {
|
names = {
|
||||||
"no-spam": "anna",
|
"no-spam": "anna",
|
||||||
"no-spam1": "ira",
|
"no-spam1": "ira",
|
||||||
@ -24,14 +24,16 @@ class Bot:
|
|||||||
self.name
|
self.name
|
||||||
)
|
)
|
||||||
self.auth = None
|
self.auth = None
|
||||||
|
|
||||||
self.triggers = [
|
self.triggers = [
|
||||||
"$",
|
"$",
|
||||||
"crypto",
|
"crypto",
|
||||||
"hacker",
|
"hacker",
|
||||||
"recovery",
|
"recovery",
|
||||||
{"regex": r"\([+,(,0-9,),-]{7,}"},
|
{"regex": "\([+,(,0-9,),-]{7,}"},
|
||||||
"money",
|
"money",
|
||||||
]
|
]
|
||||||
|
|
||||||
self.api = Api(username=self.username, password=self.password)
|
self.api = Api(username=self.username, password=self.password)
|
||||||
|
|
||||||
def rsleepii(self):
|
def rsleepii(self):
|
||||||
@ -42,14 +44,14 @@ class Bot:
|
|||||||
self.rsleepii()
|
self.rsleepii()
|
||||||
self.auth = self.api.login()
|
self.auth = self.api.login()
|
||||||
if not self.auth:
|
if not self.auth:
|
||||||
log.error("Authentication for {} failed.".format(self.username))
|
print("Authentication for {} failed.".format(self.username))
|
||||||
raise Exception("Login error")
|
raise Exception("Login error")
|
||||||
log.info("Authentication succesful for {}.".format(self.username))
|
print("Authentication succesful for {}.".format(self.username))
|
||||||
|
|
||||||
def clean_rant_text(self, rant_text):
|
def clean_rant_text(self, rant_text):
|
||||||
return rant_text.replace(" ", "").lower()
|
return rant_text.replace(" ", "").lower()
|
||||||
|
|
||||||
@method_cache
|
# @method_cache
|
||||||
def is_sus_rant(self, rant_id, rant_text):
|
def is_sus_rant(self, rant_id, rant_text):
|
||||||
clean_text = self.clean_rant_text(rant_text)
|
clean_text = self.clean_rant_text(rant_text)
|
||||||
for trigger in self.triggers:
|
for trigger in self.triggers:
|
||||||
@ -57,10 +59,10 @@ class Bot:
|
|||||||
if trigger.get("regex"):
|
if trigger.get("regex"):
|
||||||
regex = trigger["regex"]
|
regex = trigger["regex"]
|
||||||
if re.search(regex, clean_text):
|
if re.search(regex, clean_text):
|
||||||
log.info("Regex trigger {} matched!".format(regex))
|
print("Regex trigger {} matched!".format(regex))
|
||||||
return True
|
return True
|
||||||
elif trigger in clean_text:
|
elif trigger in clean_text:
|
||||||
log.info("Trigger {} matched!".format(trigger))
|
print("Trigger {} matched!".format(trigger))
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -80,7 +82,7 @@ class Bot:
|
|||||||
profile = self.api.get_profile(user_id)
|
profile = self.api.get_profile(user_id)
|
||||||
score = profile["score"]
|
score = profile["score"]
|
||||||
if score < 5:
|
if score < 5:
|
||||||
log.warning("User {} is sus with his score of only {}.".format(username, score))
|
print("User {} is sus with his score of only {}.".format(username, score))
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
@ -93,24 +95,19 @@ class Bot:
|
|||||||
self.rsleepii()
|
self.rsleepii()
|
||||||
rants = self.api.get_rants("recent", 5, 0)
|
rants = self.api.get_rants("recent", 5, 0)
|
||||||
for rant in rants:
|
for rant in rants:
|
||||||
if rant['id'] in self.rant_history:
|
|
||||||
log.debug("{}: Already checked rant {}.".format(self.name,rant['id']))
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
self.rant_history.append(rant['id'])
|
|
||||||
if not self.is_user_sus(rant["user_username"]):
|
if not self.is_user_sus(rant["user_username"]):
|
||||||
log.info("{}: User {} is trusted.".format(self.name, rant["user_username"]))
|
print("User {} is trusted.".format(rant["user_username"]))
|
||||||
continue
|
continue
|
||||||
if not self.is_sus_rant(rant["id"], rant["text"]):
|
if not self.is_sus_rant(rant["id"], rant["text"]):
|
||||||
log.info("{}: Rant by {} is not sus.".format(self.name, rant["user_username"]))
|
print("Rant by {} is not sus.".format(rant["user_username"]))
|
||||||
continue
|
continue
|
||||||
if self.is_flagged_as_sus(rant["id"], rant.get("num_comments")):
|
if self.is_flagged_as_sus(rant["id"], rant.get("num_comments")):
|
||||||
continue
|
continue
|
||||||
log.warning("{}: Rant is not {} flagged as sus yet.".format(self.name,rant["user_username"]))
|
print("Rant is not {} flagged as sus yet.".format(rant["user_username"]))
|
||||||
log.warning("{}: Flagging rant by {} as sus.".format(self.name, rant["user_username"]))
|
print("Flagging rant by {} as sus.".format(rant["user_username"]))
|
||||||
self.mark_as_sus(rant)
|
self.mark_as_sus(rant)
|
||||||
self.down_vote_rant(rant)
|
self.down_vote_rant(rant)
|
||||||
|
|
||||||
def down_vote_rant(self, rant):
|
def down_vote_rant(self, rant):
|
||||||
log.warning("Downvoting rant by {}.".format(rant["user_username"]))
|
print("Downvoting rant by {}.".format(rant["user_username"]))
|
||||||
log.debug(self.api.post_rant_vote(rant["id"], 4))
|
print(self.api.post_rant_vote(rant["id"], 4))
|
||||||
|
@ -3,7 +3,7 @@ from ragnar.bot import Bot
|
|||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
from concurrent.futures import ThreadPoolExecutor as Executor
|
from concurrent.futures import ThreadPoolExecutor as Executor
|
||||||
from ragnar import log
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
parser = argparse.ArgumentParser(description="Process username and password.")
|
parser = argparse.ArgumentParser(description="Process username and password.")
|
||||||
@ -15,7 +15,6 @@ def parse_args():
|
|||||||
|
|
||||||
|
|
||||||
def bot_task(username, password):
|
def bot_task(username, password):
|
||||||
log.info("Created new bot runniner. Username: {}".format(username))
|
|
||||||
time.sleep(random.randint(1, 20))
|
time.sleep(random.randint(1, 20))
|
||||||
bot = Bot(username=username, password=password)
|
bot = Bot(username=username, password=password)
|
||||||
bot.login()
|
bot.login()
|
||||||
@ -33,6 +32,8 @@ def main():
|
|||||||
for x in range(1, 5):
|
for x in range(1, 5):
|
||||||
username = "no-spam{}@molodetz.nl".format(str(x))
|
username = "no-spam{}@molodetz.nl".format(str(x))
|
||||||
password = args.password
|
password = args.password
|
||||||
|
time.sleep(1)
|
||||||
|
print("Starting bot {}.".format(username))
|
||||||
executor.submit(bot_task, username, password)
|
executor.submit(bot_task, username, password)
|
||||||
executor.shutdown(wait=True)
|
executor.shutdown(wait=True)
|
||||||
|
|
||||||
|
BIN
src/ragnar/tests/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
src/ragnar/tests/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
src/ragnar/tests/__pycache__/bot.cpython-310.pyc
Normal file
BIN
src/ragnar/tests/__pycache__/bot.cpython-310.pyc
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user