This commit is contained in:
TED A. ⭕ 2023-11-12 21:51:23 +00:00
commit ca59f2882a
22 changed files with 1120 additions and 0 deletions

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
.idea
.venv
lib64
lib
includes
.env
pyvenv.cfg
__pycache__
bin
logs
venv
.env
.env.dev

18
beloved_logger.py Executable file
View File

@ -0,0 +1,18 @@
import logging
import var
logger = logging.getLogger('logger')
logger.setLevel(logging.INFO)
file_log = logging.FileHandler(f'logs/log{var.log_startup_time}.txt')
file_log.setLevel(logging.INFO)
console_log = logging.StreamHandler()
console_log.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(module)s - %(levelname)s - %(message)s')
file_log.setFormatter(formatter)
console_log.setFormatter(formatter)
logger.addHandler(file_log)
logger.addHandler(console_log)

39
bot.py Executable file
View File

@ -0,0 +1,39 @@
#!/home/opc/automod3/mv/bin/python3.11
import asyncio
from datetime import datetime
import functions.get_newest_count
from functions.send_message import send_message
from functions.scan_posts import scan_posts
from functions.check_chat import check_chat
import var
from beloved_logger import logger
from functions.update_display_name import update_display_name
async def main_loop():
await check_chat()
if var.text_scanning_is_running or var.image_scanning_is_running:
await scan_posts()
async def startup():
var.current_posts_at_start_time = await functions.get_newest_count.get_newest_count()
var.bot_started_at = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
logger.info(f"Started bot with values:\n "
f"bot start time: {var.bot_started_at}\n"
f"logging start time: {var.log_startup_time}\n"
f"site used: {var.site}\n"
f"imagescanning runs: {var.image_scanning_is_running}\n"
f"image scan only scans new users: {var.scan_new_users_only}\n"
f"textscanning runs: {var.text_scanning_is_running}\n"
f"chat number: {var.chat_number}\n"
f"filters list: {var.filters_list}\n")
await update_display_name()
while True:
await main_loop()
if __name__ == "__main__":
asyncio.run(startup())

158
classes/post.py Executable file
View File

