458 lines
22 KiB
Python
458 lines
22 KiB
Python
|
#!/usr/bin/env python3.12
|
||
|
|
||
|
import os
|
||
|
import traceback
|
||
|
import json
|
||
|
import io
|
||
|
import asyncio
|
||
|
import random
|
||
|
from typing import LiteralString, Optional
|
||
|
import logging
|
||
|
import textwrap
|
||
|
import regex
|
||
|
import requests
|
||
|
import discord
|
||
|
from aiohttp import ClientSession
|
||
|
from discord.ext import bridge, commands, tasks
|
||
|
from jesusmemes import JesusMemeGenerator
|
||
|
import scrapers.reddit_scrape as memeg
|
||
|
import scrapers.explosm_scrape as explosmg
|
||
|
import scrapers.xkcd_scrape as xkcdg
|
||
|
import scrapers.smbc_scrape as smbcg
|
||
|
import scrapers.qc_scrape as qcg
|
||
|
import scrapers.dinosaur_scrape as dinog
|
||
|
import scrapers.onion_scrape as oniong
|
||
|
import scrapers.thn_scrape as thng
|
||
|
import constants
|
||
|
# pylint: disable=global-statement, bare-except, invalid-name, line-too-long
|
||
|
|
||
|
meme_choices = []
|
||
|
BOT_CHANIDS = []
|
||
|
|
||
|
class Helper:
|
||
|
"""Meme Helper"""
|
||
|
def load_meme_choices(self) -> None:
|
||
|
"""Load Available Meme Templates from JSON File"""
|
||
|
global meme_choices
|
||
|
|
||
|
memes_file: str|LiteralString = os.path.join(os.path.dirname(__file__), "memes.json")
|
||
|
with open(memes_file, 'r', encoding='utf-8') as f:
|
||
|
meme_choices = json.loads(f.read())
|
||
|
|
||
|
class MemeView(discord.ui.View):
|
||
|
"""Meme Selection discord.ui.View"""
|
||
|
helper = Helper()
|
||
|
helper.load_meme_choices()
|
||
|
@discord.ui.select(
|
||
|
placeholder = "Choose a Meme!",
|
||
|
min_values = 1,
|
||
|
max_values = 1,
|
||
|
options = [
|
||
|
discord.SelectOption(
|
||
|
label=meme_label
|
||
|
) for meme_label in meme_choices[0:24]
|
||
|
]
|
||
|
)
|
||
|
async def select_callback(self, select: discord.ui.Select,
|
||
|
interaction: discord.Interaction) -> None:
|
||
|
"""Meme Selection Callback"""
|
||
|
modal: discord.ui.Modal = MemeModal(meme=select.values[0], title="Meme Selected")
|
||
|
await interaction.response.send_modal(modal)
|
||
|
|
||
|
class MemeModal(discord.ui.Modal):
|
||
|
"""Meme Creation discord.ui.Modal"""
|
||
|
def __init__(self, *args, meme: Optional[str] = None, **kwargs) -> None:
|
||
|
super().__init__(*args, **kwargs)
|
||
|
self.selected_meme: str = meme
|
||
|
self.meme_generator = JesusMemeGenerator()
|
||
|
self.TEXT_LIMIT: int = 80
|
||
|
|
||
|
self.add_item(discord.ui.InputText(label="Top Text",
|
||
|
style=discord.InputTextStyle.singleline))
|
||
|
self.add_item(discord.ui.InputText(label="Bottom Text",
|
||
|
style=discord.InputTextStyle.singleline))
|
||
|
|
||
|
async def callback(self, interaction: discord.Interaction) -> None:
|
||
|
selected_meme: str = self.selected_meme
|
||
|
meme_top_line: str = self.children[0].value.strip()
|
||
|
meme_bottom_line: str = self.children[1].value.strip()
|
||
|
if len(meme_top_line) > self.TEXT_LIMIT or len(meme_bottom_line) > self.TEXT_LIMIT:
|
||
|
await interaction.response.send_message("ERR: Text is limited to 80 characters for each the top and bottom lines.")
|
||
|
return
|
||
|
|
||
|
meme_link: str = await self.meme_generator.create_meme(top_line=meme_top_line, bottom_line=meme_bottom_line, meme=selected_meme)
|
||
|
|
||
|
embed: discord.Embed = discord.Embed(title="Generated Meme")
|
||
|
embed.set_image(url=meme_link)
|
||
|
embed.add_field(name="Meme", value=self.selected_meme, inline=True)
|
||
|
await interaction.response.send_message(embeds=[embed])
|
||
|
return
|
||
|
|
||
|
class Meme(commands.Cog):
|
||
|
"""Meme Cog for Havoc"""
|
||
|
|
||
|
def __init__(self, bot) -> None:
|
||
|
self.bot: discord.Bot = bot
|
||
|
self.meme_choices: list = []
|
||
|
self.meme_counter: int = 0
|
||
|
self.THREADS: dict[dict[list]] = {
|
||
|
# Format: Guild1: [ChanId : [Webhook, ThreadId], Guild2: [ChanId : [Webhook, ThreadId]
|
||
|
'comic_explosm': {
|
||
|
1298729744216359055: [constants.EXPLOSM_WEBHOOK, 1299165855493390367],
|
||
|
1306414795049926676: [constants.EXPLOSM_WEBHOOK2, 1306416492304138364],
|
||
|
},
|
||
|
'comic_xkcd': {
|
||
|
1298729744216359055: [constants.XKCD_WEBHOOK, 1299165928755433483],
|
||
|
1306414795049926676: [constants.XKCD_WEBHOOK2, 1306416681991798854],
|
||
|
},
|
||
|
'comic_smbc': {
|
||
|
1298729744216359055: [constants.SMBC_WEBHOOK, 1299166071038808104],
|
||
|
1306414795049926676: [constants.SMBC_WEBHOOK2, 1306416842511745024],
|
||
|
},
|
||
|
'comic_qc': {
|
||
|
1298729744216359055: [constants.QC_WEBHOOK, 1299392115364593674],
|
||
|
1306414795049926676: [constants.QC_WEBHOOK2, 1306417084774744114],
|
||
|
},
|
||
|
'comic_dino': {
|
||
|
1298729744216359055: [constants.DINO_WEBHOOK, 1299771918886506557],
|
||
|
1306414795049926676: [constants.DINO_WEBHOOK2, 1306417286713704548],
|
||
|
}
|
||
|
}
|
||
|
|
||
|
self.NO_THREAD_WEBHOOKS: dict[list] = {
|
||
|
'theonion': [constants.ONION_WEBHOOK, constants.ONION_WEBHOOK2],
|
||
|
'thn': [constants.THN_WEBHOOK],
|
||
|
'memes': [constants.MEME_WEBHOOK1, constants.MEME_WEBHOOK2],
|
||
|
}
|
||
|
|
||
|
global BOT_CHANIDS
|
||
|
BOT_CHANIDS = self.bot.BOT_CHANIDS # Inherit
|
||
|
|
||
|
self.meme_stream_loop.start()
|
||
|
self.explosm_loop.start()
|
||
|
|
||
|
def is_spamchan() -> bool: # pylint: disable=no-method-argument
|
||
|
"""Check if channel is spamchan"""
|
||
|
def predicate(ctx):
|
||
|
try:
|
||
|
if not ctx.channel.id in BOT_CHANIDS:
|
||
|
logging.debug("%s not found in %s", ctx.channel.id, BOT_CHANIDS)
|
||
|
return ctx.channel.id in BOT_CHANIDS
|
||
|
except:
|
||
|
traceback.print_exc()
|
||
|
return False
|
||
|
return commands.check(predicate)
|
||
|
|
||
|
|
||
|
|
||
|
async def do_autos(self, only_comics: Optional[bool] = False) -> None:
|
||
|
"""
|
||
|
Run Auto Posters
|
||
|
Args:
|
||
|
only_comics (Optional[bool]): default False
|
||
|
Returns:
|
||
|
None
|
||
|
"""
|
||
|
try:
|
||
|
meme_grabber = memeg.MemeGrabber()
|
||
|
explosm_grabber = explosmg.ExplosmGrabber()
|
||
|
xkcd_grabber = xkcdg.XKCDGrabber()
|
||
|
smbc_grabber = smbcg.SMBCGrabber()
|
||
|
qc_grabber = qcg.QCGrabber()
|
||
|
dino_grabber = dinog.DinosaurGrabber()
|
||
|
onion_grabber = oniong.OnionGrabber()
|
||
|
thn_grabber = thng.THNGrabber()
|
||
|
explosm_comics = xkcd_comics = smbc_comics = qc_comics = dino_comics\
|
||
|
= onions = thns = []
|
||
|
memes: list[tuple] = await meme_grabber.get()
|
||
|
try:
|
||
|
try:
|
||
|
explosm_comics: list[tuple] = await explosm_grabber.get()
|
||
|
except:
|
||
|
pass
|
||
|
try:
|
||
|
xkcd_comics: list[tuple] = await xkcd_grabber.get()
|
||
|
except:
|
||
|
pass
|
||
|
try:
|
||
|
smbc_comics: list[tuple] = await smbc_grabber.get()
|
||
|
except:
|
||
|
pass
|
||
|
try:
|
||
|
qc_comics: list[tuple] = await qc_grabber.get()
|
||
|
print(f"QC: {qc_comics}")
|
||
|
except:
|
||
|
pass
|
||
|
try:
|
||
|
dino_comics: list[tuple] = await dino_grabber.get()
|
||
|
except Exception as e:
|
||
|
logging.debug("Dino failed: %s", str(e))
|
||
|
pass
|
||
|
try:
|
||
|
onions: list[tuple] = await onion_grabber.get()
|
||
|
except Exception as e:
|
||
|
logging.debug("Onion failed: %s", str(e))
|
||
|
pass
|
||
|
try:
|
||
|
thns: list[tuple] = await thn_grabber.get()
|
||
|
except Exception as e:
|
||
|
logging.debug("THNs failed: %s", str(e))
|
||
|
pass
|
||
|
except:
|
||
|
traceback.print_exc()
|
||
|
agents: list[str] = constants.HTTP_UA_LIST
|
||
|
headers: dict = {
|
||
|
'User-Agent': random.choice(agents)
|
||
|
}
|
||
|
if not only_comics:
|
||
|
try:
|
||
|
for meme in memes:
|
||
|
(meme_id, meme_title, meme_url) = meme # pylint: disable=unused-variable
|
||
|
request = requests.get(meme_url, stream=True, timeout=(5, 30), headers=headers)
|
||
|
if not request.status_code == 200:
|
||
|
continue
|
||
|
meme_content: bytes = request.raw.read()
|
||
|
for meme_hook in self.NO_THREAD_WEBHOOKS.get('memes'):
|
||
|
meme_image: io.BytesIO = io.BytesIO(meme_content)
|
||
|
ext: str = meme_url.split(".")[-1]\
|
||
|
.split("?")[0].split("&")[0]
|
||
|
async with ClientSession() as session:
|
||
|
webhook: discord.Webhook = discord.Webhook.from_url(meme_hook,
|
||
|
session=session)
|
||
|
await webhook.send(file=discord.File(meme_image,
|
||
|
filename=f'img.{ext}'), username="r/memes")
|
||
|
await asyncio.sleep(2)
|
||
|
except:
|
||
|
pass
|
||
|
try:
|
||
|
for comic in explosm_comics:
|
||
|
(comic_title, comic_url) = comic
|
||
|
comic_title: str = discord.utils.escape_markdown(comic_title)
|
||
|
comic_request = requests.get(comic_url, stream=True, timeout=(5, 20), headers=headers)
|
||
|
comic_request.raise_for_status()
|
||
|
comic_content: bytes = comic_request.raw.read()
|
||
|
ext: str = comic_url.split(".")[-1]\
|
||
|
.split("?")[0].split("&")[0]
|
||
|
|
||
|
async with ClientSession() as session:
|
||
|
for chanid, _hook in self.THREADS.get('comic_explosm').items():
|
||
|
comic_image: io.BytesIO = io.BytesIO(comic_content)
|
||
|
channel: int = chanid
|
||
|
hook_uri: str = _hook[0]
|
||
|
thread_id: int = _hook[1]
|
||
|
webhook: discord.Webhook = discord.Webhook.from_url(hook_uri,
|
||
|
session=session)
|
||
|
thread: discord.Thread = self.bot.get_channel(channel)\
|
||
|
.get_thread(thread_id)
|
||
|
await webhook.send(f"**{comic_title}**", file=discord.File(comic_image, filename=f'img.{ext}'),
|
||
|
username="Cyanide & Happiness", thread=thread)
|
||
|
await asyncio.sleep(2)
|
||
|
except:
|
||
|
pass
|
||
|
try:
|
||
|
for comic in xkcd_comics:
|
||
|
(comic_title, comic_url) = comic
|
||
|
comic_title: str = discord.utils.escape_markdown(comic_title)
|
||
|
comic_request = requests.get(comic_url, stream=True, timeout=(5, 20), headers=headers)
|
||
|
comic_request.raise_for_status()
|
||
|
comic_content: bytes = comic_request.raw.read()
|
||
|
comic_image: io.BytesIO = io.BytesIO(comic_request.raw.read())
|
||
|
ext: str = comic_url.split(".")[-1]\
|
||
|
.split("?")[0].split("&")[0]
|
||
|
|
||
|
async with ClientSession() as session:
|
||
|
for chanid, _hook in self.THREADS.get('comic_xkcd').items():
|
||
|
comic_image: io.BytesIO = io.BytesIO(comic_content)
|
||
|
channel: int = chanid
|
||
|
hook_uri: str = _hook[0]
|
||
|
thread_id: int = _hook[1]
|
||
|
webhook: discord.Webhook = discord.Webhook.from_url(hook_uri,
|
||
|
session=session)
|
||
|
thread: discord.Thread = self.bot.get_channel(channel)\
|
||
|
.get_thread(thread_id)
|
||
|
await webhook.send(f"**{comic_title}**", file=discord.File(comic_image, filename=f'img.{ext}'),
|
||
|
username="xkcd", thread=thread)
|
||
|
await asyncio.sleep(2)
|
||
|
except:
|
||
|
pass
|
||
|
try:
|
||
|
for comic in smbc_comics:
|
||
|
(comic_title, comic_url) = comic
|
||
|
comic_title: str = discord.utils.escape_markdown(comic_title)
|
||
|
comic_request = requests.get(comic_url, stream=True, timeout=(5, 20), headers=headers)
|
||
|
comic_request.raise_for_status()
|
||
|
comic_content: bytes = comic_request.raw.read()
|
||
|
ext: str = comic_url.split(".")[-1]\
|
||
|
.split("?")[0].split("&")[0]
|
||
|
|
||
|
async with ClientSession() as session:
|
||
|
for chanid, _hook in self.THREADS.get('comic_smbc').items():
|
||
|
comic_image: io.BytesIO = io.BytesIO(comic_content)
|
||
|
channel: int = chanid
|
||
|
hook_uri: str = _hook[0]
|
||
|
thread_id: int = _hook[1]
|
||
|
webhook: discord.Webhook = discord.Webhook.from_url(hook_uri,
|
||
|
session=session)
|
||
|
thread = self.bot.get_channel(channel)\
|
||
|
.get_thread(thread_id)
|
||
|
await webhook.send(f"**{comic_title}**", file=discord.File(comic_image, filename=f'img.{ext}'),
|
||
|
username="SMBC", thread=thread)
|
||
|
await asyncio.sleep(2)
|
||
|
except:
|
||
|
pass
|
||
|
try:
|
||
|
for comic in qc_comics:
|
||
|
logging.debug("Trying QC...")
|
||
|
(comic_title, comic_url) = comic
|
||
|
comic_title: str = discord.utils.escape_markdown(comic_title)
|
||
|
comic_url: str = regex.sub(r'^http://ww\.', 'http://www.',
|
||
|
comic_url)
|
||
|
comic_url: str = regex.sub(r'\.pmg$', '.png',
|
||
|
comic_url)
|
||
|
comic_request = requests.get(comic_url, stream=True,
|
||
|
timeout=(5, 20), headers=headers)
|
||
|
comic_request.raise_for_status()
|
||
|
comic_content: bytes = comic_request.raw.read()
|
||
|
ext: str = comic_url.split(".")[-1]\
|
||
|
.split("?")[0].split("&")[0]
|
||
|
|
||
|
async with ClientSession() as session:
|
||
|
for chanid, _hook in self.THREADS.get('comic_qc').items():
|
||
|
comic_image: io.BytesIO = io.BytesIO(comic_content)
|
||
|
channel: int = chanid
|
||
|
hook_uri: str = _hook[0]
|
||
|
thread_id: int = _hook[1]
|
||
|
webhook: discord.Webhook = discord.Webhook.from_url(hook_uri,
|
||
|
session=session)
|
||
|
thread: discord.Thread = self.bot.get_channel(channel)\
|
||
|
.get_thread(thread_id)
|
||
|
await webhook.send(f"**{comic_title}**", file=discord.File(comic_image, filename=f'img.{ext}'),
|
||
|
username="Questionable Content", thread=thread)
|
||
|
await asyncio.sleep(2)
|
||
|
except:
|
||
|
traceback.print_exc()
|
||
|
pass
|
||
|
try:
|
||
|
for comic in dino_comics:
|
||
|
(comic_title, comic_url) = comic
|
||
|
comic_title: str = discord.utils.escape_markdown(comic_title)
|
||
|
comic_request = requests.get(comic_url, stream=True, timeout=(5, 20), headers=headers)
|
||
|
comic_request.raise_for_status()
|
||
|
comic_content: bytes = comic_request.raw.read()
|
||
|
ext = comic_url.split(".")[-1]\
|
||
|
.split("?")[0].split("&")[0]
|
||
|
|
||
|
async with ClientSession() as session:
|
||
|
for chanid, _hook in self.THREADS.get('comic_dino').items():
|
||
|
comic_image: io.BytesIO = io.BytesIO(comic_content)
|
||
|
channel: int = chanid
|
||
|
hook_uri: str = _hook[0]
|
||
|
thread_id: int = _hook[1]
|
||
|
webhook: discord.Webhook = discord.Webhook.from_url(hook_uri,
|
||
|
session=session)
|
||
|
thread: discord.Thread = self.bot.get_channel(channel)\
|
||
|
.get_thread(thread_id)
|
||
|
await webhook.send(f"**{comic_title}**", file=discord.File(comic_image, filename=f'img.{ext}'),
|
||
|
username="Dinosaur Comics", thread=thread)
|
||
|
await asyncio.sleep(2)
|
||
|
except:
|
||
|
pass
|
||
|
try:
|
||
|
for onion in onions:
|
||
|
(onion_title, onion_description, onion_link, onion_video) = onion
|
||
|
onion_description: list[str] = textwrap.wrap(text=onion_description,
|
||
|
width=860, max_lines=1)[0]
|
||
|
embed: discord.Embed = discord.Embed(title=onion_title)
|
||
|
embed.add_field(name="Content", value=f"{onion_description[0:960]}\n-# {onion_link}")
|
||
|
async with ClientSession() as session:
|
||
|
for hook in self.NO_THREAD_WEBHOOKS.get('theonion'):
|
||
|
hook_uri: str = hook
|
||
|
webhook: discord.Webhook = discord.Webhook.from_url(hook_uri,
|
||
|
session=session)
|
||
|
await webhook.send(embed=embed, username="The Onion")
|
||
|
if onion_video:
|
||
|
await webhook.send(f"^ video: {onion_video}")
|
||
|
await asyncio.sleep(2)
|
||
|
except:
|
||
|
pass
|
||
|
try:
|
||
|
for thn in thns:
|
||
|
logging.debug("Trying thn...")
|
||
|
(thn_title, thn_description, thn_link, thn_pubdate, thn_video) = thn
|
||
|
thn_description: list[str] = textwrap.wrap(text=thn_description,
|
||
|
width=860, max_lines=1)[0]
|
||
|
embed: discord.Embed = discord.Embed(title=thn_title)
|
||
|
embed.add_field(name="Content", value=f"{thn_description[0:960]}\n-# {thn_link}")
|
||
|
embed.add_field(name="Published", value=thn_pubdate, inline=False)
|
||
|
async with ClientSession() as session:
|
||
|
for hook in self.NO_THREAD_WEBHOOKS.get('thn'):
|
||
|
hook_uri: str = hook
|
||
|
webhook: discord.Webhook = discord.Webhook.from_url(hook_uri,
|
||
|
session=session)
|
||
|
await webhook.send(embed=embed, username="The Hacker News")
|
||
|
if thn_video:
|
||
|
await webhook.send(f"^ video: {thn_video}")
|
||
|
await asyncio.sleep(2)
|
||
|
except:
|
||
|
pass
|
||
|
except:
|
||
|
# await self.bot.get_channel(self.MEMESTREAM_CHANID).send(f"FUCK, MY MEEMER! YOU DENTED MY MEEMER!")
|
||
|
traceback.print_exc()
|
||
|
|
||
|
@tasks.loop(hours=12.0)
|
||
|
async def meme_stream_loop(self) -> None:
|
||
|
"""Meme Stream Loop (r/memes)"""
|
||
|
try:
|
||
|
await asyncio.sleep(10) # Try to ensure we are ready first
|
||
|
self.meme_counter += 1
|
||
|
if self.meme_counter == 1:
|
||
|
return await self.do_autos(only_comics=True) # Skip first iteration!
|
||
|
|
||
|
await self.do_autos()
|
||
|
except:
|
||
|
traceback.print_exc()
|
||
|
|
||
|
@tasks.loop(hours=0.5)
|
||
|
async def explosm_loop(self) -> None:
|
||
|
"""Comic Loop"""
|
||
|
try:
|
||
|
await asyncio.sleep(10) # Try to ensure we are ready first
|
||
|
await self.do_autos(only_comics=True)
|
||
|
except:
|
||
|
traceback.print_exc()
|
||
|
|
||
|
@bridge.bridge_command()
|
||
|
@is_spamchan() # pylint: disable=too-many-function-args
|
||
|
async def meme(self, ctx) -> None:
|
||
|
"""Create Meme"""
|
||
|
await ctx.respond(view=MemeView())
|
||
|
|
||
|
@bridge.bridge_command(hidden=True)
|
||
|
@commands.is_owner()
|
||
|
async def domemestream(self, ctx) -> None:
|
||
|
"""Run Meme Stream Auto Post"""
|
||
|
try:
|
||
|
await ctx.respond("Trying!", ephemeral=True)
|
||
|
await self.do_autos()
|
||
|
except:
|
||
|
await ctx.respond("Fuck! :(", ephemeral=True)
|
||
|
traceback.print_exc()
|
||
|
|
||
|
@bridge.bridge_command(hidden=True)
|
||
|
@commands.is_owner()
|
||
|
async def doexplosm(self, ctx) -> None:
|
||
|
"""Run Comic Auto Posters"""
|
||
|
try:
|
||
|
await ctx.respond("Trying!", ephemeral=True)
|
||
|
await self.do_autos(only_comics=True)
|
||
|
except:
|
||
|
await ctx.respond("Fuck! :(", ephemeral=True)
|
||
|
traceback.print_exc()
|
||
|
|
||
|
def cog_unload(self) -> None:
|
||
|
self.meme_stream_loop.cancel()
|
||
|
self.explosm_loop.cancel()
|
||
|
|
||
|
def setup(bot) -> None:
|
||
|
"""Run on Cog Load"""
|
||
|
bot.add_cog(Meme(bot))
|