Compare commits
5 Commits
9b3d96fb7e
...
2893fd4bbc
Author | SHA1 | Date |
---|---|---|
HeyMoon | 2893fd4bbc | |
HeyMoon | 5c868d7aa7 | |
HeyMoon | 5362f8374c | |
HeyMoon | 3ba8fb6d50 | |
HeyMoon | e26e7056cd |
|
@ -1,3 +1,19 @@
|
|||
__pycache__/image_utils.cpython-39.pyc
|
||||
emoji_cache/
|
||||
*.ttf
|
||||
dry/
|
||||
*.ttf
|
||||
*.pyc
|
||||
__pycache__/
|
||||
rdrama_auth_token
|
||||
twitter_access_token
|
||||
twitter_access_token_secret
|
||||
twitter_api_key
|
||||
twitter_api_secret
|
||||
twitter_bearer_token
|
||||
twitter_client_id
|
||||
twitter_client_secret
|
||||
*.db
|
||||
*.db-journal
|
||||
log
|
||||
*.json
|
||||
|
||||
|
|
23
BotModels.py
23
BotModels.py
|
@ -139,5 +139,24 @@ class CommentString(Base):
|
|||
'''
|
||||
Mostly for keeping track of how long it's been since the last run, so we can run things without worrying about set number of pages
|
||||
'''
|
||||
class ScriptCalls(Base):
|
||||
pass
|
||||
class ScriptCall(Base):
|
||||
__tablename__ = "script_call"
|
||||
|
||||
id = Column(Integer, primary_key = True)
|
||||
time = Column(DateTime)
|
||||
|
||||
def register(session : Session):
|
||||
sc = ScriptCall(time = datetime.now())
|
||||
session.add(sc)
|
||||
|
||||
def get_time_of_last_run(session) -> datetime:
|
||||
stmt = sqlalchemy.select(ScriptCall).order_by(ScriptCall.time.desc()).limit(1)
|
||||
sc = session.execute(stmt).scalar_one_or_none()
|
||||
|
||||
if (sc == None):
|
||||
return datetime.now()
|
||||
else:
|
||||
return sc.time
|
||||
|
||||
|
||||
|
538
automeme.py
538
automeme.py
|
@ -1,42 +1,105 @@
|
|||
import base64
|
||||
import io
|
||||
import json
|
||||
from random import random
|
||||
from random import choice, choices, random
|
||||
import re
|
||||
import traceback
|
||||
from typing import Callable, TypeVar
|
||||
import meme_generator
|
||||
from meme_generator import WebcomicPanel, OneCharacterWebcomicPanel, TwoCharacterWebcomicPanel, TitleCardWebcomicPanel, add_watermark, create_webcomic
|
||||
from meme_generator import AnimatedImage, WebcomicPanel, OneCharacterWebcomicPanel, TwoCharacterWebcomicPanel, TitleCardWebcomicPanel, add_watermark, create_webcomic
|
||||
from RDramaAPIInterface import RDramaAPIInterface
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from os.path import exists, join, realpath, split
|
||||
from threading import Timer
|
||||
from BotModels import Base, Comment, CommentString, Post, User
|
||||
from BotModels import Base, Comment, CommentString, Post, ScriptCall, User
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import Session
|
||||
import os
|
||||
from markdown import markdown
|
||||
from bs4 import BeautifulSoup
|
||||
from utils import get_real_filename
|
||||
from pympler import tracker, asizeof, classtracker
|
||||
|
||||
TEST_MODE = True
|
||||
ANTISPAM_MESSAGES = [
|
||||
'i have a meme for u :marseyshy:',
|
||||
'anon -- can you suck my penis please?',
|
||||
'uwu i love memes',
|
||||
'~ oh... dramatardchan -- i wish ud stop talking about trains',
|
||||
'hey dramatardchan, its ok if u have autism',
|
||||
'its okay silly ~ i know u are retarded',
|
||||
'on rdrama someone told me to sneed, what does that mean?',
|
||||
'what if no-one likes my shitposts :marseycry:',
|
||||
'a ha ha ha, that\'s very funny dramatardchan',
|
||||
'im going to a party ~ no you dont have to come ~ love u',
|
||||
'oh dramatardchan, your such a special boy',
|
||||
'omg hes talking about "trannys" again, im so wet down there',
|
||||
'idk what that means but i still love u',
|
||||
'i love u but pls dont send me penis pictures, its kinda gross >.<',
|
||||
'aha thats very funny, thank u',
|
||||
'hey dramatardchan... i was thinking... maybe u could take a shower... if u want',
|
||||
'i love u becuz ur so sweet, even if you are kind of a pushover, dramatardchan',
|
||||
'maybe u should delete ur account - not trying to be mean tho',
|
||||
'look at this cute penis picture i found on twitter... no, its ok, u dont have to show me urs',
|
||||
'FUCK YOU. I HAVE HAD IT UP TO HERE WITH YOUR FUCKING DRAMA BULLSHIT. FUCK. OFF.',
|
||||
'idk what u mean by mommy milkers 😭 im not ur mommy pls stop saying that',
|
||||
'goldstein sama is so cute ~ what a cute nose... urs is fine to tho'
|
||||
]
|
||||
DARRELL_QUOTES = [
|
||||
":#marseyobjection: \nFor the record, your honor, I do not consent to being called that name, nor do I know anyone by that name",
|
||||
":#brooksannoyed: \nI see what you're doing. You must think you're slick.",
|
||||
":#marseyobjection: \nThe jury deserves to know, your honor.",
|
||||
":#marseyjurisdiction: \nCan we please address :marseyjurisdiction:?",
|
||||
":#marseygrouns:",
|
||||
":#marseygrouns:\n:marseygrouns: for the substain?",
|
||||
":#marseygrouns:\nWho would you say is the plantiff in this matter?",
|
||||
":#marseygrouns:\nWould it be fair to say that the State of Wisconsin is not a living, breathing person?",
|
||||
":#marseygrouns:\nThe defense calls the State of Wisconsin.",
|
||||
":#brooksannoyed:\nHow can you even call yourself a judge?",
|
||||
":#brooksannoyed:\n\*takes off shirt for some reason\*",
|
||||
":#brooksannoyed:\nMind boggling. Ming boggling.",
|
||||
":#brooksannoyed:\nSHE SAID SHE WAS 18!",
|
||||
":#marseyrelevancy:\nuhh what's the :marseyrelevancy:?",
|
||||
":#marseyrelevancy:\n[\*talks for fifty minutes straight\*](https://rdrama.net/post/113381/marseygrounsmarseyjudge-darrel-brooks-entire-50minute-long)",
|
||||
":#marseyrelevancy:\nI see what you're doing. Trying to be slick.",
|
||||
":#brooksannoyed:\nYour honor, that's a load of crap.",
|
||||
":#marseytakit:\nIs that lawfully law?",
|
||||
":#marseytakit:\nIs that a :marseytakit:?",
|
||||
":#brooksannoyed:\nI AM A GROWN MAN WITH GROWN KIDS AINT NOBODY TELL ME WHEN TO TALK",
|
||||
":#marseybiast:\nIs this a common law court or an admiralty court?",
|
||||
":#marseybiast:\nSeems to me like a lot of your answers are coached.",
|
||||
":#marseyjurisdiction:\nWe STILL have yet to discuss :#marseyjurisdiction:.",
|
||||
":#marseyjurisdiction:\nWhat da bid'ness?",
|
||||
":#marseyjurisdiction:\nI need a signed affidavit of your oath of office."
|
||||
]
|
||||
DARRELL_KEYWORDS = [
|
||||
"brooks",
|
||||
"darrell"
|
||||
]
|
||||
DARRELL_DISCLAIMER = "_I am not Darrell Brooks, I am a third party intervener, here on behalf of my client_"
|
||||
LLM_KEYWORDS = [
|
||||
"llm",
|
||||
"landlordmessiah",
|
||||
"landlord messiah"
|
||||
]
|
||||
|
||||
TEST_MODE = False
|
||||
DRY_MODE = False
|
||||
TEST_AUTH_TOKEN = "ED3eURMKP9FKBFbi-JUxo8MPGWkEihuyIlAUGtVL7xwx0NEy4Nf6J_mxWYTPgAQx1iy1X91hx7PPHyEBS79hvKVIy5DMEzOyAe9PAc5pmqSJlLGq_-ROewMwFzGrqer4"
|
||||
BETA_MODE = False
|
||||
TEST_AUTH_TOKEN = "2PR7FAqx0UVM71HayM33p1OTy_zFGOK1rJlCNi1qzvFJqQGK6NOm7I-mSfYjZgs02FU_LqLTPqDa8KN-LUiGSJ1idFfE5jKnG3DUk9rQjn_ZrAtG_VU9GK5F_EGmixg1"
|
||||
MINUTES_BEFORE_FORCED_SHUTDOWN = 10
|
||||
DB_FILENAME = "automeme_database.db"
|
||||
PAGES_TO_SCAN = 5
|
||||
MAX_PAGES_TO_SCAN = 10
|
||||
AUTOMEME_ID = 13427
|
||||
ALLOWED_COMMENTS_PER_POST = 20
|
||||
ALLOWED_COMMENTS_PER_USER_PER_DAY = 20
|
||||
MESSAGE = "i have a meme for u :marseyshy: \nPROTIP: I reply randomly. Put !!meme in your message to make sure I reply"
|
||||
|
||||
SOY_VS_CHAD_TRIGGER_CHANGE = 1.0
|
||||
MODERN_MEME_WITH_MARSEY_TRIGGER_CHANGE = 1.0 #0.1
|
||||
MODERN_MEME_WITH_IMAGE_TRIGGER_CHANGE = 1.0
|
||||
CLASSIC_MEME_WITH_MARSEY_TRIGGER_CHANGE = 1.0 #0.5
|
||||
CLASSIC_MEME_WITH_IMAGE_TRIGGER_CHANGE = 1.0
|
||||
WEBCOMIC_TRIGGER_CHANCE = 1.0
|
||||
SOY_VS_CHAD_TRIGGER_CHANGE = 0.01
|
||||
MODERN_MEME_WITH_MARSEY_TRIGGER_CHANGE = 0.001
|
||||
MODERN_MEME_WITH_IMAGE_TRIGGER_CHANGE = 0.005
|
||||
CLASSIC_MEME_WITH_MARSEY_TRIGGER_CHANGE = 0.002
|
||||
CLASSIC_MEME_WITH_IMAGE_TRIGGER_CHANGE = 0.001
|
||||
WEBCOMIC_TRIGGER_CHANCE = 0.01
|
||||
DARRELL_CHANCE = 1.0
|
||||
|
||||
FREE_POSTS = [6]
|
||||
FREE_POSTS = [6, 97416, 98286]
|
||||
RESTRICTED_POSTS = [5, 16583, 75878, 35835]
|
||||
|
||||
EMOJI_REGEX = r":[#!a-zA-Z0-9]*:"
|
||||
|
@ -87,7 +150,7 @@ class TextLine:
|
|||
|
||||
@property
|
||||
def is_argument_line(self):
|
||||
return len(self.emojis) == 2 and (len(self.captions) == 2 or len(self.captions) == 1) and isinstance(self.line[0], Emoji) and isinstance(self.line[2], Emoji)
|
||||
return len(self.emojis) == 2 and ((len(self.captions) == 0) or (len(self.captions) == 2 or len(self.captions) == 1) and isinstance(self.line[0], Emoji) and isinstance(self.line[2], Emoji))
|
||||
|
||||
@property
|
||||
def is_pure_text_line(self):
|
||||
|
@ -101,6 +164,12 @@ class TextLine:
|
|||
def is_image_line(self):
|
||||
return len(self.images) == 1 and len(self.captions) == 0
|
||||
|
||||
def __str__(self):
|
||||
return str(self.line)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return str(self.line)
|
||||
|
||||
class TextElement():
|
||||
pass
|
||||
|
||||
|
@ -133,7 +202,7 @@ class Emoji(TextElement):
|
|||
def __repr__(self) -> str:
|
||||
return f"Emoji({self.emoji}, big={self.big})"
|
||||
|
||||
def get_text_only(text_elements : list[TextElement]) -> str:
|
||||
def get_text_only(text_elements : 'list[TextElement]') -> str:
|
||||
text = [i.text for i in text_elements if isinstance(i, Text)]
|
||||
return " ".join(text)
|
||||
|
||||
|
@ -187,34 +256,56 @@ def strip_markdown(markdown_string):
|
|||
text = re.sub("@(.*?)\s", "", text)
|
||||
text = re.sub("!slots.*?\s", "", text)
|
||||
|
||||
text = re.sub("(?i)detrans lives matter", "", text)
|
||||
text = re.sub("(?i)trans lives matter", "", text)
|
||||
|
||||
return text
|
||||
return text.strip()
|
||||
|
||||
def remove_duplicates(list):
|
||||
return [json.loads(j) for j in set([json.dumps(i) for i in list])]
|
||||
|
||||
def get_eligible_comments(rdrama : RDramaAPIInterface, session : Session):
|
||||
comments = rdrama.get_comments(number_of_pages=PAGES_TO_SCAN)['data']
|
||||
comments = [comment for comment in comments if not comment['is_bot']] #No bots
|
||||
comments = [comment for comment in comments if comment['author'] == '👻' or not comment['author']['id'] == AUTOMEME_ID] #Don't reply to self
|
||||
comments = [comment for comment in comments if Post.get_number_of_replies(comment['post_id'], session) < ALLOWED_COMMENTS_PER_POST] #Don't spam posts
|
||||
comments = [comment for comment in comments if User.get_number_of_comments(0 if comment['author'] == '👻' else comment['author']['id'], session) < ALLOWED_COMMENTS_PER_USER_PER_DAY] #Don't spam users
|
||||
comments = [comment for comment in comments if Comment.get_comment(comment['id'], session) is None] #Double check that we haven't replied to the comment
|
||||
comments = remove_duplicates(comments) #Remove the duplicates
|
||||
|
||||
last_time = ScriptCall.get_time_of_last_run(session)
|
||||
current_time = int(datetime.now().timestamp())
|
||||
|
||||
begin = datetime.now()
|
||||
comments = comment_chunk(last_time, rdrama) #rdrama.get_comments(number_of_pages=3, upper_bound=current_time, lower_bound=last_time, sort="old")['data']
|
||||
end = datetime.now()
|
||||
print(end-begin)
|
||||
#comments = [comment for comment in comments if comment['created_utc'] > last_time]
|
||||
for comment in comments:
|
||||
|
||||
user_id = 0 if comment['author'] == '👻' else comment['author']['id']
|
||||
comment_id = comment['id']
|
||||
text = comment['body']
|
||||
time = datetime.fromtimestamp(comment['created_utc'])
|
||||
|
||||
CommentString.add_comment(comment_id, user_id, time, text, session)
|
||||
|
||||
print([comment['id'] for comment in comments])
|
||||
comments = [comment for comment in comments if not comment['is_bot']] #No bots
|
||||
print([comment['id'] for comment in comments])
|
||||
comments = [comment for comment in comments if comment['author'] == '👻' or not comment['author']['id'] == AUTOMEME_ID] #Don't reply to self
|
||||
print([comment['id'] for comment in comments])
|
||||
comments = [comment for comment in comments if Post.get_number_of_replies(comment['post_id'], session) < ALLOWED_COMMENTS_PER_POST] #Don't spam posts
|
||||
print([comment['id'] for comment in comments])
|
||||
comments = [comment for comment in comments if User.get_number_of_comments(0 if comment['author'] == '👻' else comment['author']['id'], session) < ALLOWED_COMMENTS_PER_USER_PER_DAY] #Don't spam users
|
||||
print([comment['id'] for comment in comments])
|
||||
comments = [comment for comment in comments if Comment.get_comment(comment['id'], session) is None] #Double check that we haven't replied to the comment
|
||||
|
||||
if BETA_MODE:
|
||||
comments = [comment for comment in comments if comment['post_id'] in FREE_POSTS]
|
||||
print([comment['id'] for comment in comments])
|
||||
|
||||
comments = remove_duplicates(comments) #Remove the duplicates
|
||||
|
||||
print([comment['id'] for comment in comments])
|
||||
ScriptCall.register(session)
|
||||
session.commit()
|
||||
return comments
|
||||
|
||||
T = TypeVar('T')
|
||||
def lambda_count(list : list[T], predicate : 'Callable[[T], bool]' ):
|
||||
def lambda_count(list : 'list[T]', predicate : 'Callable[[T], bool]' ):
|
||||
return sum(1 for i in list if predicate(i))
|
||||
|
||||
def get_full_rdrama_image_url(partial_url) -> str:
|
||||
|
@ -228,12 +319,16 @@ def extract_directives(string : str):
|
|||
list = [i.lower()[2:] for i in list]
|
||||
return list
|
||||
|
||||
def create_comment_message(chud: bool, pizza: bool, bird : bool, marsey : bool):
|
||||
def create_comment_message(chud: bool, pizza: bool, bird : bool, marsey : bool, mention : str = None):
|
||||
if (marsey):
|
||||
return ":marseyshy:"
|
||||
|
||||
message = MESSAGE
|
||||
|
||||
|
||||
message = choice(ANTISPAM_MESSAGES) +"\n"
|
||||
if (mention != None):
|
||||
message += f"@{mention}\n"
|
||||
|
||||
if (chud):
|
||||
message += "\ntrans lives matter"
|
||||
if (pizza):
|
||||
|
@ -241,6 +336,200 @@ def create_comment_message(chud: bool, pizza: bool, bird : bool, marsey : bool):
|
|||
|
||||
return message
|
||||
|
||||
def handle_comment(comment : dict, rdrama : RDramaAPIInterface, session : Session, chud: bool, pizza: bool, bird : bool, is_marsey : bool, starting_directives : 'list[str]'= [], mention : str = None):
|
||||
try:
|
||||
begin = datetime.now()
|
||||
under_post_limit = Post.get_number_of_replies(comment['post_id'], session) < ALLOWED_COMMENTS_PER_POST
|
||||
under_user_limit = User.get_number_of_comments(0 if comment['author'] == '👻' else comment['author']['id'], session) < ALLOWED_COMMENTS_PER_USER_PER_DAY
|
||||
has_not_replied_to_comment = Comment.get_comment(comment['id'], session) is None
|
||||
is_not_restricted_post = comment['post_id'] not in RESTRICTED_POSTS
|
||||
if (not (under_post_limit and under_user_limit and (has_not_replied_to_comment or len(starting_directives) != 0) and is_not_restricted_post)):
|
||||
return
|
||||
|
||||
comment_text = comment['body']
|
||||
directives = extract_directives(comment_text) if len(starting_directives) == 0 else starting_directives
|
||||
cleaned_comment_text = strip_markdown(comment_text)
|
||||
|
||||
if (cleaned_comment_text == "" and len(directives) > 0):
|
||||
parent_id = comment['parent_comment_id']
|
||||
username = None if comment['author'] == '👻' else comment['author']['username']
|
||||
parent = rdrama.get_comment(parent_id)
|
||||
handle_comment(parent, rdrama, session, chud, pizza, bird, is_marsey, starting_directives=directives, mention=username)
|
||||
return
|
||||
|
||||
if ("meme" in directives or comment['post_id'] in FREE_POSTS):
|
||||
random_float = 0.0
|
||||
else:
|
||||
random_float = random()
|
||||
|
||||
comment_lines = cleaned_comment_text.split("\n")
|
||||
comment_lines = [comment_line for comment_line in comment_lines if comment_line != ""]
|
||||
element_lines = [TextLine(line) for line in comment_lines]
|
||||
|
||||
argument_lines_count, dialog_lines_count, text_lines_count, big_marsey_lines_count = 0,0,0,0
|
||||
|
||||
dialog_lines = list(filter(lambda a : a.is_dialogue_line, element_lines))
|
||||
argument_lines = list(filter(lambda a : a.is_argument_line, element_lines))
|
||||
pure_text_lines = list(filter(lambda a : a.is_pure_text_line, element_lines))
|
||||
big_marsey_lines = list(filter(lambda a : a.is_big_marsey_line, element_lines))
|
||||
image_lines = list(filter(lambda a : a.is_image_line, element_lines))
|
||||
|
||||
argument_lines_count = len(argument_lines)
|
||||
dialog_lines_count = len(dialog_lines)
|
||||
pure_text_lines_count = len(pure_text_lines)
|
||||
big_marsey_lines_count = len(big_marsey_lines)
|
||||
image_lines_count = len(image_lines)
|
||||
|
||||
image = None
|
||||
if (dialog_lines_count == 2):
|
||||
print(f"[{comment['id']}] SOY_VS_CHAD")
|
||||
if (random_float <= SOY_VS_CHAD_TRIGGER_CHANGE):
|
||||
#Soy vs Chad
|
||||
|
||||
line1 = dialog_lines[0]
|
||||
line2 = dialog_lines[1]
|
||||
|
||||
emoji1 = line1.emojis[0].emoji
|
||||
emoji2 = line2.emojis[0].emoji
|
||||
caption1 = line1.text
|
||||
caption2 = line2.text
|
||||
|
||||
image = meme_generator.create_soy_vs_chad_meme(emoji1, emoji2, caption1, caption2)
|
||||
elif (big_marsey_lines_count == 1 and pure_text_lines_count == 1):
|
||||
print(f"[{comment['id']}] MODERN_MEME_WITH_MARSEY")
|
||||
if (random_float <= MODERN_MEME_WITH_MARSEY_TRIGGER_CHANGE):
|
||||
|
||||
# Modern Meme with Marsey
|
||||
text_line = pure_text_lines[0]
|
||||
marsey_line = big_marsey_lines[0]
|
||||
|
||||
marsey = marsey_line.emojis[0].emoji
|
||||
caption = text_line.text
|
||||
|
||||
image = meme_generator.create_modern_meme_from_emoji(marsey, caption)
|
||||
elif (image_lines_count == 1 and pure_text_lines_count == 1):
|
||||
print(f"[{comment['id']}] MODERN_MEME_WITH_IMAGE")
|
||||
if (random_float <= MODERN_MEME_WITH_IMAGE_TRIGGER_CHANGE):
|
||||
# Modern Meme with Image
|
||||
text_line = pure_text_lines[0]
|
||||
image_line = image_lines[0]
|
||||
|
||||
image = image_line.images[0]
|
||||
full_image_url = image.url
|
||||
caption = text_line.text
|
||||
|
||||
image = meme_generator.create_modern_meme_from_url(full_image_url, caption)
|
||||
elif (big_marsey_lines_count == 1 and pure_text_lines_count == 2):
|
||||
print(f"[{comment['id']}] CLASSIC_MEME_WITH_MARSEY")
|
||||
if (random_float <= CLASSIC_MEME_WITH_MARSEY_TRIGGER_CHANGE):
|
||||
# Classic Meme with big marsey
|
||||
top_text_line = pure_text_lines[0]
|
||||
bottom_text_line = pure_text_lines[1]
|
||||
marsey_line = big_marsey_lines[0]
|
||||
|
||||
emoji = marsey_line.emojis[0].emoji
|
||||
top_caption = top_text_line.text
|
||||
bottom_caption = bottom_text_line.text
|
||||
image = meme_generator.create_classic_meme_from_emoji(emoji, top_caption, bottom_caption)
|
||||
elif (image_lines_count == 1 and pure_text_lines_count == 2):
|
||||
print(f"[{comment['id']}] CLASSIC_MEME_WITH_IMAGE")
|
||||
if (random_float <= CLASSIC_MEME_WITH_IMAGE_TRIGGER_CHANGE):
|
||||
# Classic Meme with Image
|
||||
top_text_line = pure_text_lines[0]
|
||||
bottom_text_line = pure_text_lines[1]
|
||||
image_line = image_lines[0]
|
||||
|
||||
image = image_line.images[0]
|
||||
full_image_url = image.url
|
||||
top_caption = top_text_line.text
|
||||
bottom_caption = bottom_text_line.text
|
||||
|
||||
image = meme_generator.create_classic_meme_from_url(full_image_url, top_caption, bottom_caption)
|
||||
elif (argument_lines_count + dialog_lines_count >= 2):
|
||||
print(f"[{comment['id']}] WEBCOMIC")
|
||||
if (random_float <= WEBCOMIC_TRIGGER_CHANCE):
|
||||
panels : 'list[WebcomicPanel]' = []
|
||||
|
||||
for element_line in element_lines:
|
||||
if element_line.is_dialogue_line:
|
||||
caption = element_line.text
|
||||
emoji = element_line.emojis[0].emoji
|
||||
if len(caption) > 100:
|
||||
in_background = True
|
||||
else:
|
||||
in_background = False
|
||||
oneCharacterWebcomicPanel = OneCharacterWebcomicPanel(emoji, caption, in_background)
|
||||
panels.append(oneCharacterWebcomicPanel)
|
||||
elif element_line.is_argument_line:
|
||||
|
||||
if len(element_line.captions) == 2:
|
||||
left_caption = element_line.captions[0].text
|
||||
right_caption = element_line.captions[1].text
|
||||
elif (len(element_line.captions) == 1):
|
||||
left_caption = element_line.captions[0].text
|
||||
right_caption = ""
|
||||
else:
|
||||
left_caption = ""
|
||||
right_caption = ""
|
||||
left_emoji = element_line.emojis[0].emoji
|
||||
right_emoji = element_line.emojis[1].emoji
|
||||
twoCharacterWebcomicPanel = TwoCharacterWebcomicPanel(left_emoji, left_caption, right_emoji, right_caption)
|
||||
panels.append(twoCharacterWebcomicPanel)
|
||||
else:
|
||||
panels.append(TitleCardWebcomicPanel(element_line.text))
|
||||
|
||||
image = create_webcomic(panels)
|
||||
print(asizeof.asized(image, detail=1).format())
|
||||
if image != None:
|
||||
print(f"[{comment['id']}] posting...")
|
||||
image = add_watermark(image, comment['author']['username'])
|
||||
user_id = 0 if comment['author'] == '👻' else comment['author']['id']
|
||||
parent_comment_id = comment['id']
|
||||
post_id = comment['post_id']
|
||||
message = create_comment_message(chud, pizza, bird, is_marsey, mention=mention)
|
||||
if not DRY_MODE:
|
||||
automeme_comment_id = comment_with_image(message, image, comment['id'], comment['post_id'])
|
||||
else:
|
||||
automeme_comment_id = None
|
||||
image.save(get_real_filename(f"dry/{comment['id']}.webp"))
|
||||
|
||||
Comment.create_new_comment(parent_comment_id, automeme_comment_id, session)
|
||||
if post_id not in FREE_POSTS:
|
||||
Post.increment_replies(post_id, session)
|
||||
User.increase_number_of_comments(user_id, session)
|
||||
else:
|
||||
print(f"[{comment['id']}] is a free post.")
|
||||
end = datetime.now()
|
||||
print(end-begin)
|
||||
elif comment_contains_keyword(comment_text, DARRELL_KEYWORDS):
|
||||
if random_float <= DARRELL_CHANCE:
|
||||
message = choice(DARRELL_QUOTES)
|
||||
message += "\n\n"
|
||||
message += DARRELL_DISCLAIMER
|
||||
post_id = comment['post_id']
|
||||
user_id = 0 if comment['author'] == '👻' else comment['author']['id']
|
||||
new_comment_id = rdrama.reply_to_comment_easy(comment['id'], post_id, message)['id']
|
||||
Comment.create_new_comment(comment['id'], new_comment_id, session)
|
||||
Post.increment_replies(post_id, session)
|
||||
User.increase_number_of_comments(user_id, session)
|
||||
elif comment_contains_keyword(comment_text, LLM_KEYWORDS):
|
||||
message = "![](/images/1669576960061573.webp)"
|
||||
post_id = comment['post_id']
|
||||
user_id = 0 if comment['author'] == '👻' else comment['author']['id']
|
||||
new_comment_id = rdrama.reply_to_comment_easy(comment['id'], post_id, message)['id']
|
||||
Comment.create_new_comment(comment['id'], new_comment_id, session)
|
||||
Post.increment_replies(post_id, session)
|
||||
User.increase_number_of_comments(user_id, session)
|
||||
else:
|
||||
Comment.create_new_comment(comment['id'], None, session)
|
||||
except BaseException as e:
|
||||
print(f"YIKERINOS! GOT AN EXCEPTION: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
def comment_contains_keyword(comment, keywords):
|
||||
comment_words = comment.lower().split(" ")
|
||||
return len(set.intersection(set(comment_words), set(keywords))) != 0
|
||||
|
||||
def main_processing_task(rdrama : RDramaAPIInterface, session : Session):
|
||||
is_chudded = False #Do we have the chud award?
|
||||
can_communicate = True #Can we send any message at all?
|
||||
|
@ -269,175 +558,41 @@ def main_processing_task(rdrama : RDramaAPIInterface, session : Session):
|
|||
if automeme_information['is_banned']:
|
||||
print("We are banned. STOP.")
|
||||
can_communicate = False
|
||||
|
||||
if not can_communicate:
|
||||
print("I can't communicate. Why????")
|
||||
|
||||
if can_communicate:
|
||||
eligible_comments = get_eligible_comments(rdrama, session)
|
||||
for eligible_comment in eligible_comments:
|
||||
begin = datetime.now()
|
||||
under_post_limit = Post.get_number_of_replies(eligible_comment['post_id'], session) < ALLOWED_COMMENTS_PER_POST
|
||||
under_user_limit = User.get_number_of_comments(0 if eligible_comment['author'] == '👻' else eligible_comment['author']['id'], session) < ALLOWED_COMMENTS_PER_USER_PER_DAY
|
||||
has_not_replied_to_comment = Comment.get_comment(eligible_comment['id'], session) is None
|
||||
is_not_restricted_post = eligible_comment['post_id'] not in RESTRICTED_POSTS
|
||||
if (not (under_post_limit and under_user_limit and has_not_replied_to_comment and is_not_restricted_post)):
|
||||
continue
|
||||
handle_comment(eligible_comment, rdrama, session, is_chudded, is_pizzad, is_birdsite, is_marseyed)
|
||||
|
||||
def comment_chunk(time : datetime, api: RDramaAPIInterface):
|
||||
current_time_cur = int(time.timestamp()) # int(time.time() - 60*60*8)
|
||||
comments = []
|
||||
pages = 0
|
||||
while True:
|
||||
pages+=1
|
||||
res = api.get_comments(sort="old", lower_bound=current_time_cur)
|
||||
if len(res['data']) == 0:
|
||||
break
|
||||
if pages > MAX_PAGES_TO_SCAN:
|
||||
print("WARNING: Had to drop some pages. Can't make an omelette without cracking a few eggs")
|
||||
break
|
||||
comments = comments + res['data']
|
||||
current_time_cur = res['data'][-1]['created_utc']
|
||||
|
||||
comment_text = eligible_comment['body']
|
||||
directives = extract_directives(comment_text)
|
||||
cleaned_comment_text = strip_markdown(comment_text)
|
||||
return comments
|
||||
|
||||
|
||||
if ("meme" in directives):
|
||||
random_float = 0.0
|
||||
else:
|
||||
random_float = random()
|
||||
|
||||
comment_lines = cleaned_comment_text.split("\n")
|
||||
comment_lines = [comment_line for comment_line in comment_lines if comment_line != ""]
|
||||
element_lines = [TextLine(line) for line in comment_lines]
|
||||
|
||||
argument_lines_count, dialog_lines_count, text_lines_count, big_marsey_lines_count = 0,0,0,0
|
||||
|
||||
dialog_lines = list(filter(lambda a : a.is_dialogue_line, element_lines))
|
||||
argument_lines = list(filter(lambda a : a.is_argument_line, element_lines))
|
||||
pure_text_lines = list(filter(lambda a : a.is_pure_text_line, element_lines))
|
||||
big_marsey_lines = list(filter(lambda a : a.is_big_marsey_line, element_lines))
|
||||
image_lines = list(filter(lambda a : a.is_image_line, element_lines))
|
||||
|
||||
argument_lines_count = len(argument_lines)
|
||||
dialog_lines_count = len(dialog_lines)
|
||||
pure_text_lines_count = len(pure_text_lines)
|
||||
big_marsey_lines_count = len(big_marsey_lines)
|
||||
image_lines_count = len(image_lines)
|
||||
|
||||
image = None
|
||||
if (dialog_lines_count == 2):
|
||||
print(f"[{eligible_comment['id']}] SOY_VS_CHAD")
|
||||
if (random_float <= SOY_VS_CHAD_TRIGGER_CHANGE):
|
||||
#Soy vs Chad
|
||||
|
||||
line1 = dialog_lines[0]
|
||||
line2 = dialog_lines[1]
|
||||
|
||||
emoji1 = line1.emojis[0].emoji
|
||||
emoji2 = line2.emojis[0].emoji
|
||||
caption1 = line1.text
|
||||
caption2 = line2.text
|
||||
|
||||
image = meme_generator.create_soy_vs_chad_meme(emoji1, emoji2, caption1, caption2)
|
||||
elif (big_marsey_lines_count == 1 and pure_text_lines_count == 1):
|
||||
print(f"[{eligible_comment['id']}] MODERN_MEME_WITH_MARSEY")
|
||||
if (random_float <= MODERN_MEME_WITH_MARSEY_TRIGGER_CHANGE):
|
||||
|
||||
# Modern Meme with Marsey
|
||||
text_line = pure_text_lines[0]
|
||||
marsey_line = big_marsey_lines[0]
|
||||
|
||||
marsey = marsey_line.emojis[0].emoji
|
||||
caption = text_line.text
|
||||
|
||||
image = meme_generator.create_modern_meme_from_emoji(marsey, caption)
|
||||
elif (image_lines_count == 1 and pure_text_lines_count == 1):
|
||||
print(f"[{eligible_comment['id']}] MODERN_MEME_WITH_IMAGE")
|
||||
if (random_float <= MODERN_MEME_WITH_IMAGE_TRIGGER_CHANGE):
|
||||
# Modern Meme with Image
|
||||
text_line = pure_text_lines[0]
|
||||
image_line = image_lines[0]
|
||||
|
||||
image = image_line.images[0]
|
||||
full_image_url = image.url
|
||||
caption = text_line.text
|
||||
|
||||
image = meme_generator.create_modern_meme_from_url(full_image_url, caption)
|
||||
elif (big_marsey_lines_count == 1 and pure_text_lines_count == 2):
|
||||
print(f"[{eligible_comment['id']}] CLASSIC_MEME_WITH_MARSEY")
|
||||
if (random_float <= CLASSIC_MEME_WITH_MARSEY_TRIGGER_CHANGE):
|
||||
# Classic Meme with big marsey
|
||||
top_text_line = pure_text_lines[0]
|
||||
bottom_text_line = pure_text_lines[1]
|
||||
marsey_line = big_marsey_lines[0]
|
||||
|
||||
emoji = marsey_line.emojis[0].emoji
|
||||
top_caption = top_text_line.text
|
||||
bottom_caption = bottom_text_line.text
|
||||
image = meme_generator.create_classic_meme_from_emoji(emoji, top_caption, bottom_caption)
|
||||
elif (image_lines_count == 1 and pure_text_lines_count == 2):
|
||||
print(f"[{eligible_comment['id']}] CLASSIC_MEME_WITH_IMAGE")
|
||||
if (random_float <= CLASSIC_MEME_WITH_IMAGE_TRIGGER_CHANGE):
|
||||
# Classic Meme with Image
|
||||
top_text_line = pure_text_lines[0]
|
||||
bottom_text_line = pure_text_lines[1]
|
||||
image_line = image_lines[0]
|
||||
|
||||
image = image_line.images[0]
|
||||
full_image_url = image.url
|
||||
top_caption = top_text_line.text
|
||||
bottom_caption = bottom_text_line.text
|
||||
|
||||
image = meme_generator.create_classic_meme_from_url(full_image_url, top_caption, bottom_caption)
|
||||
elif (argument_lines_count + dialog_lines_count >= 2):
|
||||
print(f"[{eligible_comment['id']}] WEBCOMIC")
|
||||
if (random_float <= WEBCOMIC_TRIGGER_CHANCE):
|
||||
panels : 'list[WebcomicPanel]' = []
|
||||
|
||||
for element_line in element_lines:
|
||||
if element_line.is_dialogue_line:
|
||||
caption = element_line.text
|
||||
emoji = element_line.emojis[0].emoji
|
||||
if len(caption) > 100:
|
||||
in_background = True
|
||||
else:
|
||||
in_background = False
|
||||
oneCharacterWebcomicPanel = OneCharacterWebcomicPanel(emoji, caption, in_background)
|
||||
panels.append(oneCharacterWebcomicPanel)
|
||||
elif element_line.is_argument_line:
|
||||
left_caption = element_line.captions[0].text
|
||||
if len(element_line.captions) == 2:
|
||||
right_caption = element_line.captions[1].text
|
||||
else:
|
||||
right_caption = ""
|
||||
left_emoji = element_line.emojis[0].emoji
|
||||
right_emoji = element_line.emojis[1].emoji
|
||||
twoCharacterWebcomicPanel = TwoCharacterWebcomicPanel(left_emoji, left_caption, right_emoji, right_caption)
|
||||
panels.append(twoCharacterWebcomicPanel)
|
||||
elif element_line.is_pure_text_line:
|
||||
panels.append(TitleCardWebcomicPanel(element_line.text))
|
||||
|
||||
image = create_webcomic(panels)
|
||||
if image != None:
|
||||
print(f"[{eligible_comment['id']}] posting...")
|
||||
image = add_watermark(image, eligible_comment['author']['username'])
|
||||
user_id = 0 if eligible_comment['author'] == '👻' else eligible_comment['author']['id']
|
||||
parent_comment_id = eligible_comment['id']
|
||||
post_id = eligible_comment['post_id']
|
||||
message = create_comment_message(is_chudded, is_pizzad, is_birdsite, is_marseyed)
|
||||
if not DRY_MODE:
|
||||
automeme_comment_id = comment_with_image(message, image, eligible_comment['id'], eligible_comment['post_id'])
|
||||
else:
|
||||
automeme_comment_id = None
|
||||
image.save(f"dry/{eligible_comment['id']}.webp")
|
||||
|
||||
Comment.create_new_comment(parent_comment_id, automeme_comment_id, session)
|
||||
if post_id not in FREE_POSTS:
|
||||
Post.increment_replies(post_id, session)
|
||||
User.increase_number_of_comments(user_id, session)
|
||||
else:
|
||||
print(f"[{eligible_comment['id']}] is a free post.")
|
||||
end = datetime.now()
|
||||
print(end-begin)
|
||||
else:
|
||||
Comment.create_new_comment(eligible_comment['id'], None, session)
|
||||
|
||||
if __name__ == "__main__":
|
||||
TEST_AUTH_TOKEN = "ED3eURMKP9FKBFbi-JUxo8MPGWkEihuyIlAUGtVL7xwx0NEy4Nf6J_mxWYTPgAQx1iy1X91hx7PPHyEBS79hvKVIy5DMEzOyAe9PAc5pmqSJlLGq_-ROewMwFzGrqer4"
|
||||
print(f"======= RUNNING AT {datetime.now().hour}:{datetime.now().minute} ======= ")
|
||||
def get_rdrama():
|
||||
global AUTOMEME_ID, OPERATOR_ID
|
||||
if TEST_MODE:
|
||||
website = "localhost"
|
||||
auth = TEST_AUTH_TOKEN
|
||||
https = False
|
||||
timeout = 1
|
||||
AUTOMEME_ID = 7
|
||||
AUTOMEME_ID = 6
|
||||
OPERATOR_ID = 9
|
||||
ACTUALLY_CALL_OPEN_AI = False
|
||||
else:
|
||||
website = "rdrama.net"
|
||||
with open(get_real_filename("rdrama_auth_token"), "r") as f:
|
||||
|
@ -445,6 +600,11 @@ if __name__ == "__main__":
|
|||
https = True
|
||||
timeout = 10
|
||||
rdrama = RDramaAPIInterface(auth, website, timeout, https=https)
|
||||
return rdrama
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"======= RUNNING AT {datetime.now().hour}:{datetime.now().minute} ======= ")
|
||||
rdrama = get_rdrama()
|
||||
|
||||
#Set up fail safe
|
||||
def exitfunc():
|
||||
|
@ -460,4 +620,4 @@ if __name__ == "__main__":
|
|||
with Session(engine) as session:
|
||||
main_processing_task(rdrama, session)
|
||||
session.commit()
|
||||
timer.cancel()
|
||||
timer.cancel()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import math
|
||||
from tkinter import UNITS
|
||||
from typing import Tuple, Union
|
||||
from typing import Callable, Tuple, Union
|
||||
from PIL import Image
|
||||
from PIL import ImageDraw, ImageSequence
|
||||
from PIL import ImageFont
|
||||
|
@ -11,9 +10,10 @@ from utils import get_real_filename
|
|||
from image_utils import ImageText
|
||||
from os.path import exists
|
||||
from random import choice
|
||||
from pympler import tracker
|
||||
|
||||
HIGHLIGHT_MODE = False
|
||||
CAPTION_FILENAME = "watermark_captions.txt"
|
||||
HIGHLIGHT_MODE = False #Adds a square around the region where text is being put in a text box. Useful for debugging.
|
||||
CAPTION_FILENAME = "watermark_captions.txt" #Name of the file containing watermark captions.
|
||||
|
||||
class ColorScheme:
|
||||
BLACK = 0
|
||||
|
@ -29,7 +29,6 @@ def create_soy_vs_chad_meme(emoji1, emoji2, caption1, caption2):
|
|||
MIDDLE_MARGIN_ROW = 20
|
||||
TEXT_ROW = 100
|
||||
BOTTOM_MARGIN_ROW = 80
|
||||
|
||||
total_image_size_x = 2*CONTENT_COLUMN + LEFT_MARGIN_COLUMN + RIGHT_MARGIN_COLUMN + MIDDLE_MARGIN_COLUMN
|
||||
total_image_size_y = TOP_MARGIN_ROW + IMAGE_ROW + MIDDLE_MARGIN_ROW + TEXT_ROW + BOTTOM_MARGIN_ROW
|
||||
|
||||
|
@ -79,7 +78,7 @@ def create_classic_meme(image: 'AnimatedImage', top_caption : str, bottom_captio
|
|||
return image
|
||||
|
||||
def create_classic_meme_from_url(url : str, top_caption : str, bottom_caption : str) -> 'AnimatedImage':
|
||||
return create_classic_meme(get_image_file_from_url(url), top_caption, bottom_caption)
|
||||
return create_classic_meme(get_image_file_from_url(url).stretch_maintain_aspect_ratio(500), top_caption, bottom_caption)
|
||||
|
||||
def create_classic_meme_from_emoji(emoji : str, top_caption : str, bottom_caption : str):
|
||||
EMOJI_SIZE = 600
|
||||
|
@ -102,7 +101,7 @@ def create_modern_meme(image : 'AnimatedImage', caption : str) -> 'AnimatedImage
|
|||
return base
|
||||
|
||||
def create_modern_meme_from_url(url : str, caption : str):
|
||||
return create_modern_meme(get_image_file_from_url(url), caption)
|
||||
return create_modern_meme(get_image_file_from_url(url).stretch_maintain_aspect_ratio(500), caption)
|
||||
|
||||
def create_modern_meme_from_emoji(emoji: str, caption: str):
|
||||
EMOJI_SIZE = 600
|
||||
|
@ -117,9 +116,12 @@ def create_modern_meme_from_emoji(emoji: str, caption: str):
|
|||
return create_modern_meme(base, caption)
|
||||
|
||||
class WebcomicPanel():
|
||||
PANEL_SIZE = 400
|
||||
FONT = "Impact.ttf"
|
||||
COLOR = ColorScheme.WHITE_WITH_BLACK_BORDER
|
||||
'''
|
||||
A panel in a webcomic.
|
||||
'''
|
||||
PANEL_SIZE = 400 #Size of a webcomic panel.
|
||||
FONT = "impact.ttf" #The font to use.
|
||||
COLOR = ColorScheme.WHITE_WITH_BLACK_BORDER #The color of the font.
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
@ -127,10 +129,13 @@ class WebcomicPanel():
|
|||
def create_image(self) -> Image:
|
||||
return AnimatedImage.new((self.PANEL_SIZE,self.PANEL_SIZE))
|
||||
|
||||
def add_text_box(self, base : Image, caption : str, region_size : tuple[int, int], coordinates : tuple[int, int], align="") -> 'AnimatedImage':
|
||||
def add_text_box(self, base : Image, caption : str, region_size : 'tuple[int, int]', coordinates : 'tuple[int, int]', align="") -> 'AnimatedImage':
|
||||
return add_text_box(base, caption, region_size, coordinates, align=align, font=self.FONT, color=self.COLOR, init_font_size=int(self.PANEL_SIZE/10))
|
||||
|
||||
class OneCharacterWebcomicPanel(WebcomicPanel):
|
||||
'''
|
||||
Panel with one character talking.
|
||||
'''
|
||||
def __init__(self, emoji, caption, words_in_background):
|
||||
self.emoji = emoji
|
||||
self.caption = caption
|
||||
|
@ -156,6 +161,9 @@ class OneCharacterWebcomicPanel(WebcomicPanel):
|
|||
return add_border_to_image(base)
|
||||
|
||||
class TwoCharacterWebcomicPanel(WebcomicPanel):
|
||||
'''
|
||||
Panel with two characters talking.
|
||||
'''
|
||||
def __init__(self, left_emoji, left_caption, right_emoji, right_caption):
|
||||
self.left_emoji = left_emoji
|
||||
self.left_caption = left_caption
|
||||
|
@ -205,6 +213,9 @@ class TwoCharacterWebcomicPanel(WebcomicPanel):
|
|||
return add_border_to_image(base)
|
||||
|
||||
class TitleCardWebcomicPanel(WebcomicPanel):
|
||||
'''
|
||||
A caption in a webcomic.
|
||||
'''
|
||||
def __init__(self, caption):
|
||||
self.caption = caption
|
||||
|
||||
|
@ -222,13 +233,36 @@ def create_webcomic(layout : 'list[WebcomicPanel]'):
|
|||
|
||||
image = AnimatedImage.new((total_image_x_size, total_image_y_size))
|
||||
for i in range(len(layout)):
|
||||
print(i)
|
||||
panel = layout[i]
|
||||
x = i%2
|
||||
y = math.floor(i/2)
|
||||
image = image.paste(panel.create_image(), (x*assumed_panel_x_size, y*assumed_panel_y_size))
|
||||
return image
|
||||
|
||||
def add_text_box(base : Image, caption : str, region_size : tuple[int, int], coordinates : tuple[int, int], font : str= "arial.ttf", init_font_size = 45, align :str = "", color = ColorScheme.BLACK):
|
||||
def add_text_box(base : Image, caption : str, region_size : 'tuple[int, int]', coordinates : 'tuple[int, int]', font : str= "arial.ttf", init_font_size = 45, align :str = "", color = ColorScheme.BLACK):
|
||||
'''
|
||||
Adds a text box, where the size of the text scales to fit the box as closely as possible.
|
||||
|
||||
base: the image to put the text in.
|
||||
caption: the text
|
||||
region size: the size of the text box.
|
||||
coordinates: the position of the text box.
|
||||
font: name of the font file to use (defaults to arial)
|
||||
init_font_size: the largest font size that will be used.
|
||||
align: sets the alignment of the text.
|
||||
Horizontal Alignments:
|
||||
t: top
|
||||
b: bottom
|
||||
cv: center
|
||||
Vertical Alignments:
|
||||
l: left
|
||||
r: right
|
||||
ch: center
|
||||
color: what colorscheme to use.
|
||||
'''
|
||||
font = get_real_filename(font)
|
||||
|
||||
if caption == "":
|
||||
return base
|
||||
if init_font_size == 0:
|
||||
|
@ -244,6 +278,7 @@ def add_text_box(base : Image, caption : str, region_size : tuple[int, int], coo
|
|||
fill_color = (255,255,255)
|
||||
stroke = (0,0,0)
|
||||
stroke_size = 3
|
||||
caption = caption.upper()
|
||||
line_image = ImageText((region_x_size+stroke_size, region_y_size))
|
||||
place = "left"
|
||||
if "ch" in align:
|
||||
|
@ -285,7 +320,12 @@ def add_text_box(base : Image, caption : str, region_size : tuple[int, int], coo
|
|||
else:
|
||||
return add_text_box(base, caption, region_size, coordinates, font=font, init_font_size=init_font_size-1, align=align, color=color)
|
||||
|
||||
def add_text(base : Image, caption : str, region_size : tuple[int, int], coordinates : tuple[int, int], font : str= "arial.ttf"):
|
||||
def add_text(base : Image, caption : str, region_size : 'tuple[int, int]', coordinates : 'tuple[int, int]', font : str= "arial.ttf"):
|
||||
'''
|
||||
Very simple interface for adding text.
|
||||
'''
|
||||
font = get_real_filename(font)
|
||||
print(font)
|
||||
if caption == "":
|
||||
return
|
||||
region_x_size, region_y_size = region_size
|
||||
|
@ -294,6 +334,9 @@ def add_text(base : Image, caption : str, region_size : tuple[int, int], coordin
|
|||
return base.paste(AnimatedImage.from_image(line_image.image), coordinates)
|
||||
|
||||
def add_watermark(image : Image, name_of_other_creator):
|
||||
'''
|
||||
Adds the rdrama watermark to the bottom of the image, crediting the other creator of the image.
|
||||
'''
|
||||
global watermark_captions
|
||||
WATERMARK_HEIGHT = int(0.05 * image.height)
|
||||
image_size_x, image_size_y = image.size
|
||||
|
@ -312,7 +355,11 @@ def add_watermark(image : Image, name_of_other_creator):
|
|||
|
||||
return base
|
||||
|
||||
def center_and_paste(base : Image, to_paste : Image, coordinates: tuple[int, int], box_size : tuple[int, int]) -> 'AnimatedImage':
|
||||
def center_and_paste(base : Image, to_paste : Image, coordinates: 'tuple[int, int]', box_size : 'tuple[int, int]') -> 'AnimatedImage':
|
||||
'''
|
||||
Centers to_paste in a box whose upper-left corder is "coordinates", and with size box_size.
|
||||
'''
|
||||
|
||||
to_paste = to_paste.fit(box_size)
|
||||
|
||||
image_size_x, image_size_y = to_paste.size
|
||||
|
@ -331,6 +378,9 @@ def center_and_paste(base : Image, to_paste : Image, coordinates: tuple[int, int
|
|||
return base.paste(to_paste, (x, y))
|
||||
|
||||
def add_border_to_image(image : Image, thickness : int = 5):
|
||||
'''
|
||||
Adds a black border to an image.
|
||||
'''
|
||||
inner_image_x_size, inner_image_y_size = image.size
|
||||
outer_image_x_size, outer_image_y_size = inner_image_x_size + 2*thickness, inner_image_y_size + 2*thickness
|
||||
outside_image = AnimatedImage.new((outer_image_x_size,outer_image_y_size), color=(0,0,0))
|
||||
|
@ -339,6 +389,9 @@ def add_border_to_image(image : Image, thickness : int = 5):
|
|||
return outside_image
|
||||
|
||||
def get_emoji_from_rdrama(emoji_name) -> 'AnimatedImage':
|
||||
'''
|
||||
Gets an emoji from rdrama. If there is a "!" in the emoji name, it will be flipped.
|
||||
'''
|
||||
cleaned_emoji_name : str = emoji_name
|
||||
should_flip = False
|
||||
if '!' in emoji_name:
|
||||
|
@ -346,7 +399,7 @@ def get_emoji_from_rdrama(emoji_name) -> 'AnimatedImage':
|
|||
should_flip = True
|
||||
|
||||
if (exists(get_real_filename(f"emoji_cache/{cleaned_emoji_name}.webp"))):
|
||||
image = AnimatedImage.from_image(Image.open(f"emoji_cache/{cleaned_emoji_name}.webp"))
|
||||
image = AnimatedImage.from_image(Image.open(get_real_filename(f"emoji_cache/{cleaned_emoji_name}.webp")))
|
||||
|
||||
else:
|
||||
image = get_image_file_from_url(f"https://www.rdrama.net/e/{cleaned_emoji_name}.webp")
|
||||
|
@ -356,6 +409,9 @@ def get_emoji_from_rdrama(emoji_name) -> 'AnimatedImage':
|
|||
return image
|
||||
|
||||
def get_image_file_from_url(url) -> 'AnimatedImage':
|
||||
'''
|
||||
Gets the image at the given address.
|
||||
'''
|
||||
try:
|
||||
r = requests.get(url)
|
||||
image_file = io.BytesIO(r.content)
|
||||
|
@ -363,7 +419,11 @@ def get_image_file_from_url(url) -> 'AnimatedImage':
|
|||
return AnimatedImage.from_image(im)
|
||||
except:
|
||||
print(f"ERROR GETTING FILE FROM {url}")
|
||||
|
||||
def parse_caption_file(filename):
|
||||
'''
|
||||
Opens a file of strings and returns them.
|
||||
'''
|
||||
if not exists(filename):
|
||||
return []
|
||||
to_return = []
|
||||
|
@ -372,53 +432,84 @@ def parse_caption_file(filename):
|
|||
to_return.append(id.strip())
|
||||
return to_return
|
||||
|
||||
watermark_captions = parse_caption_file(get_real_filename(CAPTION_FILENAME))
|
||||
|
||||
|
||||
class AnimatedImage():
|
||||
def __init__(self, frames : list[Image.Image]):
|
||||
self.frames = frames
|
||||
'''
|
||||
Basically a list of images, meant to be animated.
|
||||
'''
|
||||
def __init__(self, frames : 'list[Image.Image]'):
|
||||
if len(frames) > 100:
|
||||
self.frames = frames[0:100]
|
||||
else:
|
||||
self.frames = frames
|
||||
|
||||
@property
|
||||
def size(self) -> Tuple[int, int]:
|
||||
return self.frames[0].size
|
||||
def size(self) -> 'Tuple[int, int]':
|
||||
'''
|
||||
Size of the image.
|
||||
'''
|
||||
return self.frames[0].size
|
||||
|
||||
@property
|
||||
def height(self) -> int:
|
||||
'''
|
||||
Height of the image.
|
||||
'''
|
||||
return self.frames[0].size[1]
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
'''
|
||||
Width of the image.
|
||||
'''
|
||||
return self.frames[0].size[0]
|
||||
|
||||
def flip(self) -> 'AnimatedImage':
|
||||
new_frames = []
|
||||
for frame in self.frames:
|
||||
#frame.show()
|
||||
new_frame = ImageOps.mirror(frame)
|
||||
#new_frame.show()
|
||||
new_frames.append(new_frame)
|
||||
return AnimatedImage(new_frames)
|
||||
'''
|
||||
Flips image horizontally.
|
||||
'''
|
||||
|
||||
return self.perform_transform_for_all_images(lambda image : ImageOps.mirror(image))
|
||||
|
||||
def save(self, filename) -> None:
|
||||
def save(self, filename : str) -> None:
|
||||
'''
|
||||
Saves the image to the filename
|
||||
'''
|
||||
self.frames[0].save(f"{filename}", save_all = True, append_images = self.frames[1:], duration = 100, loop=0)
|
||||
|
||||
def get_binary(self) -> bytes:
|
||||
'''
|
||||
Converts the image to binary.
|
||||
'''
|
||||
output = io.BytesIO()
|
||||
self.frames[0].save(output, format="webp", save_all = True, append_images = self.frames[1:], duration = 100, loop=0)
|
||||
return output.getvalue()
|
||||
|
||||
def get_binary_gif(self) -> bytes:
|
||||
output = io.BytesIO()
|
||||
self.frames[0].save(output, format="gif", save_all = True, append_images = self.frames[1:], duration = 100, loop=0)
|
||||
return output.getvalue()
|
||||
|
||||
def from_image(image: Image.Image) -> 'AnimatedImage':
|
||||
'''
|
||||
Converts a run of the mill PIL image to an AnimatedImage.
|
||||
'''
|
||||
frames = []
|
||||
for image_frame in ImageSequence.Iterator(image):
|
||||
frames.append(image_frame.copy())
|
||||
return AnimatedImage(frames)
|
||||
|
||||
def paste(self, other : 'AnimatedImage', position : 'Tuple[int, int]') -> 'AnimatedImage':
|
||||
'''
|
||||
Pastes one image onto another. Tries to do it without breaking the loop.
|
||||
'''
|
||||
num_self_frames = len(self.frames)
|
||||
num_other_frames = len(other.frames)
|
||||
|
||||
new_frames = []
|
||||
for i in range(get_ideal_number_of_frames(num_self_frames, num_other_frames)):
|
||||
frames_to_create = min(get_ideal_number_of_frames(num_self_frames, num_other_frames), 100)
|
||||
for i in range(frames_to_create):
|
||||
self_frame = self.frames[i%num_self_frames]
|
||||
other_frame = other.frames[i%num_other_frames]
|
||||
|
||||
|
@ -431,14 +522,35 @@ class AnimatedImage():
|
|||
new_frames.append(new_frame)
|
||||
return AnimatedImage(new_frames)
|
||||
|
||||
def new(size : Tuple[int, int], color=(255,255,255)) -> 'AnimatedImage':
|
||||
def new(size : 'Tuple[int, int]', color=(255,255,255)) -> 'AnimatedImage':
|
||||
'''
|
||||
Creates a blank AnimatedImage.
|
||||
'''
|
||||
base = Image.new(mode="RGB", size=size, color=color)
|
||||
return AnimatedImage.from_image(base)
|
||||
|
||||
def fit(self, region : Tuple[int, int]) -> 'AnimatedImage':
|
||||
def fit(self, region : 'Tuple[int, int]') -> 'AnimatedImage':
|
||||
'''
|
||||
Shrinks the image while preserving it's aspect ratio, such that the new image has the same ratio but is contained in the given box.
|
||||
'''
|
||||
return self.perform_transform_for_all_images(lambda image : ImageOps.contain(image, region))
|
||||
|
||||
def stretch(self, region : 'Tuple[int, int]') -> 'AnimatedImage':
|
||||
'''
|
||||
Resizes the image such that the final image has the exact dimensions given by region
|
||||
'''
|
||||
return self.perform_transform_for_all_images(lambda image : image.resize(region, Image.ANTIALIAS))
|
||||
|
||||
def stretch_maintain_aspect_ratio(self, size : int) -> 'AnimatedImage':
|
||||
aspect_ratio = self.height / self.width
|
||||
ideal_height = int(size * aspect_ratio)
|
||||
|
||||
return self.stretch((size, ideal_height))
|
||||
|
||||
def perform_transform_for_all_images(self, transform : 'Callable[[Image.Image], Image.Image]') -> 'AnimatedImage':
|
||||
new_frames = []
|
||||
for frame in self.frames:
|
||||
new_frames.append(ImageOps.contain(frame, region))
|
||||
new_frames.append(transform(frame))
|
||||
return AnimatedImage(new_frames)
|
||||
|
||||
def prime_decomposition(a):
|
||||
|
@ -479,6 +591,9 @@ def get_leftover_primes(a_, b_):
|
|||
leftover_primes.append(i)
|
||||
return leftover_primes
|
||||
|
||||
watermark_captions = parse_caption_file(get_real_filename(CAPTION_FILENAME))
|
||||
|
||||
|
||||
# shooting = get_emoji_from_rdrama("!marseyshooting")
|
||||
# shooting2 = get_emoji_from_rdrama("marseydeterminedgun")
|
||||
|
||||
|
@ -498,7 +613,7 @@ def get_leftover_primes(a_, b_):
|
|||
# OneCharacterWebcomicPanel("marseylaugh", "Just Kidding!", False)
|
||||
# ]).save("webcomic.webp")
|
||||
#create_modern_meme_from_url("https://media.giphy.com/media/gYkga3bZav66I/giphy.webp", "me when i see a black person (i am extremely racist)").save("racism.webp")
|
||||
#create_classic_meme_from_url("https://rdrama.net/assets/images/rDrama/sidebar/98.webp", "in all seriousness it probably isn't worth the effort because this is just one of many ways they could goad the bot into saying something innapropriate. Like who cares lol its just one of many things they could say.", "also there isn't really any specific harm done to anyone by a bot saying pedophile nonsense, it's offensive at worst and funny at best. i laughed at least. also also this legit is something that only happens every 2500 comments or so (there was another comment a while back where bbbb said something similar)").save("bruh.webp")
|
||||
#create_modern_meme_from_url("https://cdn.discordapp.com/attachments/1007776259910152292/1007833711540174885/unknown.png", "and then heymoon says the imposter is among us").save("modern_image.webp")
|
||||
#create_classic_meme_from_url("https://rdrama.net/images/16619117705921352.webp", "I left out 'hiking' intentionally since it's outdoor hobby 101. But even if he's trying to attract a homebody some kind of interest would be good to have. Like home brewing, art, instrument, or god forbid tabletop gaming.", "This just screams most boring, generic, codependent 'man' ever. I need a wife and I'll be happy to do whatever you want to do 24/7 because I have no personality, friends, or interests").save("classic.webp")
|
||||
#create_modern_meme_from_url("https://rdrama.net/images/16617243111639555.webp", "It really does, especially with that weird lighting. ANd what's up with the subset of troons with Himmler eyes?").save("modern_image.webp")
|
||||
#create_classic_meme_from_emoji("marseycock", "I WANT TO PUT MY DICK", "INSIDE OF MARSEY").save("classic_with_emoji.webp")
|
||||
#create_modern_meme_from_emoji("rdramajanny", "youll be sorry when i get my mop you BITCH").save("modern_emoji.webp")
|
|
@ -0,0 +1,47 @@
|
|||
from io import BytesIO
|
||||
import tweepy
|
||||
from automeme import TextLine, get_rdrama, strip_markdown
|
||||
import utils
|
||||
import json
|
||||
from meme_generator import get_image_file_from_url
|
||||
|
||||
def load_key_from_file(filename : str) -> str:
|
||||
with open(utils.get_real_filename(filename), "r") as f:
|
||||
return f.read()
|
||||
|
||||
consumer_key = load_key_from_file("twitter_api_key")
|
||||
consumer_secret = load_key_from_file("twitter_api_secret")
|
||||
access_token = load_key_from_file("twitter_access_token")
|
||||
access_token_secret = load_key_from_file("twitter_access_token_secret")
|
||||
|
||||
auth = tweepy.OAuth1UserHandler(
|
||||
consumer_key,
|
||||
consumer_secret,
|
||||
access_token,
|
||||
access_token_secret
|
||||
)
|
||||
api = tweepy.API(auth)
|
||||
|
||||
rdrama = get_rdrama()
|
||||
for comment in rdrama.get_comments(user = "automeme", sort = "top", t="day")['data']:
|
||||
if "Darrell Brooks" not in comment['body'] and "twitter" not in comment['body']:
|
||||
best = comment
|
||||
break
|
||||
print(json.dumps(best, indent=4))
|
||||
|
||||
comment_text = best['body']
|
||||
cleaned_comment_text = strip_markdown(comment_text)
|
||||
comment_lines = cleaned_comment_text.split("\n")
|
||||
comment_lines = [comment_line for comment_line in comment_lines if comment_line != ""]
|
||||
element_lines = [TextLine(line) for line in comment_lines]
|
||||
|
||||
for i in element_lines:
|
||||
print(i)
|
||||
|
||||
image = element_lines[-1].images[0]
|
||||
print(image.url)
|
||||
animated_image = get_image_file_from_url(image.url)
|
||||
file = BytesIO(initial_bytes=animated_image.get_binary_gif())
|
||||
media = api.media_upload(filename = "foo.gif", file=file, chunked=True, wait_for_async_finalize = True)
|
||||
media_id = media.media_id_string
|
||||
api.update_status("#meme", media_ids=[media_id])
|
|
@ -0,0 +1,98 @@
|
|||
from io import BytesIO
|
||||
import re
|
||||
import tweepy
|
||||
from automeme import TextLine, get_rdrama, strip_markdown
|
||||
import utils
|
||||
import json
|
||||
from meme_generator import get_image_file_from_url
|
||||
|
||||
def load_key_from_file(filename : str) -> str:
|
||||
with open(utils.get_real_filename(filename), "r") as f:
|
||||
return f.read()
|
||||
|
||||
TWEET_LENGTH = 280
|
||||
URL_LENGTH = 23
|
||||
HASHTAG_REGION = 23424977
|
||||
IMAGE_URL_REGEX = r"https://rdrama\.net/images/([1234567890]*)\.webp"
|
||||
TWEET_URL_REGEX = r"https://twitter\.com/([a-zA-Z]*)/status/([0-9]*)"
|
||||
|
||||
consumer_key = load_key_from_file("twitter_api_key")
|
||||
consumer_secret = load_key_from_file("twitter_api_secret")
|
||||
access_token = load_key_from_file("twitter_access_token")
|
||||
access_token_secret = load_key_from_file("twitter_access_token_secret")
|
||||
|
||||
auth = tweepy.OAuth1UserHandler(
|
||||
consumer_key,
|
||||
consumer_secret,
|
||||
access_token,
|
||||
access_token_secret
|
||||
)
|
||||
api = tweepy.API(auth)
|
||||
rdrama = get_rdrama()
|
||||
|
||||
def post_rdrama_tweet(post, api):
|
||||
tweet_url = post['url']
|
||||
tweet_text = get_tweet_text(post)
|
||||
|
||||
return api.update_status(tweet_text, attachment_url=tweet_url)
|
||||
|
||||
def post_rdrama_image(post, api):
|
||||
animated_image = get_image_file_from_url(post['url'])
|
||||
file = BytesIO(initial_bytes=animated_image.get_binary_gif())
|
||||
media = api.media_upload(filename = "foo.gif", file=file, chunked=True, wait_for_async_finalize = True)
|
||||
media_id = media.media_id_string
|
||||
tweet_text = get_tweet_text(post)
|
||||
return api.update_status(tweet_text, media_ids=[media_id])
|
||||
|
||||
def post_rdrama_basic_article(post, api):
|
||||
tweet_text = get_tweet_text(post)
|
||||
return api.update_status(tweet_text)
|
||||
|
||||
def hashtagify(string, api):
|
||||
trends = api.get_place_trends(id=23424977)[0]['trends']
|
||||
trend_words = []
|
||||
for trend in trends:
|
||||
name = trend['name'].lower()
|
||||
if '#' in name:
|
||||
trend_words.append(name[1:])
|
||||
else:
|
||||
for word in name.split(' '):
|
||||
trend_words.append(word)
|
||||
print(trend_words)
|
||||
input = string
|
||||
for hashtag in trend_words:
|
||||
input = re.sub(f" {hashtag} ", f" #{hashtag} ", input)
|
||||
return input
|
||||
|
||||
def get_tweet_text(post):
|
||||
rdrama_url = post['permalink']
|
||||
title = post['title']
|
||||
|
||||
text_space = TWEET_LENGTH - URL_LENGTH - 1
|
||||
actual_title = re.sub(r":[^ ]*:", "", title) #remove marseys
|
||||
actual_title = hashtagify(actual_title, api)
|
||||
if (len(title) > text_space):
|
||||
actual_title = actual_title[0:text_space-3] + "..."
|
||||
else:
|
||||
actual_title = actual_title
|
||||
|
||||
return f"{actual_title} {rdrama_url}"
|
||||
|
||||
def post_top_scoring_link(rdrama, api):
|
||||
post = None
|
||||
for current_post in rdrama.get_posts(sort='top', t='hour')['data']:
|
||||
if not current_post['club']:
|
||||
post = current_post
|
||||
break
|
||||
#post = rdrama.get_posts(sort='top', t='hour')['data'][0] #top scoring post
|
||||
if re.match(TWEET_URL_REGEX, post['url']):
|
||||
tweet = post_rdrama_tweet(post, api)
|
||||
elif re.match(IMAGE_URL_REGEX, post['url']):
|
||||
tweet = post_rdrama_image(post, api)
|
||||
else:
|
||||
tweet = post_rdrama_basic_article(post, api)
|
||||
url = f"https://twitter.com/drama_meme/status/{tweet.id}"
|
||||
rdrama.reply_to_post(post['id'], f"Nice post, bro! [I posted it to twitter]({url}).")
|
||||
|
||||
if __name__ == "__main__":
|
||||
post_top_scoring_link(rdrama, api)
|
Loading…
Reference in New Issue