@ -0,0 +1,158 @@
import requests
import datetime
import boto3
import var
import classes.rating
from functions.delete_post import delete_post
from beloved_logger import logger
class Post:
post_id = None
post_description = None
post_name = None
image = None
user_id = None
poster_username = None
poster_sign_up_time = None
response = None
date = None
infringing_words = []
ratings = None
post_will_be_deleted = None
deletion_status = None
def __init__(self, post_id):
logger.info(f"Post was made with ID {post_id}")
self.post_id = post_id
self.infringing_words = []
async def get_values(self):
response = requests.get(f"https://{var.site}/posts/{self.post_id}",
headers={'User-Agent': 'automod',
"Accept": "application/json,text/plain, */*"})
if response.status_code == 200:
self.response = response.json()
if response.json()['post']['resource_url']:
self.image = requests.get(self.response['post']['resource_url']).content
self.post_name = self.response['post']["title"]
self.user_id = self.response['post']['profile_id']
self.poster_username = self.response['post']['profile']['username']
self.poster_sign_up_time = self.response['post']['profile']['created_at']
self.date = self.response['post']['created_at']
self.post_description = self.response['post']["description"]
logger.debug(f"Got post {self.post_id} with details {self.response}")
else:
logger.info(f"An error occurred getting post with ID {self.post_id} for reason {response.status_code}")
return False
async def analyse_text(self):
logger.info(f"Getting text analysis of post with post ID {self.post_id}")
if self.post_name:
for word in var.filters_list:
if word.lower() in self.post_name.lower() and word not in self.infringing_words:
logger.info(f"Post {self.post_id}'s name contained infringing word {word}")
self.infringing_words.append(word)
if self.post_description:
for word in var.filters_list:
if word.lower() in self.post_description.lower() and word not in self.infringing_words:
logger.info(f"Post {self.post_id}'s description contained infringing word {word}")
self.infringing_words.append(word)
async def analyse_image(self):
logger.info(f"Getting image analysis of post with post ID {self.post_id}")
if self.image:
logger.info(f"Post {self.post_id} had an image")
await self.detect_image()
if self.ratings:
logger.info(f"Post {self.post_id} has ratings: {await self.get_ratings_string()}")
await self.check_ratings()
if self.post_will_be_deleted:
logger.info(f"Post {self.post_id} will be deleted")
await self.remove_post()
logger.info(f"Post {self.post_id} was deleted")
else:
logger.info(f"Post {self.post_id} will not be deleted")
else:
logger.info(f"Post {self.post_id} did not have any ratings")
else:
logger.info(f"Post {self.post_id} was either a text post or deleted before analysis")
async def detect_image(self):
logger.info(f"Sending data to AWS for post {self.post_id}")
client = boto3.client('rekognition', region_name=var.aws_region)
self.ratings = classes.rating.create_ratings(client.detect_moderation_labels(Image={'Bytes': self.image}))
async def check_ratings(self):
logger.info(f"Checking ratings for post {self.post_id}")
one_rating_was_false = False
for post_rating in self.ratings:
if post_rating.confidence > 90 and post_rating.enabled:
logger.info(f"Post {self.post_id} contained enabled rating more than 90 for {post_rating.name} which "
f"was type of {post_rating.type}")
self.post_will_be_deleted = True
elif not post_rating.enabled:
logger.info(f"Post {self.post_id} had a rating of {post_rating.name} in the "
f"{post_rating.type} category at confidence {post_rating.confidence}"
f"but it was not enabled. Post will not be deleted.")
one_rating_was_false = True
if one_rating_was_false:
self.post_will_be_deleted = False
self.ratings = None
async def remove_post(self):
var.most_recently_deleted_post_id = self.post_id
self.deletion_status = False
if self.post_will_be_deleted:
logger.info(f"Attempting to delete a post {self.post_id} for {await self.get_ratings_string()}")
self.deletion_status = await delete_post(self.post_id)
if self.deletion_status == 200:
logger.info(f"Post {self.post_id} was deleted")
self.deletion_status = True
else:
logger.info(f"An issue occurred deleting {self.post_id} for reason {self.deletion_status}")
self.deletion_status = f"Problem occurred with deletion: {self.deletion_status}"
async def get_readable_date(self):
return datetime.datetime.utcfromtimestamp(self.date).strftime('%Y-%m-%d %H:%M:%S')
async def get_censorship_string(self):
return '", "'.join(self.infringing_words)
async def get_ratings_string(self):
ratings_string = ""
for post_rating in self.ratings:
ratings_string += str(post_rating)
return ratings_string
async def get_as_text_detection_text(self):
string = (f'‼️ *DETECTED:* "{await self.get_censorship_string()}"\n' +
f'🗒️ *TITLE:* "{self.post_name}"\n' +
f"🔗 *URL:* https://{var.site}/admin/posts/{self.post_id}\n" +
f"👤 *POSTER:* https://{var.site}/users/{self.user_id} / @{self.poster_username}\n"
f"The user was also suspended for a day, you will have to reverse this suspension if the moderation action was not warranted..")
return string
async def get_as_image_deletion_text(self):
ratings_string = ""
for post_rating in self.ratings:
ratings_string += str(post_rating)
if self.deletion_status is None:
self.deletion_status = False
string = (f"{ratings_string} \n " +
f'🗒️ *TITLE:* "{self.post_name}"\n' +
f"🔗 *URL:* https://{var.site}/admin/posts/{self.post_id}\n" +
f"👤 *POSTER:* https://{var.site}/users/{self.user_id} / @{self.poster_username}\n" +
f"❌ *DELETED:* {str(self.deletion_status)}"
f"The user was also suspended for a day, if the suspension was not warranted you must undo the suspension")
return string

54
classes/rating.py Executable file
View File

@ -0,0 +1,54 @@
import var
import json
class Rating:
name = ""
confidence = 0
enabled = False
type = ""
def __init__(self, name, confidence):
self.name = name
self.confidence = confidence
self.enabled = self.get_status()
def __str__(self):
return f"‼️ *{self.name}:* {round(self.confidence, 2)}%\n"
def get_status(self):
with open(var.labels_file, 'r') as file:
data = json.load(file)
top_level_true = []
top_level_false = []
lower_level_true = []
lower_level_false = []
for top_level_object, top_level_data in data.items():
if top_level_data.get("status") is True:
top_level_true.append(top_level_object)
elif top_level_data.get("status") is False:
top_level_false.append(top_level_object)
for lower_level_object, lower_level_status in top_level_data.items():
if lower_level_status is True and lower_level_object != "status":
lower_level_true.append(f"{lower_level_object}")
elif lower_level_status is False and lower_level_object != "status":
lower_level_false.append(f"{lower_level_object}")
if self.name in lower_level_true:
self.type = "secondary"
return True
if self.name in top_level_true:
self.type = "primary"
return True
return False
def create_ratings(response):
ratings = []
for label in response['ModerationLabels']:
rating = Rating(label['Name'], label['Confidence'])
ratings.append(rating)
return ratings

