from __future__ import annotations from datetime import datetime import html import io import re import nextcord from nextcord import ChannelType, Message from Database.dbcontroller import DatabaseController from Database.x_classes import ErrorID, x_accounts from Twitter.tweetHelper import DownloadedMedia from exceptions import NO_CHANNEL, OTHER_ERROR from tweety.types.twDataTypes import Tweet from typing import TYPE_CHECKING if TYPE_CHECKING: from runtimeBotData import RuntimeBotData def chunks(s, n): """Produce `n`-character chunks from `s`.""" for start in range(0, len(s), n): yield s[start:start+n] def build_pixiv_embed(post): url = "https://www.pixiv.net/en/artworks/" + str(post.id) text = re.sub(r"https://t.co\S+", "", html.unescape(post.caption)) date = post.create_date embed=nextcord.Embed(description=text) embed.set_author(name=post.user.name, url=url) embed.set_footer(text=date) return embed def build_x_embed(handle : str, post : Tweet): url = "https://x.com/" + handle + "/status/" + str(post.id) text = re.sub(r"https://t.co\S+", "", html.unescape(post.text)) date = datetime.strftime(post.created_on, '%Y-%m-%d %H:%M:%S') embed=nextcord.Embed(description=text) embed.set_author(name=handle, url=url, icon_url=post.author.profile_image_url_https) embed.set_footer(text=date) return embed def build_secondary_embed(main_post : Message, handle : str, post : Tweet): text = re.sub(r"https://t.co\S+", "", html.unescape(post.text)) date = datetime.strftime(post.created_on, '%Y-%m-%d %H:%M:%S') attachment_urls = [attachment.url.split("?")[0] for attachment in main_post.attachments] embeds = [] for url in attachment_urls: embed = nextcord.Embed(url="https://katworks.sytes.net") embed.set_image(url) embeds.append(embed) if len(embeds) == 0: return None embed : nextcord.Embed = embeds[0] embed.description = text embed.set_footer(text=date) embed.set_author(name=handle, url=main_post.jump_url, icon_url=post.author.profile_image_url_https) return embeds async def send_error(ex : Exception, botData : RuntimeBotData): print(ex) errors_channel = nextcord.utils.get(botData.client.guilds[0].channels, name="bot-status") await errors_channel.send(content=str(ex)) def get_secondary_channel(is_animated, is_filtered, rating, tags : list, artist : x_accounts, guild : nextcord.Guild): if is_animated: return nextcord.utils.get(guild.channels, name="animation-feed") if artist.rating == 'NSFL': return nextcord.utils.get(guild.channels, name="hidden-feed") if "futanari" in tags: return nextcord.utils.get(guild.channels, name="hidden-feed") if is_filtered or not rating: return nextcord.utils.get(guild.channels, name="filtered-feed") if rating == "general": return nextcord.utils.get(guild.channels, name="safe-feed") if rating == "sensitive" or rating == "questionable": return nextcord.utils.get(guild.channels, name="unsafe-feed") if rating == 'explicit': return nextcord.utils.get(guild.channels, name="explicit-feed") return nextcord.utils.get(guild.channels, name="filtered-feed") async def edit_existing_embed_color(message : Message, color : nextcord.Colour): embeds = message.embeds embeds[0].colour = color await message.edit(embeds=embeds) async def send_x_post(post : Tweet, artist : x_accounts, guild : nextcord.Guild, new_accounts : list, files_to_send : list[DownloadedMedia], is_filtered: bool, rating: str, tags : list, auto_approve : bool = False, vox_labels : list = None, duplicate_posts : list = None, xView: nextcord.ui.View = None, yView: nextcord.ui.View = None): if vox_labels is None: vox_labels = [] if duplicate_posts is None: duplicate_posts = [] if artist.discord_channel_id != 0: channel = guild.get_channel(artist.discord_channel_id) elif artist.discord_thread_id != 0: channel = guild.get_thread(artist.discord_thread_id) else: raise NO_CHANNEL("Ensure channel for the account exists") embed = build_x_embed(artist.name, post) if rating != "": embed.add_field(name = "rating", value=rating, inline=False) embed.add_field(name = "tags", value=f'{", ".join(tags)}'.replace("_","\\_")[:1024], inline=False) if len(duplicate_posts) > 0: links = [f"[{id}]()" for id in duplicate_posts] embed.add_field(name = "duplicates", value=f'{" ".join(links)}'[:1024], inline=False) discord_files = [nextcord.File(fp = io.BytesIO(x.file_bytes), filename = x.file_name, force_close=True) for x in files_to_send] try: main_post : Message = await channel.send(embed=embed, files=discord_files) except Exception as e: raise OTHER_ERROR(e) finally: for file in discord_files: file.close() #skip posting in the public feed for accounts with too many posts if artist.name in new_accounts and not is_filtered: return main_post is_animated = files_to_send[0].file_name.endswith(".mp4") secondary_channel : nextcord.TextChannel = get_secondary_channel(is_animated, is_filtered, rating, tags, artist, guild) if is_animated: link = "https://vxtwitter.com/" + artist.name + "/status/" + post.id + " " + main_post.jump_url secondary_post : Message = await secondary_channel.send(content=link, view = None if auto_approve else yView) else: embeds = build_secondary_embed(main_post, artist.name, post) if rating != "": embeds[0].add_field(name = "rating", value=rating, inline=False) #embeds[0].add_field(name = "tags", value=f'{", ".join(tags)}'.replace("_","\\_")[:1024], inline=False) if len(vox_labels) > 0: embeds[0].add_field(name = "prediction", value=", ".join(vox_labels), inline=False) if len(duplicate_posts) > 0: links = [f"[{id}]()" for id in duplicate_posts] embeds[0].add_field(name = "duplicates", value=f'{" ".join(links)}'[:1024], inline=False) secondary_post : Message = await secondary_channel.send(embeds=embeds, view = None if auto_approve else xView) return main_post async def send_pixiv_post(post, files_to_send, channel : nextcord.TextChannel): embed = build_pixiv_embed(post) discord_files = [nextcord.File(fp = io.BytesIO(file["file"]), filename = file["name"], force_close=True) for file in files_to_send] try: main_post : Message = await channel.send(embed=embed, files=discord_files) except Exception as e: print(e) return None, ErrorID.OTHER_ERROR finally: for file in discord_files: file.close() return main_post, ErrorID.SUCCESS async def post_result(results, guild : nextcord.Guild, new_accounts : list): print("Results: ", len(results)) channel = nextcord.utils.get(guild.channels, name="bot-status") string = f'Last check: {datetime.now().strftime("%d/%m/%Y %H:%M:%S")}' for result in results: if result in new_accounts: string += f'\n{result} (new): {results[result]}' new_accounts.remove(result) else: string += f'\n{result}: {results[result]}' for chunk in chunks(string, 6000): embed = nextcord.Embed(description=chunk) await channel.send(embed=embed) async def get_channel_from_handle(guild: nextcord.Guild, handle : str): channel = nextcord.utils.get(guild.channels, name=handle.lower()) if channel is None: catId = 0 category = None while category is None or len(category.channels) == 50: category = nextcord.utils.get(guild.categories, name=f'TWITTER-{catId}') if category is None: category = await guild.create_category(f'TWITTER-{catId}') catId+=1 channel = await guild.create_text_channel(name=handle.lower(), category=category) return channel async def get_thread_from_handle(guild: nextcord.Guild, handle : str): channel = nextcord.utils.get(guild.channels, name="threads") thread = nextcord.utils.get(channel.threads, name=handle.lower()) if thread is None: thread = await channel.create_thread(name=handle.lower(), auto_archive_duration=10080, type=ChannelType.public_thread) return thread async def get_category_by_name(client: nextcord.Client, name): guild = client.guilds[0] category = nextcord.utils.get(guild.categories, name=name) if category is None: category = await guild.create_category(name) return category def check_permission(interaction : nextcord.Interaction): role = nextcord.utils.find(lambda r: r.name == 'Archivist', interaction.guild.roles) return role in interaction.user.roles async def message_from_jump_url(server : nextcord.Guild, jump_url:str): link = jump_url.split('/') server_id = int(link[4]) channel_id = int(link[5]) msg_id = int(link[6]) channel = server.get_channel(channel_id) if channel is None: channel = server.get_thread(channel_id) message = await channel.fetch_message(msg_id) return message async def get_main_post_and_data(guild, jump_url, botData : RuntimeBotData): main_post = await message_from_jump_url(guild, jump_url) tw_embed = main_post.embeds[0] x_post_id = int(tw_embed.author.url.split('/')[-1]) return main_post, x_post_id async def ensure_has_channel_or_thread(artist: x_accounts, guild: nextcord.Guild, database: DatabaseController): if artist.discord_channel_id == 0 and artist.discord_thread_id == 0: try: channel = await get_channel_from_handle(guild, artist.name) artist.discord_channel_id = channel.id artist.discord_thread_id = 0 except: thread = await get_thread_from_handle(guild, artist.name) artist.discord_thread_id = thread.id artist.discord_channel_id = 0 database.x_update_account(artist)