9
data/filter-dev.txt Normal file
View File

@ -0,0 +1,9 @@
ted
green
bert
filter
HELLO
DIGLES
wiglett
STUPID

1
data/filter.txt Normal file
View File

@ -0,0 +1 @@
boto3~=1.28.39\nselenium~=4.12.0\npy-cord\nrequests~=2.31.0\npython-dotenv

71
data/labels-dev.json Normal file
View File

@ -0,0 +1,71 @@
{
"Explicit Nudity": {
"status": true,
"Nudity": true,
"Graphic Male Nudity": true,
"Graphic Female Nudity": true,
"Sexual Activity": true,
"Illustrated Explicit Nudity": true,
"Adult Toys": true
},
"Suggestive": {
"status": true,
"Female Swimwear Or Underwear": true,
"Male Swimwear Or Underwear": true,
"Partial Nudity": false,
"Barechested Male": false,
"Revealing Clothes": false,
"Sexual Situations": true
},
"Violence": {
"status": true,
"Graphic Violence Or Gore": true,
"Physical Violence": true,
"Weapon Violence": true,
"Weapons": false,
"Self Injury": true
},
"Visually Disturbing":
{
"status": true,
"Emaciated Bodies": true,
"Corpses": true,
"Hanging": true,
"Air Crash": false,
"Explosions And Blasts": false
},
"Rude Gestures":
{
"status": false,
"Middle Finger": false
},
"Drugs": {
"status": false,
"Drug Products": false,
"Drug Use": false,
"Pills": false,
"Drug Paraphernalia": false
},
"Tobacco": {
"status": false,
"Tobacco Products": false,
"Smoking": false
},
"Alcohol": {
"status": false,
"Drinking": false,
"Alcoholic Beverages": false
},
"Gambling": {
"status": false,
"Gambling": false
},
"Hate Symbols": {
"status": true,
"Nazi Party": true,
"White Supremacy": true,
"Extremist": true
}
}

69
data/labels.json Normal file
View File

@ -0,0 +1,69 @@
{
"Explicit Nudity": {
"status": true,
"Nudity": true,
"Graphic Male Nudity": true,
"Graphic Female Nudity": true,
"Sexual Activity": true,
"Illustrated Explicit Nudity": true,
"Adult Toys": true
},
"Suggestive": {
"status": true,
"Female Swimwear Or Underwear": false,
"Male Swimwear Or Underwear": false,
"Partial Nudity": false,
"Barechested Male": false,
"Revealing Clothes": false,
"Sexual Situations": true
},
"Violence": {
"status": true,
"Graphic Violence Or Gore": true,
"Physical Violence": true,
"Weapon Violence": true,
"Weapons": false,
"Self Injury": true
},
"Visually Disturbing":
{
"status": true,
"Emaciated Bodies": true,
"Corpses": true,
"Hanging": true,
"Air Crash": false,
"Explosions And Blasts": false
},
"Rude Gestures":
{
"status": false,
"Middle Finger": false
},
"Drugs": {
"status": false,
"Drug Products": false,
"Drug Use": false,
"Pills": false,
"Drug Paraphernalia": false
},
"Tobacco": {
"status": false,
"Tobacco Products": false,
"Smoking": false
},
"Alcohol": {
"status": false,
"Drinking": false,
"Alcoholic Beverages": false
},
"Gambling": {
"status": false,
"Gambling": false
},
"Hate Symbols": {
"status": true,
"Nazi Party": true,
"White Supremacy": true,
"Extremist": true
}
}

BIN
data/off.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
data/on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

274
functions/check_chat.py Executable file
View File

@ -0,0 +1,274 @@
import logging
import requests
import json
import functions.get_filters_from_text
import functions.get_newest_count
import var
from functions.send_message import send_message
from functions.roll_token import roll_token
from beloved_logger import logger
from functions.update_display_name import update_display_name
async def get_chat_messages():
await roll_token()
cookies = {
var.cookie_name: var.cookie_value,
'sketchers_united_session': var.session,
'XSRF-TOKEN': var.xsrf_token
}
logger.debug(f"Getting chat messages with cookies {cookies}")
headers = {
'User-Agent': 'automod',
'Accept': 'application/json, */*',
'X-CSRF-TOKEN': var.csrf_token,
'X-XSRF-TOKEN': var.xsrf_token,
}
params = {
'n': '30',
}
get_page_correctly = False
response = ""
while not get_page_correctly:
logging.info(f"Getting messages for {var.chat_number}")
response = requests.get(f'https://{var.site}/chats/{var.chat_number}/messages', params=params,
cookies=cookies,
headers=headers)
if response.status_code == 200:
get_page_correctly = True
else:
logging.error(f"An issue occurred getting the messages "
f"for {var.chat_number} with error {response.status_code}")
return response.json()
async def check_chat():
new_chat = await get_chat_messages()
if len(var.old_chat) == 0:
logging.info("No chat messages were loaded before, so setting the old chat value to the new one")
var.old_chat = new_chat
old_ids = [message["id"] for message in var.old_chat]
newest_messages = [message for message in new_chat if message["id"] not in old_ids]
newest_messages.sort(key=lambda message: message["id"])
newest = newest_messages[:15]
for x in newest:
if "@automod" in x['text'][:8]:
logging.info(f"Somebody mentioned me with {x['text']}")
if "@automod status" in x['text']:
if var.image_scanning_is_running:
if var.scan_new_users_only:
image_scan_status = "Yes, but only for users who have signed up in the past 2 weeks"
else:
image_scan_status = "Yes"
else:
image_scan_status = "No"
if var.text_scanning_is_running:
text_scan_status = "Yes"
else:
text_scan_status = "No"
await send_message("*🕰 Status*\n"
f"🖼️️ *Is image scanning on?:* {image_scan_status}\n"
f"💬 *Is text scanning on?:* {text_scan_status}\n"
f"🏃‍♀️ *Running since:* {var.bot_started_at}\n"
f"❓ Try typing '@automod help'")
elif "@automod i love you" in x['text']:
await send_message(f"💌 I love you too @{x['profile']['username']}...")
elif "@automod ping" in x['text']:
await send_message("☹️ I've been pinged")
elif "@automod help" in x['text']:
await send_message("""⌨️ Prefix commands with @automod e.g. @automod ping
*Informational*
status
i love you
ping
🖼 *Image scanning*
imagescanning on
imagescanning off
imagescanning list
🛑 imagescanning enable _label_
🛑 imagescanning disable _label_
💬 *Text scanning*
textscanning on
textscanning off
textscanning list
textscanning add _word_
textscanning remove _word_
*If I didn't reply to your message, I am likely subject to a rate limit.*
🙂 I still did something though, I just didn't tell you about it.""")
#
# TEXT SCANNING
#
elif "@automod textscanning on" in x['text']:
if var.text_scanning_is_running:
logging.info("Text scanning attempted to be turned on but was already running.")
await send_message("💬️ Text scanning is already running!")
else:
var.current_posts_at_start_time = await functions.get_newest_count.get_newest_count()
var.text_scanning_is_running = True
logging.info("Turned on textscanning")
await send_message(f"💬 Text scanning has been enabled by @{x['profile']['username']}")
await update_display_name()
elif "@automod textscanning off" in x['text']:
if var.image_scanning_is_running is False:
logging.info("Text scanning attempted to be turning off but was already off")
await send_message("🖼️ Text scanning is already off!")
else:
var.text_scanning_is_running = False
logging.info("Turning off textscanning")
await send_message(f"💬 Text scanning has been disabled by @{x['profile']['username']}")
await update_display_name()
elif "@automod textscanning list" in x['text']:
logging.info("Retrieving textscanning list")
with open(var.filter_file, 'r') as file:
lines = [line.strip() for line in file if line.strip()]
lines_joined = ', '.join(lines)
logging.debug(f"Currently filtered words retrieved as {lines_joined}")
await send_message(f"*Current filtered words*\n"
f"{lines_joined}")
elif "@automod textscanning add" in x['text']:
parts = x['text'].split()
if len(parts) == 4:
text_to_add = parts[3]
logging.info(f"Adding new word {text_to_add} to list {var.filters_list}")
if text_to_add.strip() in var.filters_list:
logging.info(f"Word {text_to_add} was already in the list {var.filters_list}")
await send_message(f"‼️ '{text_to_add}' is already in the filtered words list!")
else:
with open(var.filter_file, 'a') as file:
logging.info(f"Writing {text_to_add} to {var.filter_file}")
file.write(text_to_add + '\n')
await send_message(f"🎉 Added {text_to_add} to the filtered words list.")
var.filters_list = await functions.get_filters_from_text.get_filters_from_text(var.filter_file)
else:
logging.info("Adding of a textscanning word was called without any word provided.")
await send_message(f"‼️ You have to provide a word to add to the filtered words list!"
f"\ne.g. '@automod filters add _sketch_' will add "
f"the word 'sketch' to the filtered words list.")
elif "@automod textscanning remove" in x['text']:
parts = x['text'].split()
if len(parts) == 4:
text_to_remove = parts[3]
logging.info(f"Removing word {text_to_remove} from list {var.filters_list}")
if text_to_remove.strip() not in var.filters_list:
logging.info(f"{text_to_remove} was not a pre-existing filtered word in {var.filters_list}")
await send_message(f"⁉️ '{text_to_remove}' was not a pre-existing filtered word.")
else:
with open(var.filter_file, 'r') as file:
lines = file.readlines()
logging.info(f"Taking away {text_to_remove} from {var.filter_file}")
modified_lines = [line.replace(text_to_remove, '') for line in lines]
with open(var.filter_file, 'w') as file:
logging.info(f"Writing the new file back to {var.filter_file}")
file.writelines(modified_lines)
var.filters_list = await functions.get_filters_from_text.get_filters_from_text(var.filter_file)
await send_message(f"🎉 Removed '{text_to_remove}' from the filtered words list.")
else:
logging.info("Removal of a textscanning word was called without any word provided.")
await send_message(f"‼️ You have to provide a word to add to the filtered words list!"
f"\ne.g. '@automod filters remove _sketch_' will remove "
f"the word 'sketch' from the filtered words list.")
#
# IMAGE SCANNING
#
elif "@automod imagescanning on" in x['text']:
if var.image_scanning_is_running:
logging.info("Image scanning attempted to be turned on but was already running.")
await send_message("🖼️ Image scanning is already running!")
else:
var.current_posts_at_start_time = await functions.get_newest_count.get_newest_count()
var.image_scanning_is_running = True
logging.info("Image scanning was turned on")
await send_message(f"🖼️ Image scanning has been enabled by @{x['profile']['username']}")
await update_display_name()
elif "@automod imagescanning off" in x['text']:
if var.image_scanning_is_running is False:
logging.info("Image scanning attempted to be turning off but was already off")
await send_message("🖼️ Image scanning is already off!")
else:
var.image_scanning_is_running = False
logging.info("Image scanning was turned off")
await send_message(f"🖼️ Image scanning has been disabled by @{x['profile']['username']}")
await update_display_name()
elif "@automod imagescanning list" in x['text']:
with open(var.labels_file, 'r') as file:
logging.info(f"Showing list of imagescanning labels from {var.labels_file}")
data = json.load(file)
top_level_true = []
top_level_false = []
lower_level_true = []
lower_level_false = []
for top_level_object, top_level_data in data.items():
if top_level_data.get("status") is True:
top_level_true.append(top_level_object)
elif top_level_data.get("status") is False:
top_level_false.append(top_level_object)
for lower_level_object, lower_level_status in top_level_data.items():
if lower_level_status is True and lower_level_object != "status":
lower_level_true.append(f"{lower_level_object}")
elif lower_level_status is False and lower_level_object != "status":
lower_level_false.append(f"{lower_level_object}")
logging.debug(f"*❌ Disabled:* \n"
f" _Top level:_ {', '.join(top_level_false)}, \n"
f" _Lower level:_ {', '.join(lower_level_false)}\n"
f"*✅ Enabled:* \n"
f" _Top level:_ {', '.join(top_level_true)}, \n"
f" _Lower level:_ {', '.join(lower_level_true)}")
await send_message(f"*❌ Disabled:* \n"
f" _Top level:_ {', '.join(top_level_false)}, \n"
f" _Lower level:_ {', '.join(lower_level_false)}\n"
f"*✅ Enabled:* \n"
f" _Top level:_ {', '.join(top_level_true)}, \n"
f" _Lower level:_ {', '.join(lower_level_true)}")
elif "@automod imagescanning enablefilter" in x['text']:
await send_message("Not yet implemented")
elif "@automod imagescanning disablefilter" in x['text']:
await send_message("Not yet implemented")
else:
await send_message("Invalid command given")
var.old_chat = new_chat

28
functions/delete_post.py Executable file
View File

@ -0,0 +1,28 @@
import var
import requests
from functions.roll_token import roll_token
from beloved_logger import logger
async def delete_post(post_id):
await roll_token()
cookies = {
var.cookie_name: var.cookie_value,
'sketchers_united_session': var.session,
'XSRF-TOKEN': var.xsrf_token
}
logger.debug(f"Deleting post {post_id} with cookies {cookies}")
headers = {
'User-Agent': 'automod',
'Accept': 'application/json, */*',
'X-CSRF-TOKEN': var.csrf_token,
'X-XSRF-TOKEN': var.xsrf_token
}
response = requests.delete(f'https://{var.site}/admin/posts/{post_id}', cookies=cookies,
headers=headers)
logger.info(f"Deleting post {post_id} concluded with response {response.status_code}")
return response.status_code

View File

@ -0,0 +1,8 @@
async def get_filters_from_text(filter_file):
lines_list = []
with open(filter_file, 'r') as file:
for line in file:
stripped_line = line.strip()
if stripped_line:
lines_list.append(stripped_line)
return lines_list

26
functions/get_newest_count.py Executable file
View File

@ -0,0 +1,26 @@
import requests
import var
from beloved_logger import logger
async def get_newest_count():
# logger.info("Getting newest post count")
while True:
headers = {
"Accept": "application/json, */*"
}
response = requests.get(f"https://{var.site}/json/posts", headers=headers)
if response.status_code == 200:
if response.json():
if response.json()['data']['posts'][0]['id']:
# logger.info(f"Newest post count of {response.json()['data']['posts'][0]['id']}")
return response.json()['data']['posts'][0]['id']
else:
while response.status_code != 200:
logger.info(f"Error getting newest post count with reason {response.status_code}, retrying")
response = requests.get(f"https://{var.site}/json/posts", headers=headers)
# logger.info(f"Newest post count of {response.json()['data']['posts'][0]['id']}")
return response.json()['data']['posts'][0]['id']

45
functions/roll_token.py Executable file
View File

@ -0,0 +1,45 @@
import var
from bs4 import BeautifulSoup
import requests
from beloved_logger import logger
async def roll_token():
logger.debug("Rolled a new token set")
cookies = {
var.cookie_name: var.cookie_value,
'sketchers_united_session': var.session
}
logger.debug(f"Using cookies {cookies}")
headers = {
'User-Agent': 'automod',
}
get_page_correctly = False
response = None
while not get_page_correctly:
try:
response = requests.get(f'https://{var.site}', cookies=cookies, headers=headers)
if response.status_code == 200:
var.session = response.cookies["sketchers_united_session"]
logger.debug(f"Got session of {var.session}")
var.xsrf_token = response.cookies["XSRF-TOKEN"]
logger.debug(f"Got xsrf of {var.xsrf_token}")
get_page_correctly = True
else:
logger.info("An error occurred trying to get the page when rolling tokens, retrying")
except Exception as e:
logger.info(f"Exception in rolling token {e}")
soup = BeautifulSoup(response.text, 'html.parser')
var.csrf_token = soup.find('meta', {'name': 'csrf-token'}).get('content')
logger.debug(f"Got csrf of {var.csrf_token}")

67
functions/scan_posts.py Executable file
View File

@ -0,0 +1,67 @@
import functions.get_newest_count
import var
from classes import post
from datetime import datetime, timedelta
from functions.send_message import send_message
from functions.suspend_user import suspend_user_for_post
from beloved_logger import logger
async def scan_posts():
logger.debug("Start post scan")
if var.most_recently_deleted_post_id > var.current_posts_at_start_time:
var.current_posts_at_start_time = var.most_recently_deleted_post_id
current_posts_now = var.current_posts_at_start_time
new_posts = await functions.get_newest_count.get_newest_count()
if new_posts != 0:
current_posts_now = new_posts
logger.debug(f"posts now: {current_posts_now}")
logger.debug(f"posts previous: {var.current_posts_at_start_time}")
if current_posts_now != var.current_posts_at_start_time:
for i in range(int(var.current_posts_at_start_time) + 1, int(current_posts_now) + 1):
post_to_analyse = post.Post(i)
await post_to_analyse.get_values()
if post_to_analyse.response:
if var.text_scanning_is_running:
await post_to_analyse.analyse_text()
if len(post_to_analyse.infringing_words) > 0:
await send_message(await post_to_analyse.get_as_text_detection_text())
if var.image_scanning_is_running:
if var.scan_new_users_only:
cut_off_date = datetime.now() - timedelta(weeks=2)
current_date = datetime.strptime(post_to_analyse.poster_sign_up_time, "%Y-%m-%d %H:%M:%S")
logger.debug(f"User signed up at {current_date}, we are checking for "
f"users that signed up after {cut_off_date}")
if current_date < cut_off_date:
logger.info(f"User was signed up before {cut_off_date}, so checks won't occur")
var.most_recently_deleted_post_id = i
return
else:
logger.info(f"User was signed up after {cut_off_date}, so checks will occur")
logger.info(f"Analysing {i} for image")
await post_to_analyse.analyse_image()
if post_to_analyse.ratings:
if post_to_analyse.deletion_status:
message_string = f"""👋 Hello, I am an automatic moderation bot for Sketchers United.
Your most recent post titled '{post_to_analyse.post_name}' was removed automatically as it was detected to have inappropriate content.
As a result, your account was suspended for one day.
*If this was a mistake don't worry, all deletions and suspensions are subject to human review and corrections to automatic moderation actions (removal of suspensions and restoration of deleted posts) will be made as soon as possible if they are not warranted.*
If there any questions, problems, or concerns please contact @username
"""
await suspend_user_for_post(post_to_analyse)
await send_message(await post_to_analyse.get_as_image_deletion_text())
await send_message(message_string, post_to_analyse.poster_username)
var.current_posts_at_start_time = current_posts_now

65
functions/send_message.py Executable file
View File

@ -0,0 +1,65 @@
import requests
from functions.roll_token import roll_token
import var
from beloved_logger import logger
async def send_message(text, chat_id=None):
if chat_id is None:
logger.debug(f"No chat id was given, using {var.chat_number}")
chat_id = int(var.chat_number)
logger.info(f"Sending message to {chat_id}")
logger.debug(f"Message contents: {text}")
await roll_token()
if type(chat_id) != int:
cookies = {
var.cookie_name: var.cookie_value,
'sketchers_united_session': var.session
}
headers = {
'User-Agent': 'automod',
'Accept': 'application/json, text/plain, */*',
}
response = requests.get(f'https://{var.site}/chats/@{chat_id}',
cookies=cookies,
headers=headers)
if response.status_code != 200 and response.status_code != 201:
logger.error(f"There was an issue with getting an id from the {chat_id} "
f"with error {response.status_code}")
return False
chat_id = response.json()['id']
cookies = {
var.cookie_name: var.cookie_value,
'sketchers_united_session': var.session,
'XSRF-TOKEN': var.xsrf_token
}
headers = {
'User-Agent': 'automod',
'Accept': 'application/json, */*',
'X-CSRF-TOKEN': var.csrf_token,
'X-XSRF-TOKEN': var.xsrf_token,
'Content-Type': 'multipart/form-data; boundary=---------------------------9051447283201882072943098316'
}
data = (f'-----------------------------9051447283201882072943098316'
f'\r\nContent-Disposition: form-data; '
f'name="text"\r\n\r\n{text}'
f'\r\n-----------------------------9051447283201882072943098316--\r\n').encode()
response = requests.post(f'https://{var.site}/chats/{chat_id}/messages', cookies=cookies,
headers=headers,
data=data)
logger.info(f"Sending message concluded with response {response.status_code}")
if response.status_code != 200 and response.status_code != 201:
logger.error(f"There was an issue with sending a message to the {chat_id} "
f"with error {response.status_code}")
return False

46
functions/suspend_user.py Executable file
View File

@ -0,0 +1,46 @@
import logging
import var
import requests
from functions.roll_token import roll_token
from beloved_logger import logger
async def suspend_user_for_post(post):
await roll_token()
cookies = {
var.cookie_name: var.cookie_value,
'sketchers_united_session': var.session,
'XSRF-TOKEN': var.xsrf_token
}
logging.debug(f"Suspending user with cookies {cookies}")
headers = {
'User-Agent': 'automod',
'Content-Type': 'multipart/form-data; boundary=---------------------------33943441651739618973055574259',
}
data = ('-----------------------------33943441651739618973055574259\r\n'
'Content-Disposition: form-data; name="_method"\r\n'
'\r\nPOST\r\n'
'-----------------------------33943441651739618973055574259\r\n'
'Content-Disposition: form-data; name="_token"\r\n'
f'\r\n{var.csrf_token}\r\n'
'-----------------------------33943441651739618973055574259'
'\r\nContent-Disposition: form-data; name="blocked_id"\r\n'
f'\r\n{post.user_id}\r\n'
f'-----------------------------33943441651739618973055574259\r\n'
'Content-Disposition: form-data; name="days"\r\n'
'\r\n1\r\n'
'-----------------------------33943441651739618973055574259\r\n'
'Content-Disposition: form-data; name="reason"\r\n'
'\r\nother\r\n'
'-----------------------------33943441651739618973055574259'
'\r\nContent-Disposition: form-data; name="note"\r\n'
f'\r\n{await post.get_ratings_string()}\r\n\r\n at https://{var.site}/admin/posts/{post.post_id}\r\n'
f'-----------------------------33943441651739618973055574259--\r\n').encode()
response = requests.post(f'https://{var.site}/admin/blocks', cookies=cookies, headers=headers, data=data)
logger.info(f"Suspending user {post.user_id} occurred with: {response.status_code}")

View File

@ -0,0 +1,89 @@
import requests
import var
from functions.roll_token import roll_token
from beloved_logger import logger
async def update_display_name():
await roll_token()
cookies = {
var.cookie_name: var.cookie_value,
'sketchers_united_session': var.session,
'XSRF-TOKEN': var.xsrf_token
}
logger.debug(f"Updating display name with cookies {cookies}")
headers = {
'User-Agent': 'automod',
'Content-Type': 'multipart/form-data; boundary=---------------------------39838723211327797103443672495',
}
value = ""
if var.text_scanning_is_running:
value += "💬"
if var.image_scanning_is_running:
value += "🖼️"
new_name = "ms alice a. automod " + value
description = (f"automatic moderation bot run by @username, send me a message if there\'s a problem"
f"\r\nmessages sent to this account will not be read\r\n")
data = (f'-----------------------------39838723211327797103443672495'
f'\r\nContent-Disposition: form-data; '
f'name="_token"\r\n'
f'\r\n{var.csrf_token}'
f'\r\n-----------------------------39838723211327797103443672495\r'
f'\nContent-Disposition: form-data; name="profile_image"; filename=""'
f'\r\nContent-Type: '
f'application/octet-stream\r\n'
f'\r\n-----------------------------39838723211327797103443672495'
f'\r\nContent'
f'-Disposition: form-data; name="backdrop_image"; filename=""'
f'\r\nContent-Type: '
f'application/octet-stream\r\n'
f'\r\n-----------------------------39838723211327797103443672495'
f'\r\nContent'
f'-Disposition: form-data; name="name"\r\n'
f'\r\n{new_name}'
f'\r\n-----------------------------39838723211327797103443672495'
f'\r\nContent-Disposition: form-data; '
f'name="username"\r\n'
f'\r\nakemi'
f'\r\n-----------------------------39838723211327797103443672495'
f'\r\nContent'
f'-Disposition: form-data; name="description"\r\n'
f'\r\n{description}'
f'-----------------------------39838723211327797103443672495'
f'\r\nContent'
f'-Disposition: form-data; '
f'name="link_amino"\r\n'
f'\r\n\r\n-----------------------------39838723211327797103443672495'
f'\r\nContent'
f'-Disposition: form-data; '
f'name="link_deviantart"\r\n'
f'\r\n\r\n-----------------------------39838723211327797103443672495'
f'\r\nContent'
f'-Disposition: form-data; '
f'name="link_facebook"\r\n'
f'\r\n\r\n-----------------------------39838723211327797103443672495'
f'\r\nContent'
f'-Disposition: form-data; '
f'name="link_instagram"\r\n'
f'\r\n\r\n-----------------------------39838723211327797103443672495'
f'\r\nContent'
f'-Disposition: form-data; '
f'name="link_twitter"\r\n'
f'\r\n\r\n-----------------------------39838723211327797103443672495'
f'\r\nContent'
f'-Disposition: form-data; '
f'name="link_youtube"\r\n'
f'\r\n\r\n-----------------------------39838723211327797103443672495--'
f'\r\n').encode()
response = requests.post(f'https://{var.site}/users/{var.bot_user_number}/update',
cookies=cookies,
headers=headers,
data=data)
logger.info(f"Updating display name concluded with response {response.status_code}")

5
requirements.txt Executable file
View File

@ -0,0 +1,5 @@
boto3~=1.28.39
requests~=2.31.0
python-dotenv~=1.0.0
websockets~=11.0.3
beautifulsoup4~=4.12.2

35
var.py Executable file
View File

@ -0,0 +1,35 @@
import asyncio
import os
from dotenv import load_dotenv
from datetime import datetime
from functions.get_filters_from_text import get_filters_from_text
load_dotenv('data/.env')
site = os.getenv("SITE")
cookie_name = os.getenv("COOKIE_NAME")
cookie_value = os.getenv("COOKIE_VALUE")
chat_number = os.getenv("CHAT_NUMBER")
filter_file = os.getenv("FILTER_FILE")
websocket_url = os.getenv("WEBSOCKET_URL")
labels_file = os.getenv("LABELS_FILE")
session = os.getenv("STARTUP_SESSION_VALUE")
bot_user_number = os.getenv("USER_NUMBER")
aws_region = os.getenv("AWS_REGION")
old_chat = {}
scan_new_users_only = True
image_scanning_is_running = True
text_scanning_is_running = False
most_recently_deleted_post_id = 0
current_posts_at_start_time = None
bot_started_at = None
posts_removed = None
scanning_since = None
user_timeout = None
xsrf_token = None
csrf_token = None
filters_list = asyncio.run(get_filters_from_text(filter_file))
log_startup_time = datetime.now().strftime("%Y%m%d%H%M%S")