diff --git a/SunnexGB/Heroku-Modules/Assets/TgTheme/TgThemeLib.py b/SunnexGB/Heroku-Modules/Assets/TgTheme/TgThemeLib.py new file mode 100644 index 0000000..8b3213a --- /dev/null +++ b/SunnexGB/Heroku-Modules/Assets/TgTheme/TgThemeLib.py @@ -0,0 +1,826 @@ +# requires: Pillow + +import io +from PIL import Image +from .. import loader + +class ThemeLib(loader.Library): + developer = "@SunnexGB" + + def extract_colors(self, img, count=10): + small = img.copy().convert("RGB") + if max(small.size) > 200: + scale = 200 / max(small.size) + small = small.resize( + (int(small.size[0] * scale), int(small.size[1] * scale)), + Image.LANCZOS, + ) + + buckets = {} + for r, g, b in small.getdata(): + key = (r >> 4, g >> 4, b >> 4) + if key not in buckets: + buckets[key] = [r, g, b, 1] + else: + buckets[key][0] += r + buckets[key][1] += g + buckets[key][2] += b + buckets[key][3] += 1 + + top = sorted(buckets.values(), key=lambda x: x[3], reverse=True) + colors = [(sr // c, sg // c, sb // c) for sr, sg, sb, c in top[:count]] + return tuple(f"{c[0]:02x}{c[1]:02x}{c[2]:02x}" for c in colors[:3]) + + def darken(self, h, factor=0.7): + return ( + f"{int(int(h[0:2], 16) * factor):02x}" + f"{int(int(h[2:4], 16) * factor):02x}" + f"{int(int(h[4:6], 16) * factor):02x}" + ) + + def blend(self, h1, h2, ratio=0.15): + r1, g1, b1 = int(h1[0:2], 16), int(h1[2:4], 16), int(h1[4:6], 16) + r2, g2, b2 = int(h2[0:2], 16), int(h2[2:4], 16), int(h2[4:6], 16) + return ( + f"{int(r1 * (1 - ratio) + r2 * ratio):02x}" + f"{int(g1 * (1 - ratio) + g2 * ratio):02x}" + f"{int(b1 * (1 - ratio) + b2 * ratio):02x}" + ) + + def build_theme(self, bg_color, text_color, accent_color, alpha="ff"): + tr = lambda c, a: f"#{a}{bg_color if c == 'f' else text_color if c == 't' else accent_color}" + dsel = f"#{alpha}{self.blend(bg_color, accent_color, 0.25)}" + return ( + f"actionBarActionModeDefault={tr('f', alpha)}\n" + f"actionBarActionModeDefaultIcon=#{text_color}\n" + f"actionBarActionModeDefaultSelector=#{accent_color}\n" + f"actionBarActionModeDefaultTop=#{accent_color}\n" + f"actionBarActionModeReaction=#{accent_color}\n" + f"actionBarActionModeReactionText=#{text_color}\n" + f"actionBarActionModeReactionDot=#{text_color}\n" + f"actionBarBrowser=#{accent_color}\n" + f"actionBarDefault={tr('f', alpha)}\n" + f"actionBarDefaultArchived=#{accent_color}\n" + f"actionBarDefaultArchivedIcon=#{accent_color}\n" + f"actionBarDefaultArchivedSearch=#{accent_color}\n" + f"actionBarDefaultArchivedSelector=#{accent_color}\n" + f"actionBarDefaultArchivedTitle=#{accent_color}\n" + f"actionBarDefaultIcon=#{text_color}\n" + f"actionBarDefaultSearch=#{accent_color}\n" + f"actionBarDefaultSearchArchivedPlaceholder=#{accent_color}\n" + f"actionBarDefaultSearchPlaceholder=#{accent_color}\n" + f"actionBarDefaultSelector=#{accent_color}\n" + f"actionBarDefaultSubmenuBackground={tr('f', alpha)}\n" + f"actionBarDefaultSubmenuItem=#{text_color}\n" + f"actionBarDefaultSubmenuItemIcon=#{text_color}\n" + f"actionBarDefaultSubmenuSeparator=#{text_color}\n" + f"actionBarDefaultSubtitle=#{text_color}\n" + f"actionBarDefaultTitle=#{text_color}\n" + f"actionBarTabActiveText=#{accent_color}\n" + f"actionBarTabLine=#{accent_color}\n" + f"actionBarTabSelector=#{accent_color}\n" + f"actionBarTabUnactiveText=#{text_color}\n" + f"actionBarWhiteSelector=#{accent_color}\n" + f"avatar_actionBarIconBlue=#{text_color}\n" + f"avatar_actionBarSelectorBlue=#{accent_color}\n" + f"avatar_backgroundActionBarBlue=#{accent_color}\n" + f"avatar_backgroundArchived=#{accent_color}\n" + f"avatar_backgroundArchivedHidden=#{accent_color}\n" + f"avatar_backgroundBlue=#{accent_color}\n" + f"avatar_backgroundCyan=#{accent_color}\n" + f"avatar_backgroundGreen=#{accent_color}\n" + f"avatar_backgroundInProfileBlue=#{text_color}\n" + f"avatar_backgroundOrange=#{accent_color}\n" + f"avatar_backgroundPink=#{accent_color}\n" + f"avatar_backgroundRed=#{accent_color}\n" + f"avatar_backgroundSaved=#{accent_color}\n" + f"avatar_backgroundViolet=#{accent_color}\n" + f"avatar_background2Blue=#{accent_color}\n" + f"avatar_background2Cyan=#{accent_color}\n" + f"avatar_background2Green=#{accent_color}\n" + f"avatar_background2Orange=#{accent_color}\n" + f"avatar_background2Pink=#{accent_color}\n" + f"avatar_background2Red=#{accent_color}\n" + f"avatar_background2Saved=#{accent_color}\n" + f"avatar_background2Violet=#{accent_color}\n" + f"avatar_nameInMessageBlue=#{accent_color}\n" + f"avatar_nameInMessageCyan=#{accent_color}\n" + f"avatar_nameInMessageGreen=#{accent_color}\n" + f"avatar_nameInMessageOrange=#{accent_color}\n" + f"avatar_nameInMessagePink=#{accent_color}\n" + f"avatar_nameInMessageRed=#{accent_color}\n" + f"avatar_nameInMessageViolet=#{accent_color}\n" + f"avatar_subtitleInProfileBlue=#{text_color}\n" + f"avatar_text={tr('f', alpha)}\n" + f"bot_loadingIcon=#{text_color}\n" + f"calls_callReceivedGreenIcon=#{accent_color}\n" + f"calls_callReceivedRedIcon=#{text_color}\n" + f"changephoneinfo_image2=#{text_color}\n" + f"chats_actionBackground=#{accent_color}\n" + f"chats_actionIcon={tr('f', alpha)}\n" + f"chats_actionMessage=#{accent_color}\n" + f"chats_actionPressedBackground=#{accent_color}\n" + f"chats_archiveBackground=#{accent_color}\n" + f"chats_archiveIcon={tr('f', alpha)}\n" + f"chats_archivePinBackground=#{accent_color}\n" + f"chats_archivePullDownBackground=#{accent_color}\n" + f"chats_archivePullDownBackgroundActive=#{accent_color}\n" + f"chats_archiveText={tr('f', alpha)}\n" + f"chats_attachMessage=#{accent_color}\n" + f"chats_date=#{text_color}\n" + f"chats_draft=#{accent_color}\n" + f"chats_mentionIcon={tr('f', alpha)}\n" + f"chats_menuBackground={tr('f', alpha)}\n" + f"chats_menuItemCheck=#{text_color}\n" + f"chats_menuItemIcon=#{text_color}\n" + f"chats_menuItemText=#{text_color}\n" + f"chats_menuName=#{accent_color}\n" + f"chats_menuPhone=#{text_color}\n" + f"chats_menuPhoneCats=#{text_color}\n" + f"chats_menuTopBackground=#{accent_color}\n" + f"chats_menuTopBackgroundCats=#{accent_color}\n" + f"chats_menuTopShadow=#00000000\n" + f"chats_menuTopShadowCats=#00000000\n" + f"chats_message=#{text_color}\n" + f"chats_messageArchived=#{text_color}\n" + f"chats_message_threeLines=#{text_color}\n" + f"chats_muteIcon=#{text_color}\n" + f"chats_name=#{text_color}\n" + f"chats_nameArchived=#{text_color}\n" + f"chats_nameMessage=#{accent_color}\n" + f"chats_nameMessageArchived=#{accent_color}\n" + f"chats_nameMessageArchived_threeLines=#{accent_color}\n" + f"chats_nameMessage_threeLines=#{accent_color}\n" + f"chats_onlineCircle=#{accent_color}\n" + f"chats_pinnedIcon=#{text_color}\n" + f"chats_pinnedOverlay=#00000000\n" + f"chats_secretIcon=#{accent_color}\n" + f"chats_secretName=#{accent_color}\n" + f"chats_sentCheck=#{accent_color}\n" + f"chats_sentClock=#{text_color}\n" + f"chats_sentError=#{text_color}\n" + f"chats_sentErrorIcon={tr('f', alpha)}\n" + f"chats_sentReadCheck=#{accent_color}\n" + f"chats_tabUnreadActiveBackground=#{accent_color}\n" + f"chats_tabUnreadUnactiveBackground=#{accent_color}\n" + f"chats_tabletSelectedOverlay=#00000000\n" + f"chats_unreadCounter=#{accent_color}\n" + f"chats_unreadCounterMuted=#{text_color}\n" + f"chats_unreadCounterText={tr('f', alpha)}\n" + f"chats_verifiedBackground=#{accent_color}\n" + f"chats_verifiedCheck={tr('f', alpha)}\n" + f"chat_addContact=#{accent_color}\n" + f"chat_adminSelectedText=#{text_color}\n" + f"chat_adminText=#{text_color}\n" + f"chat_attachActiveTab=#{accent_color}\n" + f"chat_attachAudioBackground=#{accent_color}\n" + f"chat_attachAudioText={tr('f', alpha)}\n" + f"chat_attachCheckBoxBackground=#{accent_color}\n" + f"chat_attachCheckBoxCheck={tr('f', alpha)}\n" + f"chat_attachContactBackground=#{accent_color}\n" + f"chat_attachContactText={tr('f', alpha)}\n" + f"chat_attachEmptyImage=#{text_color}\n" + f"chat_attachFileBackground=#{accent_color}\n" + f"chat_attachFileText={tr('f', alpha)}\n" + f"chat_attachGalleryBackground=#{accent_color}\n" + f"chat_attachGalleryText={tr('f', alpha)}\n" + f"chat_attachIcon={tr('f', alpha)}\n" + f"chat_attachLocationBackground=#{accent_color}\n" + f"chat_attachLocationText={tr('f', alpha)}\n" + f"chat_attachPermissionImage=#{text_color}\n" + f"chat_attachPermissionMark=#{text_color}\n" + f"chat_attachPermissionText=#{text_color}\n" + f"chat_attachPhotoBackground=#00000000\n" + f"chat_attachPollBackground=#{accent_color}\n" + f"chat_attachPollText={tr('f', alpha)}\n" + f"chat_attachUnactiveTab=#{text_color}\n" + f"chat_BlurAlpha=#be000000\n" + f"chat_BlurAlphaSlow=#be000000\n" + f"chat_botButtonText=#{accent_color}\n" + f"chat_botKeyboardButtonBackground={tr('f', alpha)}\n" + f"chat_botKeyboardButtonBackgroundPressed=#{accent_color}\n" + f"chat_botKeyboardButtonText=#{accent_color}\n" + f"chat_botSwitchToInlineText=#{accent_color}\n" + f"chat_editMediaButton=#{accent_color}\n" + f"chat_emojiBottomPanelIcon=#{text_color}\n" + f"chat_emojiPanelBackground={tr('f', alpha)}\n" + f"chat_emojiPanelBackspace=#{text_color}\n" + f"chat_emojiPanelEmptyText=#{text_color}\n" + f"chat_emojiPanelIcon=#{text_color}\n" + f"chat_emojiPanelIconSelected=#{accent_color}\n" + f"chat_emojiPanelNewTrending=#{accent_color}\n" + f"chat_emojiPanelShadowLine=#{accent_color}\n" + f"chat_emojiPanelStickerPackSelector=#{accent_color}\n" + f"chat_emojiPanelStickerPackSelectorLine=#{accent_color}\n" + f"chat_emojiPanelStickerSetName=#{text_color}\n" + f"chat_emojiPanelStickerSetNameHighlight=#{accent_color}\n" + f"chat_emojiPanelStickerSetNameIcon=#{text_color}\n" + f"chat_emojiPanelTrendingDescription=#{text_color}\n" + f"chat_emojiPanelTrendingTitle=#{text_color}\n" + f"chat_emojiSearchBackground={tr('f', alpha)}\n" + f"chat_emojiSearchIcon=#{text_color}\n" + f"chat_fieldOverlayText=#{accent_color}\n" + f"chat_gifSaveHintBackground=#{accent_color}\n" + f"chat_gifSaveHintText={tr('f', alpha)}\n" + f"chat_goDownButton={tr('f', alpha)}\n" + f"chat_goDownButtonCounter={tr('f', alpha)}\n" + f"chat_goDownButtonCounterBackground=#{accent_color}\n" + f"chat_goDownButtonIcon=#{text_color}\n" + f"chat_inAudioCacheSeekbar=#{text_color}\n" + f"chat_inAudioDurationSelectedText=#{text_color}\n" + f"chat_inAudioDurationText=#{text_color}\n" + f"chat_inAudioPerfomerSelectedText=#{text_color}\n" + f"chat_inAudioPerfomerText=#{text_color}\n" + f"chat_inAudioProgress={tr('f', alpha)}\n" + f"chat_inAudioSeekbar=#{text_color}\n" + f"chat_inAudioSeekbarFill=#{accent_color}\n" + f"chat_inAudioSeekbarSelected=#{accent_color}\n" + f"chat_inAudioSelectedProgress=#{accent_color}\n" + f"chat_inAudioTitleText=#{text_color}\n" + f"chat_inBubble={tr('f', alpha)}\n" + f"chat_inBubbleSelected={dsel}\n" + f"chat_inBubbleShadow=#00000000\n" + f"chat_inContactBackground=#{accent_color}\n" + f"chat_inContactIcon={tr('f', alpha)}\n" + f"chat_inContactNameText=#{text_color}\n" + f"chat_inContactPhoneSelectedText=#{text_color}\n" + f"chat_inContactPhoneText=#{text_color}\n" + f"chat_inDownCall=#{text_color}\n" + f"chat_inFileBackground={tr('f', alpha)}\n" + f"chat_inFileBackgroundSelected=#{accent_color}\n" + f"chat_inFileInfoSelectedText=#{text_color}\n" + f"chat_inFileInfoText=#{text_color}\n" + f"chat_inFileNameText=#{text_color}\n" + f"chat_inFileProgress={tr('f', alpha)}\n" + f"chat_inFileProgressSelected=#{accent_color}\n" + f"chat_inForwardedNameText=#{accent_color}\n" + f"chat_inInstant=#{accent_color}\n" + f"chat_inInstantSelected=#{accent_color}\n" + f"chat_inlineResultIcon=#{accent_color}\n" + f"chat_inLoader=#{accent_color}\n" + f"chat_inLoaderPhoto=#{text_color}\n" + f"chat_inLoaderSelected=#{accent_color}\n" + f"chat_inLocationBackground=#{accent_color}\n" + f"chat_inLocationIcon=#{text_color}\n" + f"chat_inMediaIcon=#{text_color}\n" + f"chat_inMediaIconSelected=#{text_color}\n" + f"chat_inMenu=#{text_color}\n" + f"chat_inMenuSelected=#{text_color}\n" + f"chat_inPollCorrectAnswer=#{accent_color}\n" + f"chat_inPollWrongAnswer=#{text_color}\n" + f"chat_inPreviewInstantText=#{text_color}\n" + f"chat_inPreviewLine=#{accent_color}\n" + f"chat_inPsaNameText=#{text_color}\n" + f"chat_inReactionButtonBackground=#{accent_color}\n" + f"chat_inReactionButtonText=#{text_color}\n" + f"chat_inReactionButtonTextSelected=#{text_color}\n" + f"chat_inReplyLine=#{accent_color}\n" + f"chat_inReplyMediaMessageSelectedText=#{text_color}\n" + f"chat_inReplyMediaMessageText=#{text_color}\n" + f"chat_inReplyMessageText=#{text_color}\n" + f"chat_inReplyNameText=#{accent_color}\n" + f"chat_inSentClock=#{text_color}\n" + f"chat_inSentClockSelected=#{text_color}\n" + f"chat_inSiteNameText=#{accent_color}\n" + f"chat_inTextSelectionHighlight={tr('p', '44')}\n" + f"chat_inTimeSelectedText=#{text_color}\n" + f"chat_inTimeText=#{text_color}\n" + f"chat_inVenueInfoSelectedText=#{text_color}\n" + f"chat_inVenueInfoText=#{text_color}\n" + f"chat_inViaBotNameText=#{accent_color}\n" + f"chat_inViews=#{text_color}\n" + f"chat_inViewsSelected=#{text_color}\n" + f"chat_inVoiceSeekbar=#{text_color}\n" + f"chat_inVoiceSeekbarFill=#{accent_color}\n" + f"chat_inVoiceSeekbarSelected=#{accent_color}\n" + f"chat_linkSelectBackground={tr('p', '44')}\n" + f"chat_lockIcon=#{text_color}\n" + f"chat_mediaInfoText=#{text_color}\n" + f"chat_mediaLoaderPhoto=#00000000\n" + f"chat_mediaLoaderPhotoIcon=#{text_color}\n" + f"chat_mediaLoaderPhotoIconSelected=#{text_color}\n" + f"chat_mediaLoaderPhotoSelected=#00000000\n" + f"chat_mediaMenu=#{text_color}\n" + f"chat_mediaProgress={tr('f', alpha)}\n" + f"chat_mediaSentCheck=#{text_color}\n" + f"chat_mediaSentClock=#{text_color}\n" + f"chat_mediaTimeBackground={tr('p', '88')}\n" + f"chat_mediaTimeText={tr('f', alpha)}\n" + f"chat_mediaViews=#{text_color}\n" + f"chat_messageLinkIn=#{accent_color}\n" + f"chat_messageLinkOut=#{accent_color}\n" + f"chat_messagePanelBackground={tr('f', alpha)}\n" + f"chat_messagePanelCancelInlineBot={tr('f', alpha)}\n" + f"chat_messagePanelCursor=#{accent_color}\n" + f"chat_messagePanelHint=#{text_color}\n" + f"chat_messagePanelIcons=#{text_color}\n" + f"chat_messagePanelSend=#{accent_color}\n" + f"chat_messagePanelShadow=#{accent_color}\n" + f"chat_messagePanelText=#{text_color}\n" + f"chat_messagePanelVoiceBackground=#{accent_color}\n" + f"chat_messagePanelVoiceDelete=#{text_color}\n" + f"chat_messagePanelVoiceDuration={tr('f', alpha)}\n" + f"chat_messagePanelVoicePressed=#{accent_color}\n" + f"chat_messageTextIn=#{text_color}\n" + f"chat_messageTextOut=#{text_color}\n" + f"chat_muteIcon=#{text_color}\n" + f"chat_outAdminSelectedText=#{text_color}\n" + f"chat_outAdminText=#{text_color}\n" + f"chat_outAudioCacheSeekbar=#{text_color}\n" + f"chat_outAudioDurationSelectedText=#{text_color}\n" + f"chat_outAudioDurationText=#{text_color}\n" + f"chat_outAudioPerfomerSelectedText=#{text_color}\n" + f"chat_outAudioPerfomerText=#{text_color}\n" + f"chat_outAudioProgress=#{accent_color}\n" + f"chat_outAudioSeekbar=#{accent_color}\n" + f"chat_outAudioSeekbarFill=#{accent_color}\n" + f"chat_outAudioSeekbarSelected=#{accent_color}\n" + f"chat_outAudioSelectedProgress=#{accent_color}\n" + f"chat_outAudioTitleText=#{text_color}\n" + f"chat_outBubble={tr('f', alpha)}\n" + f"noGradient=#{accent_color}\n" + f"noGradient2=#{accent_color}\n" + f"noGradient3=#{accent_color}\n" + f"chat_outBubbleGradientAnimated=#{accent_color}\n" + f"chat_outBubbleGradientSelectedOverlay=#{accent_color}\n" + f"chat_outBubbleSelected={dsel}\n" + f"chat_outBubbleShadow=#00000000\n" + f"chat_outContactBackground={tr('f', alpha)}\n" + f"chat_outContactIcon=#{text_color}\n" + f"chat_outContactNameText=#{text_color}\n" + f"chat_outContactPhoneSelectedText=#{text_color}\n" + f"chat_outContactPhoneText=#{text_color}\n" + f"chat_outFileBackground={tr('f', alpha)}\n" + f"chat_outFileBackgroundSelected=#{accent_color}\n" + f"chat_outFileInfoSelectedText=#{text_color}\n" + f"chat_outFileInfoText=#{text_color}\n" + f"chat_outFileNameText=#{text_color}\n" + f"chat_outFileProgress=#{accent_color}\n" + f"chat_outFileProgressSelected=#{accent_color}\n" + f"chat_outForwardedNameText=#{text_color}\n" + f"chat_outInstant=#{text_color}\n" + f"chat_outInstantSelected=#{text_color}\n" + f"chat_outLinkSelectBackground={tr('p', '44')}\n" + f"chat_outLoader=#{text_color}\n" + f"chat_outLoaderSelected=#{text_color}\n" + f"chat_outLocationIcon=#{text_color}\n" + f"chat_outMediaIcon=#{accent_color}\n" + f"chat_outMediaIconSelected=#{accent_color}\n" + f"chat_outMenu=#{text_color}\n" + f"chat_outMenuSelected=#{text_color}\n" + f"chat_outPollCorrectAnswer=#{accent_color}\n" + f"chat_outPollWrongAnswer=#{text_color}\n" + f"chat_outPreviewInstantText=#{text_color}\n" + f"chat_outPreviewLine=#{accent_color}\n" + f"chat_outPsaNameText=#{text_color}\n" + f"chat_outReactionButtonBackground={tr('p', '88')}\n" + f"chat_outReactionButtonText=#{text_color}\n" + f"chat_outReactionButtonTextSelected=#{text_color}\n" + f"chat_outReplyLine=#{accent_color}\n" + f"chat_outReplyMediaMessageSelectedText=#{text_color}\n" + f"chat_outReplyMediaMessageText=#{text_color}\n" + f"chat_outReplyMessageText=#{text_color}\n" + f"chat_outReplyNameText=#{text_color}\n" + f"chat_outSentCheck=#{text_color}\n" + f"chat_outSentCheckRead=#{text_color}\n" + f"chat_outSentCheckReadSelected=#{text_color}\n" + f"chat_outSentCheckSelected=#{text_color}\n" + f"chat_outSentClock=#{text_color}\n" + f"chat_outSentClockSelected=#{text_color}\n" + f"chat_outSiteNameText=#{text_color}\n" + f"chat_outTextSelectionCursor=#{text_color}\n" + f"chat_outTextSelectionHighlight={tr('p', '44')}\n" + f"chat_outTimeSelectedText=#{text_color}\n" + f"chat_outTimeText=#{text_color}\n" + f"chat_outUpCall=#{text_color}\n" + f"chat_outVenueInfoSelectedText=#{text_color}\n" + f"chat_outVenueInfoText=#{text_color}\n" + f"chat_outViaBotNameText=#{text_color}\n" + f"chat_outViews=#{text_color}\n" + f"chat_outViewsSelected=#{text_color}\n" + f"chat_outVoiceSeekbar=#{accent_color}\n" + f"chat_outVoiceSeekbarFill=#{accent_color}\n" + f"chat_outVoiceSeekbarSelected=#{accent_color}\n" + f"chat_previewDurationText={tr('f', alpha)}\n" + f"chat_previewGameText={tr('f', alpha)}\n" + f"chat_recordedVoiceBackground=#{accent_color}\n" + f"chat_recordedVoiceDarkerBackground=#{accent_color}\n" + f"chat_recordedVoiceDot=#{text_color}\n" + f"chat_recordedVoicePlayPause={tr('f', alpha)}\n" + f"chat_recordedVoiceProgress=#{accent_color}\n" + f"chat_recordedVoiceProgressInner={tr('f', alpha)}\n" + f"chat_recordTime=#{text_color}\n" + f"chat_recordVoiceCancel=#{text_color}\n" + f"chat_replyPanelClose=#{text_color}\n" + f"chat_replyPanelIcons=#{accent_color}\n" + f"chat_replyPanelLine=#{accent_color}\n" + f"chat_replyPanelName=#{accent_color}\n" + f"chat_searchPanelIcons=#{text_color}\n" + f"chat_searchPanelText=#{text_color}\n" + f"chat_secretChatStatusText=#{text_color}\n" + f"chat_secretTimeText=#{text_color}\n" + f"chat_selectedBackground={tr('p', '55')}\n" + f"chat_sentError=#{text_color}\n" + f"chat_sentErrorIcon={tr('f', alpha)}\n" + f"chat_serviceBackground={tr('f', '88')}\n" + f"chat_serviceBackgroundSelected=#{accent_color}\n" + f"chat_serviceBackgroundSelector=#{accent_color}\n" + f"chat_serviceIcon=#{text_color}\n" + f"chat_serviceLink=#{accent_color}\n" + f"chat_serviceText=#{text_color}\n" + f"chat_status=#{accent_color}\n" + f"chat_stickerNameText={tr('f', alpha)}\n" + f"chat_stickerReplyLine=#{accent_color}\n" + f"chat_stickerReplyMessageText=#{text_color}\n" + f"chat_stickerReplyNameText=#{accent_color}\n" + f"chat_stickerViaBotNameText={tr('f', alpha)}\n" + f"chat_stickersHintPanel={tr('f', alpha)}\n" + f"chat_textSelectBackground={tr('p', '55')}\n" + f"chat_TextSelectionCursor=#{text_color}\n" + f"chat_topPanelBackground={tr('f', alpha)}\n" + f"chat_topPanelClose=#{text_color}\n" + f"chat_topPanelLine=#{accent_color}\n" + f"chat_topPanelMessage=#{text_color}\n" + f"chat_topPanelTitle=#{accent_color}\n" + f"chat_unreadMessagesStartArrowIcon=#{text_color}\n" + f"chat_unreadMessagesStartBackground=#{accent_color}\n" + f"chat_unreadMessagesStartText={tr('f', alpha)}\n" + f"checkbox=#{accent_color}\n" + f"checkboxCheck={tr('f', alpha)}\n" + f"checkboxDisabled=#{text_color}\n" + f"checkboxSquareBackground=#{accent_color}\n" + f"checkboxSquareCheck={tr('f', alpha)}\n" + f"checkboxSquareDisabled=#{text_color}\n" + f"checkboxSquareUnchecked=#{text_color}\n" + f"color_blue=#{accent_color}\n" + f"color_green=#{accent_color}\n" + f"color_lightblue=#{accent_color}\n" + f"color_lightgreen=#{accent_color}\n" + f"color_orange=#{accent_color}\n" + f"color_purple=#{accent_color}\n" + f"color_red=#{text_color}\n" + f"color_yellow=#{accent_color}\n" + f"contacts_inviteBackground=#{accent_color}\n" + f"contacts_inviteText={tr('f', alpha)}\n" + f"contextProgressInner1=#{accent_color}\n" + f"contextProgressInner2=#{accent_color}\n" + f"contextProgressInner3=#{accent_color}\n" + f"contextProgressInner4=#{accent_color}\n" + f"contextProgressOuter1=#{accent_color}\n" + f"contextProgressOuter2=#{accent_color}\n" + f"contextProgressOuter3=#{accent_color}\n" + f"contextProgressOuter4=#{accent_color}\n" + f"dialogBackground={tr('f', alpha)}\n" + f"dialogBackgroundGray={tr('f', alpha)}\n" + f"dialogButton=#{accent_color}\n" + f"dialogButtonSelector=#{accent_color}\n" + f"dialogCardShadow=#00000000\n" + f"dialogCameraIcon={tr('f', alpha)}\n" + f"dialogCheckboxSquareBackground=#{accent_color}\n" + f"dialogCheckboxSquareCheck={tr('f', alpha)}\n" + f"dialogCheckboxSquareDisabled=#{text_color}\n" + f"dialogCheckboxSquareUnchecked=#{text_color}\n" + f"dialogEmptyImage=#{text_color}\n" + f"dialogEmptyText=#{text_color}\n" + f"dialogFloatingButton=#{accent_color}\n" + f"dialogFloatingButtonPressed=#{accent_color}\n" + f"dialogFloatingIcon={tr('f', alpha)}\n" + f"dialogGiftsBackground=#{accent_color}\n" + f"dialogGiftsTabText=#{text_color}\n" + f"dialogGrayLine=#{text_color}\n" + f"dialogIcon=#{text_color}\n" + f"dialogInputField=#{text_color}\n" + f"dialogInputFieldActivated=#{accent_color}\n" + f"dialogLineProgress=#{accent_color}\n" + f"dialogLineProgressBackground=#{accent_color}\n" + f"dialogLinkSelection={tr('p', '44')}\n" + f"dialogRadioBackground=#{text_color}\n" + f"dialogRadioBackgroundChecked=#{accent_color}\n" + f"dialogReactionMentionBackground=#{accent_color}\n" + f"dialogRoundCheckBox=#{accent_color}\n" + f"dialogRoundCheckBoxCheck={tr('f', alpha)}\n" + f"dialogScrollGlow={tr('f', alpha)}\n" + f"dialogSearchBackground={tr('f', alpha)}\n" + f"dialogSearchHint=#{text_color}\n" + f"dialogSearchIcon=#{text_color}\n" + f"dialogSearchText=#{text_color}\n" + f"dialogShadowLine=#{text_color}\n" + f"dialogSwipeRemove=#{text_color}\n" + f"dialogTextBlack=#{text_color}\n" + f"dialogTextBlue=#{accent_color}\n" + f"dialogTextBlue2=#{accent_color}\n" + f"dialogTextBlue4=#{accent_color}\n" + f"dialogTextGray=#{accent_color}\n" + f"dialogTextGray2=#{text_color}\n" + f"dialogTextGray3=#{text_color}\n" + f"dialogTextGray4=#{text_color}\n" + f"dialogTextHint=#{text_color}\n" + f"dialogTextLink=#{accent_color}\n" + f"dialogTopBackground=#{accent_color}\n" + f"dialog_inlineProgress=#{accent_color}\n" + f"dialog_inlineProgressBackground=#{text_color}\n" + f"dialog_liveLocationProgress=#{accent_color}\n" + f"divider={tr('f', '66')}\n" + f"emptyListPlaceholder=#{text_color}\n" + f"fastScrollActive=#{accent_color}\n" + f"fastScrollInactive=#{text_color}\n" + f"fastScrollText={tr('f', alpha)}\n" + f"featuredStickers_addButton=#{accent_color}\n" + f"featuredStickers_addButtonPressed=#{accent_color}\n" + f"featuredStickers_addedIcon=#{accent_color}\n" + f"featuredStickers_buttonProgress=#{accent_color}\n" + f"featuredStickers_buttonText={tr('f', alpha)}\n" + f"featuredStickers_removeButtonText=#{text_color}\n" + f"featuredStickers_unread=#{accent_color}\n" + f"files_folderIcon=#{text_color}\n" + f"files_folderIconBackground=#{accent_color}\n" + f"files_iconText=#{text_color}\n" + f"fill_RedNormal=#{text_color}\n" + f"fill_RedDark=#{text_color}\n" + f"gift_ribbon=#{accent_color}\n" + f"gift_ribbon_soldout=#{accent_color}\n" + f"graySection=#{text_color}\n" + f"groupcreate_cursor=#{accent_color}\n" + f"groupcreate_hintText=#{text_color}\n" + f"groupcreate_sectionShadow=#{accent_color}\n" + f"groupcreate_sectionText=#{text_color}\n" + f"groupcreate_spanBackground={tr('f', alpha)}\n" + f"groupcreate_spanDelete=#{accent_color}\n" + f"groupcreate_spanText=#{text_color}\n" + f"inappPlayerBackground={tr('f', alpha)}\n" + f"inappPlayerClose=#{text_color}\n" + f"inappPlayerPerformer=#{text_color}\n" + f"inappPlayerPlayPause=#{accent_color}\n" + f"inappPlayerTitle=#{text_color}\n" + f"iv_ab_progress=#{accent_color}\n" + f"iv_background=#00000000\n" + f"iv_backgroundGray=#00000000\n" + f"iv_navigationBackground=#00000000\n" + f"key_chat_messagePanelVoiceLock=#{text_color}\n" + f"key_chat_messagePanelVoiceLockBackground=#{accent_color}\n" + f"key_chat_messagePanelVoiceLockShadow=#00000000\n" + f"key_graySectionText=#{text_color}\n" + f"key_player_progressCachedBackground=#{text_color}\n" + f"key_sheet_other=#{text_color}\n" + f"key_sheet_scrollUp=#{accent_color}\n" + f"listSelectorSDK21={tr('f', '88')}\n" + f"location_actionActiveIcon=#{accent_color}\n" + f"location_actionBackground=#{accent_color}\n" + f"location_actionIcon=#{text_color}\n" + f"location_actionPressedBackground=#{accent_color}\n" + f"location_liveLocationProgress=#{accent_color}\n" + f"location_placeLocationBackground=#{accent_color}\n" + f"location_sendLiveLocationBackground=#{accent_color}\n" + f"location_sendLiveLocationIcon={tr('f', alpha)}\n" + f"location_sendLiveLocationText={tr('f', alpha)}\n" + f"location_sendLocationBackground=#{accent_color}\n" + f"location_sendLocationIcon={tr('f', alpha)}\n" + f"location_sendLocationText={tr('f', alpha)}\n" + f"login_progressInner=#{accent_color}\n" + f"login_progressOuter=#{accent_color}\n" + f"passport_authorizeBackground=#{accent_color}\n" + f"passport_authorizeBackgroundSelected=#{accent_color}\n" + f"passport_authorizeText={tr('f', alpha)}\n" + f"picker_badge=#{accent_color}\n" + f"picker_badgeText={tr('f', alpha)}\n" + f"picker_disabledButton=#{text_color}\n" + f"picker_enabledButton=#{accent_color}\n" + f"player_actionBarItems=#{text_color}\n" + f"player_actionBarSelector=#{accent_color}\n" + f"player_actionBarSubtitle=#{text_color}\n" + f"player_actionBarTitle=#{text_color}\n" + f"player_background={tr('f', alpha)}\n" + f"player_button=#{text_color}\n" + f"player_buttonActive=#{accent_color}\n" + f"player_progress=#{accent_color}\n" + f"player_progressBackground={tr('f', alpha)}\n" + f"player_time=#{text_color}\n" + f"premiumCoinGradient1=#{accent_color}\n" + f"premiumCoinGradient2=#{accent_color}\n" + f"premiumGradient0=#{accent_color}\n" + f"premiumGradient1=#{accent_color}\n" + f"premiumGradient2=#{accent_color}\n" + f"premiumGradient3=#{accent_color}\n" + f"premiumGradient4=#{accent_color}\n" + f"premiumGradientBackground1=#{accent_color}\n" + f"premiumGradientBackground2=#{accent_color}\n" + f"premiumGradientBackground3=#{accent_color}\n" + f"premiumGradientBackground4=#{accent_color}\n" + f"premiumGradientBackgroundOverlay=#{accent_color}\n" + f"premiumGradientBottomSheet1=#{accent_color}\n" + f"premiumGradientBottomSheet2=#{accent_color}\n" + f"premiumGradientBottomSheet3=#{accent_color}\n" + f"premiumStarGradient1=#{accent_color}\n" + f"premiumStarGradient2=#{accent_color}\n" + f"premiumStartSmallStarsColor=#{accent_color}\n" + f"premiumStartSmallStarsColor2=#{accent_color}\n" + f"profile_actionBackground=#{accent_color}\n" + f"profile_actionIcon={tr('f', alpha)}\n" + f"profile_actionPressedBackground=#{accent_color}\n" + f"profile_creatorIcon=#{accent_color}\n" + f"profile_status=#{accent_color}\n" + f"profile_tabSelectedLine=#{accent_color}\n" + f"profile_tabSelectedText=#{accent_color}\n" + f"profile_tabSelector=#{accent_color}\n" + f"profile_tabText=#{accent_color}\n" + f"profile_title=#{text_color}\n" + f"profile_verifiedBackground=#{accent_color}\n" + f"profile_verifiedCheck={tr('f', alpha)}\n" + f"progressCircle=#{accent_color}\n" + f"radioBackground=#{text_color}\n" + f"radioBackgroundChecked=#{accent_color}\n" + f"reactionStarSelector=#{accent_color}\n" + f"returnToCallBackground=#{accent_color}\n" + f"returnToCallMutedBackground=#{accent_color}\n" + f"returnToCallText={tr('f', alpha)}\n" + f"sessions_devicesImage=#{text_color}\n" + f"sharedMedia_linkPlaceholder={tr('f', alpha)}\n" + f"sharedMedia_linkPlaceholderText=#{text_color}\n" + f"sharedMedia_photoPlaceholder={tr('f', alpha)}\n" + f"sharedMedia_startStopLoadIcon=#{accent_color}\n" + f"statisticChartActiveLine=#{accent_color}\n" + f"statisticChartActivePickerChart=#{accent_color}\n" + f"statisticChartBackZoomColor=#{accent_color}\n" + f"statisticChartChevronColor=#{accent_color}\n" + f"statisticChartHintLine=#{accent_color}\n" + f"statisticChartInactivePickerChart=#{accent_color}\n" + f"statisticChartLineEmpty=#{accent_color}\n" + f"statisticChartLine_blue=#{accent_color}\n" + f"statisticChartLine_cyan=#{accent_color}\n" + f"statisticChartLine_golden=#{accent_color}\n" + f"statisticChartLine_green=#{accent_color}\n" + f"statisticChartLine_indigo=#{accent_color}\n" + f"statisticChartLine_lightblue=#{accent_color}\n" + f"statisticChartLine_lightgreen=#{accent_color}\n" + f"statisticChartLine_orange=#{accent_color}\n" + f"statisticChartLine_purple=#{accent_color}\n" + f"statisticChartLine_red=#{accent_color}\n" + f"statisticChartRipple=#{accent_color}\n" + f"statisticChartSignature=#{accent_color}\n" + f"statisticChartSignatureAlpha=#{accent_color}\n" + f"stickers_menu=#{text_color}\n" + f"stickers_menuSelector=#{text_color}\n" + f"stories_circle_closeFriends1=#{accent_color}\n" + f"stories_circle_closeFriends2=#{accent_color}\n" + f"stories_circle_dialog1=#{accent_color}\n" + f"stories_circle_dialog2=#{accent_color}\n" + f"stories_circle1=#{accent_color}\n" + f"stories_circle2=#{accent_color}\n" + f"switch2Track=#{text_color}\n" + f"switch2TrackChecked=#{accent_color}\n" + f"switchTrack=#{text_color}\n" + f"switchTrackBlue=#{accent_color}\n" + f"switchTrackBlueChecked=#{accent_color}\n" + f"switchTrackBlueSelector=#{accent_color}\n" + f"switchTrackBlueSelectorChecked=#{accent_color}\n" + f"switchTrackBlueThumb=#{accent_color}\n" + f"switchTrackBlueThumbChecked=#{accent_color}\n" + f"switchTrackChecked=#{accent_color}\n" + f"table_background=#{accent_color}\n" + f"table_border=#{text_color}\n" + f"text_RedBold=#{text_color}\n" + f"text_RedRegular=#{text_color}\n" + f"topics_unreadCounter=#{accent_color}\n" + f"topics_unreadCounterMuted=#{accent_color}\n" + f"undo_background={tr('f', '88')}\n" + f"undo_cancelColor=#{text_color}\n" + f"undo_infoColor=#{text_color}\n" + f"voipgroup_actionBar=#{accent_color}\n" + f"voipgroup_actionBarItems=#{text_color}\n" + f"voipgroup_actionBarItemsSelector=#{accent_color}\n" + f"voipgroup_actionBarUnscrolled=#{accent_color}\n" + f"voipgroup_checkMenu=#{accent_color}\n" + f"voipgroup_connectingProgress=#{accent_color}\n" + f"voipgroup_dialogBackground=#{accent_color}\n" + f"voipgroup_disabledButton=#{accent_color}\n" + f"voipgroup_disabledButtonActive=#{accent_color}\n" + f"voipgroup_disabledButtonActiveScrolled=#{accent_color}\n" + f"voipgroup_inviteMembersBackground=#{accent_color}\n" + f"voipgroup_lastSeenText=#{text_color}\n" + f"voipgroup_lastSeenTextUnscrolled=#{text_color}\n" + f"voipgroup_leaveButton=#{accent_color}\n" + f"voipgroup_leaveButtonScrolled=#{accent_color}\n" + f"voipgroup_leaveCallMenu=#{accent_color}\n" + f"voipgroup_listeningText=#{text_color}\n" + f"voipgroup_listSelector=#{accent_color}\n" + f"voipgroup_listViewBackground=#{accent_color}\n" + f"voipgroup_listViewBackgroundUnscrolled=#{accent_color}\n" + f"voipgroup_muteButton=#{accent_color}\n" + f"voipgroup_muteButton2=#{accent_color}\n" + f"voipgroup_muteButton3=#{accent_color}\n" + f"voipgroup_mutedByAdminGradient=#{accent_color}\n" + f"voipgroup_mutedByAdminGradient2=#{accent_color}\n" + f"voipgroup_mutedByAdminGradient3=#{accent_color}\n" + f"voipgroup_mutedByAdminIcon=#{text_color}\n" + f"voipgroup_mutedByAdminMuteButton=#{accent_color}\n" + f"voipgroup_mutedByAdminMuteButtonDisabled=#{accent_color}\n" + f"voipgroup_mutedIcon=#{text_color}\n" + f"voipgroup_mutedIconUnscrolled=#{text_color}\n" + f"voipgroup_nameText=#{text_color}\n" + f"voipgroup_overlayAlertGradientMuted=#{accent_color}\n" + f"voipgroup_overlayAlertGradientMuted2=#{accent_color}\n" + f"voipgroup_overlayAlertGradientUnmuted=#{accent_color}\n" + f"voipgroup_overlayAlertGradientUnmuted2=#{accent_color}\n" + f"voipgroup_overlayAlertMutedByAdmin=#{accent_color}\n" + f"voipgroup_overlayAlertMutedByAdmin2=#{accent_color}\n" + f"voipgroup_overlayBlue1=#{accent_color}\n" + f"voipgroup_overlayBlue2=#{accent_color}\n" + f"voipgroup_overlayGreen1=#{accent_color}\n" + f"voipgroup_overlayGreen2=#{accent_color}\n" + f"voipgroup_rtmpButton=#{accent_color}\n" + f"voipgroup_scrollUp=#{accent_color}\n" + f"voipgroup_searchBackground=#{accent_color}\n" + f"voipgroup_searchPlaceholder=#{text_color}\n" + f"voipgroup_searchText=#{text_color}\n" + f"voipgroup_soundButton=#{accent_color}\n" + f"voipgroup_soundButton2=#{accent_color}\n" + f"voipgroup_soundButtonActive=#{accent_color}\n" + f"voipgroup_soundButtonActive2=#{accent_color}\n" + f"voipgroup_soundButtonActive2Scrolled=#{accent_color}\n" + f"voipgroup_soundButtonActiveScrolled=#{accent_color}\n" + f"voipgroup_speakingText=#{text_color}\n" + f"voipgroup_topPanelBlue1=#{accent_color}\n" + f"voipgroup_topPanelBlue2=#{accent_color}\n" + f"voipgroup_topPanelGray=#{accent_color}\n" + f"voipgroup_topPanelGreen1=#{accent_color}\n" + f"voipgroup_topPanelGreen2=#{accent_color}\n" + f"voipgroup_unmuteButton=#{accent_color}\n" + f"voipgroup_unmuteButton2=#{accent_color}\n" + f"voipgroup_windowBackgroundWhiteInputField=#{accent_color}\n" + f"voipgroup_windowBackgroundWhiteInputFieldActivated=#{accent_color}\n" + f"windowBackgroundChecked=#{accent_color}\n" + f"windowBackgroundCheckText={tr('f', alpha)}\n" + f"windowBackgroundGray={tr('f', alpha)}\n" + f"windowBackgroundGrayShadow=#00000000\n" + f"windowBackgroundUnchecked=#{text_color}\n" + f"windowBackgroundWhite={tr('f', alpha)}\n" + f"windowBackgroundWhiteBlackText=#{text_color}\n" + f"windowBackgroundWhiteBlueButton=#{accent_color}\n" + f"windowBackgroundWhiteBlueHeader=#{accent_color}\n" + f"windowBackgroundWhiteBlueIcon=#{accent_color}\n" + f"windowBackgroundWhiteBlueText=#{accent_color}\n" + f"windowBackgroundWhiteBlueText2=#{accent_color}\n" + f"windowBackgroundWhiteBlueText3=#{accent_color}\n" + f"windowBackgroundWhiteBlueText4=#{accent_color}\n" + f"windowBackgroundWhiteBlueText5=#{accent_color}\n" + f"windowBackgroundWhiteBlueText6=#{accent_color}\n" + f"windowBackgroundWhiteBlueText7=#{accent_color}\n" + f"windowBackgroundWhiteGrayIcon=#{text_color}\n" + f"windowBackgroundWhiteGrayText=#{text_color}\n" + f"windowBackgroundWhiteGrayText2=#{text_color}\n" + f"windowBackgroundWhiteGrayText3=#{text_color}\n" + f"windowBackgroundWhiteGrayText4=#{text_color}\n" + f"windowBackgroundWhiteGrayText5=#{text_color}\n" + f"windowBackgroundWhiteGrayText6=#{text_color}\n" + f"windowBackgroundWhiteGrayText7=#{text_color}\n" + f"windowBackgroundWhiteGrayText8=#{text_color}\n" + f"windowBackgroundWhiteGreenText=#{accent_color}\n" + f"windowBackgroundWhiteGreenText2=#{accent_color}\n" + f"windowBackgroundWhiteHintText=#{text_color}\n" + f"windowBackgroundWhiteInputField=#{text_color}\n" + f"windowBackgroundWhiteInputFieldActivated=#{accent_color}\n" + f"windowBackgroundWhiteLinkSelection={tr('p', '44')}\n" + f"windowBackgroundWhiteLinkText=#{accent_color}\n" + f"windowBackgroundWhiteValueText=#{accent_color}\n" + f"chat_wallpaper=#00000000\n" + f"chat_wallpaper_gradient_to=#00000000\n" + f"key_chat_wallpaper_gradient_to2=#00000000\n" + f"key_chat_wallpaper_gradient_to3=#00000000\n" + f"dialogTextRed=#{text_color}\n" + f"dialogTextRed2=#{text_color}\n" + f"dialogTextBlue3=#{accent_color}\n" + f"dialogInputFieldText=#{text_color}\n" + f"dialogBadgeBackground=#{accent_color}\n" + f"dialogBadgeText={tr('f', alpha)}\n" + f"windowBackgroundWhiteRadius={tr('f', alpha)}\n" + f"windowBackgroundWhiteRedText=#{text_color}\n" + f"windowBackgroundWhiteRedText2=#{text_color}\n" + f"windowBackgroundWhiteRedText3=#{text_color}\n" + f"windowBackgroundWhiteRedText4=#{text_color}\n" + f"windowBackgroundWhiteRedText5=#{text_color}\n" + f"windowBackgroundWhiteRedText6=#{text_color}\n" + f"windowBackgroundWhiteGrayLine=#{text_color}\n" + f"windowBackgroundGrayLine=#{text_color}\n" + f"profile_adminIcon=#{accent_color}\n" + f"switchThumb=#{text_color}\n" + f"switchThumbChecked=#{accent_color}\n" + ) + + def process_photo(self, photo_bytes: bytes, alpha_hex: str = "ff") -> bytes: + img = Image.open(io.BytesIO(photo_bytes)) + bg, text, accent = self.extract_colors(img) + theme = self.build_theme(bg, text, accent, alpha_hex) + wallpaper = self.theme_wallpaper(img) + return theme.encode("utf-8") + b"\n\nWPS\n" + wallpaper + b"\nWPE\n" + + def theme_wallpaper(self, img: Image.Image) -> bytes: + wall_img = img.copy().convert("RGB") + if max(wall_img.size) > 1920: + scale = 1920 / max(wall_img.size) + wall_img = wall_img.resize( + (int(wall_img.size[0] * scale), int(wall_img.size[1] * scale)), + Image.LANCZOS, + ) + buf = io.BytesIO() + wall_img.save(buf, format="JPEG", quality=100, optimize=True) + return buf.getvalue() diff --git a/SunnexGB/Heroku-Modules/LiveLyrics.py b/SunnexGB/Heroku-Modules/LiveLyrics.py new file mode 100644 index 0000000..27c1ac1 --- /dev/null +++ b/SunnexGB/Heroku-Modules/LiveLyrics.py @@ -0,0 +1,370 @@ +# meta developer: @SunnexGB +# requires: aiohttp +# meta pic: https://r2.fakecrime.bio/uploads/6725e5a0-0c9e-48ed-be85-dfd857c2aa5f.jpg +# meta banner: https://r2.fakecrime.bio/uploads/6725e5a0-0c9e-48ed-be85-dfd857c2aa5f.jpg +# meta fhsdesc: Spotify, YaMusic, music, музыка, Lyrics, слова, текст, трек, песня + +__version__ = (1, 0, 0) + +from herokutl.types import Message +from .. import loader, utils +from ..types import InlineCall +import aiohttp +import asyncio +import re + +@loader.tds +class LiveLyrics(loader.Module): + """life lyrics current song""" + + strings = { + "name": "LiveLyrics", + "no_spotifymod": "💢 SpotifyMod not found,but u can install it. You can also support developer: @ke_mods", + "no_yamusic": "💢 YaMusicMod not found,but u can install it. You can also support developer: @codrago_m", + "no_auth_spotify": "⁉️ You not authorized in SpotifyMod, visit you Saved Messages.", + "no_auth_yamusic": "⁉️ You not authorized in SpotifyMod, visit you Saved Messages and setup token to continue.", + "no_spotify": "😅 Nothing is playing on Spotify.", + "no_ym": "😅 Nothing is playing on YaMusic.", + "no_lyrics": "💢 Lyrics not found for: {}", + "not_synced": "⚠️ Lyrics are not synchronized.\n\n", + "TrackEnded": "‼️ Playback ended or track changed.", + "header": "🎤 {} - {}\n\n", + "timeout": " Oopsi, looks like we've got a timeout here.", + "yamusic_installed": "YaMusic installed!", + "spotify_installed": "SpotifyMod installed!", + "song_link": "🔗 song.link", + "close": "❌ Close", + "ok": "OK", + } + + strings_ru = { + "_cls_doc": "Лайв слова текущей песни.", + "no_spotifymod": "💢 SpotifyMod не найден,но его можно установить. Вы также можете поддержать разработчика: @ke_mods", + "no_yamusic": "💢 YaMusicMod не найден, но его можно установить. Вы также можете поддержать разработчика: @codrago_m", + "no_auth_spotify": "⁉️ Вы не авторизованы в SpotifyMod. Перейдите в Избранное.", + "no_auth_yamusic": "⁉️ Вы не авторизированы в YaMusicMod, перейдите в Избранное и установите токен для продолжения работы.", + "no_spotify": "😅 В Spotify ничего не играет.", + "no_ym": "😅 В YaMusic ничего не играет.", + "no_lyrics": "💢 Текст не найден для: {}", + "not_synced": "⚠️ Текст не синхронизирован.\n\n", + "TrackEnded": "‼️ Воспроизведение завершено или трек сменился.", + "header": "🎤 {} - {}\n\n", + "timeout": " Упси, похоже кто то словил таймаут..", + "yamusic_installed": "YaMusic Установлен!", + "spotify_installed": "SpotifyMod Установлен!", + "song_link": "🔗 song.link", + "close": "❌ Закрыть", + "ok": "OK", + } + + def __init__(self): + self._active_tasks: dict = {} + self.config = loader.ModuleConfig( + loader.ConfigValue( + "emoji_current", + "🤯", + "Emoji for the current line", + validator=loader.validators.String(), + ), + loader.ConfigValue( + "dot", + "♪", + "instrumental_emoji or text", + validator=loader.validators.String(), + ), + loader.ConfigValue( + "text_lines", + "6", + "Count lines in message, with synchronized text", + validator=loader.validators.Integer(), + ), + loader.ConfigValue( + "lyrics_delay", + 0.5, + "delay in switching to a new timing sector with words", + ), + loader.ConfigValue( + "request_timeout", + 12, + "timeout value", + validator=loader.validators.Integer(), + ), + ) + + async def install_mod(self, call: InlineCall, heroku_module: str): + if heroku_module == "SpotifyMod": + download_url = "https://raw.githubusercontent.com/radiocycle/Modules/refs/heads/master/SpotifyMod.py" + module_name = "SpotifyMod" + installed_btn = self.strings["spotify_installed"] + no_auth_str = self.strings["no_auth_spotify"] + auth_command = "sauth" + else: + download_url = "https://raw.githubusercontent.com/coddrago/modules/main/YaMusic.py" + module_name = "YaMusic" + installed_btn = self.strings["yamusic_installed"] + no_auth_str = self.strings["no_auth_yamusic"] + auth_command = "yguide" + try: + m = self.lookup("loader") + await m.download_and_install(download_url) + await call.answer(installed_btn, show_alert=True) + mod = self.lookup(module_name) + if heroku_module == "SpotifyMod": + authorized = mod and mod.get("acs_tkn") + else: + authorized = mod and mod.get("__config__")["token"] + if not authorized: + await self.invoke(auth_command, " ", "me") + await call.edit( + no_auth_str, + reply_markup=[ + [ + {"text": self.strings["ok"], "callback": self.close} + ] + ], + ) + else: + await call.delete() + except Exception as e: + await call.answer(f"Error: {e}", show_alert=True) + + def close(self, call: InlineCall): + return call.delete() + + async def get_lyrics(self, artist: str, track: str): + ClearTimeSections = re.sub(r"\(.*?\)|\[.*?\]", "", track).strip() + try: + async with aiohttp.ClientSession() as session: + async with session.get( + "https://lrclib.net/api/search", + params={"track_name": ClearTimeSections, "artist_name": artist}, + timeout=aiohttp.ClientTimeout(total=self.config["request_timeout"]), + ) as resp: + if resp.status == 200: + result = await resp.json() + return result[0] if result else None + except asyncio.TimeoutError: + return {"timeout": True} + except Exception: + pass + return None + + def parse_synced(self, synced_text: str) -> list: + lines = [] + for line in synced_text.split("\n"): + m = re.search(r"\[(\d+):(\d+\.\d+)\](.*)", line) + if m: + mins, secs, text = m.groups() + lines.append({ + "time": (int(mins) * 60 + float(secs)) * 1000, + "text": text.strip(), + }) + return lines + + def build_lyrics(self, artist, track, lines, plain, progress_ms, not_synced_str): + header = self.strings["header"].format( + utils.escape_html(artist), + utils.escape_html(track), + ) + if lines: + curr_idx = 0 + for i, line in enumerate(lines): + if progress_ms >= line["time"]: + curr_idx = i + l_start = max(0, curr_idx - 1) + l_end = min(len(lines), l_start + self.config["text_lines"]) + rows = [] + for i in range(l_start, l_end): + t = lines[i]["text"] or self.config["dot"] + if i == curr_idx: + rows.append(f"{self.config['emoji_current']} {utils.escape_html(t)}") + else: + rows.append(f"{utils.escape_html(t)}") + return header + "\n".join(rows) + return header + not_synced_str + f"
{utils.escape_html((plain or '')[:4000])}
" + + def build_keyboard(self, song_url): + return [ + [ + {"text": self.strings["song_link"], "url": song_url} + ], + [ + {"text": self.strings["close"], "callback": self.close_cb} + ], + ] + + async def close_cb(self, call: InlineCall): + for track_id, task in list(self._active_tasks.items()): + task.cancel() + self._active_tasks.pop(track_id, None) + try: + await call.answer() + await call.delete() + except Exception: + pass + + async def za_loop_a(self, form, mod, track_id, artist_name, track_name, song_url, lines, plain, not_synced_str, heroku_module: str): + buffer_clipboard = "" + try: + while True: + if heroku_module == "SpotifyMod": + pb = mod.sp.current_playback() + TrackEnded = not pb or not pb.get("item") or pb["item"]["id"] != track_id + else: + pb = await mod._YaMusicMod__get_now_playing() + TrackEnded = not pb or not pb.get("track") or pb["track"]["track_id"] != track_id + if TrackEnded: + try: + await form.edit( + self.strings["TrackEnded"], + reply_markup=[[{"text": self.strings["close"], "callback": self.close_cb}]], + ) + except Exception: + pass + break + prog = pb.get("progress_ms", 0) + content = self.build_lyrics(artist_name, track_name, lines, plain, prog, not_synced_str) + if content != buffer_clipboard: + try: + await form.edit(content, reply_markup=self.build_keyboard(song_url)) + buffer_clipboard = content + except Exception: + break + if not lines: + break + await asyncio.sleep(self.config["lyrics_delay"]) + except asyncio.CancelledError: + raise + except Exception: + pass + finally: + self._active_tasks.pop(track_id, None) + + @loader.command(ru_doc="- показать синхронизированный текст песни") + async def snowlcmd(self, message: Message): + """- show synchronized lyrics for current track""" + mod = self.lookup("SpotifyMod") + if not mod: + form = await self.inline.form("⏳", message=message) + await form.edit( + self.strings["no_spotifymod"], + reply_markup=[ + [ + { + "text": self.strings["spotify_installed"], + "callback": self.install_mod, + "kwargs": {"heroku_module": "SpotifyMod"} + } + ] + ], + ) + return + if not mod.get("acs_tkn"): + await self.invoke("sauth", " ", "me") + form = await self.inline.form("⏳", message=message) + await form.edit( + self.strings["no_auth_spotify"], + reply_markup=[[{"text": self.strings["ok"], "callback": self.close}]], + ) + return + + playback = mod.sp.current_playback() + if not playback or not playback.get("item"): + return await utils.answer(message, self.strings["no_spotify"]) + track = playback["item"] + track_id = track["id"] + artist_name = track["artists"][0]["name"] + track_name = track["name"] + song_url = f"https://song.link/s/{track_id}" + + old = self._active_tasks.pop(track_id, None) + if old: + old.cancel() + + data = await self.get_lyrics(artist_name, track_name) + if data and data.get("timeout"): + return await utils.answer(message, self.strings["timeout"]) + if not data or data.get("instrumental"): + return await utils.answer( + message, + self.strings["no_lyrics"].format(f"{utils.escape_html(track_name)} - {utils.escape_html(artist_name)}"), + ) + + synced_raw = data.get("syncedLyrics") + plain = data.get("plainLyrics", "") + lines = self.parse_synced(synced_raw) if synced_raw else [] + not_synced_str = self.strings["not_synced"] + form = await self.inline.form( + text=self.build_lyrics(artist_name, track_name, lines, plain, playback.get("progress_ms", 0), not_synced_str), + message=message, + reply_markup=self.build_keyboard(song_url), + ) + + self._active_tasks[track_id] = asyncio.ensure_future( + self.za_loop_a(form, mod, track_id, artist_name, track_name, song_url, lines, plain, not_synced_str, heroku_module="SpotifyMod") + ) + + @loader.command(ru_doc="- показать синхронизированный текст песни") + async def ynowlcmd(self, message: Message): + """- show synchronized lyrics for current track""" + mod = self.lookup("YaMusic") + if not mod: + form = await self.inline.form("⏳", message=message) + await form.edit( + self.strings["no_yamusic"], + reply_markup=[ + [ + { + "text": "Install YaMusicMod", + "callback": self.install_mod, + "kwargs": {"heroku_module": "YaMusic"} + } + ] + ], + ) + return + if not mod.get("__config__")["token"]: + await self.invoke("yguide", " ", "me") + form = await self.inline.form("⏳", message=message) + await form.edit( + self.strings["no_auth_yamusic"], + reply_markup=[[{"text": self.strings["ok"], "callback": self.close}]], + ) + return + + playback = await mod._YaMusicMod__get_now_playing() + if not playback or not playback.get("track"): + return await utils.answer(message, self.strings["no_ym"]) + track = playback["track"] + track_id = track["track_id"] + artist_name = ", ".join(track["artist"]) + track_name = track["title"] + song_url = f"https://song.link/s/{track_id}" + + old = self._active_tasks.pop(track_id, None) + if old: + old.cancel() + + data = await self.get_lyrics(artist_name, track_name) + if data and data.get("timeout"): + return await utils.answer(message, self.strings["timeout"]) + if not data or data.get("instrumental"): + return await utils.answer( + message, + self.strings["no_lyrics"].format(f"{utils.escape_html(track_name)} - {utils.escape_html(artist_name)}"), + ) + + synced_raw = data.get("syncedLyrics") + plain = data.get("plainLyrics", "") + lines = self.parse_synced(synced_raw) if synced_raw else [] + not_synced_str = self.strings["not_synced"] + + form = await self.inline.form( + text=self.build_lyrics(artist_name, track_name, lines, plain, playback.get("progress_ms", 0), not_synced_str), + message=message, + reply_markup=self.build_keyboard(song_url), + ) + + self._active_tasks[track_id] = asyncio.ensure_future( + self.za_loop_a(form, mod, track_id, artist_name, track_name, song_url, lines, plain, not_synced_str, heroku_module="YaMusic") + ) \ No newline at end of file diff --git a/SunnexGB/Heroku-Modules/NoChess.py b/SunnexGB/Heroku-Modules/NoChess.py index 5a6ffc2..3c9b28c 100644 --- a/SunnexGB/Heroku-Modules/NoChess.py +++ b/SunnexGB/Heroku-Modules/NoChess.py @@ -1,25 +1,21 @@ -# requires: aiohttp pyngrok +# requires: aiohttp # meta developer: @H_SunMods # meta banner: https://r2.fakecrime.bio/uploads/965a3206-4609-4dff-beb0-6831f8b90e12.jpg # current ver -__version__ = (0, 1, 0) +__version__ = (0, 1, 1) import json import socket import asyncio import secrets import logging +import re from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit from aiohttp import ClientSession, ClientTimeout, web from herokutl.types import Message -from pyngrok import conf, ngrok from .. import loader, utils from ..inline.types import InlineCall -logging.getLogger("pyngrok").setLevel(logging.WARNING) -logging.getLogger("pyngrok.process").setLevel(logging.WARNING) -logging.getLogger("pyngrok.process.ngrok").setLevel(logging.WARNING) - html_raw = "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/refs/heads/main/Assets/NoChess/raw_assets/index.html" css_raw = "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/refs/heads/main/Assets/NoChess/raw_assets/style.css" js_raw = "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/refs/heads/main/Assets/NoChess/raw_assets/javascript.js" @@ -30,7 +26,6 @@ botfather_photo_url = "https://r2.fakecrime.bio/uploads/d3e16245-15a2-43f1-b176- class NoChess(loader.Module): """NoChess - web module that allows u to launch a web page either as a functional HTML page or as a Telegram Mini-App. This is an add-on for Chess module by @nullmod""" - # я пытался кароче сделать тут перевод делая реплейсы в зависимости от стрингов,но это не работает,поэтому да strings = { "name": "NoChess", "starting": "( ノ・ェ・ )ノ Starting NoChess...", @@ -38,8 +33,7 @@ class NoChess(loader.Module): "already_running": "ʕᵕᴥᵕʔ NoChess is already running", "stopped": "・゚・(。>д<。)・゚・ NoChess stopped", "not_running": "(✿╹◡╹) NoChess is not running", - "ngrok_missing": "Set a ngrok_token", - "ngrok_error": "Ngrok start error: {}", + "tunnel_error": "Serveo tunnel error: {}", "asset_read_error": "Failed to load web assets: {}", "open_button": "Open mini-app", "stop_button": "Stop", @@ -59,8 +53,7 @@ class NoChess(loader.Module): "already_running": "ʕᵕᴥᵕʔ NoChess уже запущен", "stopped": "・゚・(。>д<。)・゚・ NoChess остановлен", "not_running": "(✿╹◡╹) NoChess не запущен", - "ngrok_missing": "Укажи ngrok_token", - "ngrok_error": "Ошибка запуска ngrok: {}", + "tunnel_error": "Ошибка туннеля Serveo: {}", "asset_read_error": "Не удалось загрузить веб-ассеты: {}", "open_button": "Открыть мини-приложение", "stop_button": "Остановить", @@ -73,6 +66,146 @@ class NoChess(loader.Module): "not_supported_platform": "(┬┬_┬┬) К сожалению, на эту платформу невозможно установить этот модуль.\n\n(〜^∇^)〜 Это не ошибка, пожалуйста, не обращайтесь в поддержку." } + strings_de = { + "_cls_doc": "NoChess - Webmodul zum Starten einer Webseite als HTML oder Telegram Mini-App. Erweiterung für Chess von @nullmod", + "starting": "( ノ・ェ・ )ノ Starte NoChess...", + "online": "(*˘︶˘*) NoChess läuft", + "already_running": "ʕᵕᴥᵕʔ NoChess läuft bereits", + "stopped": "・゚・(。>д<。)・゚・ NoChess gestoppt", + "not_running": "(✿╹◡╹) NoChess läuft nicht", + "tunnel_error": "Serveo-Tunnel-Fehler: {}", + "asset_read_error": "Fehler beim Laden der Web-Assets: {}", + "open_button": "Mini-App öffnen", + "stop_button": "Stopp", + "about_text": "Wichtig zu lesen:\nManchmal startet der Server nicht, weil zu viele Prozesse laufen. cma richtet die App über eine Vorlage ein, musst einige Web-App-Einstellungen selbst setzen.\nUnd außerdem:\n 1. Erster Start beginnt direkt mit einem Seitenlink, nicht als Web-App\n 2. Verwende nochess und dann cma zum Einrichten\n 3. Danach Prozess neustarten mit nochess -kill und nochmal nochess\nPre-Alpha, später 1.0.0 falls die Leute die Idee mögen.", + "cma_start": "( ノ・ェ・ )ノ Erstelle Mini-App im BotFather...", + "cma_need_url": "Setze zuerst die Mini-App-Web-URL oder führe .nochess aus.", + "cma_done": "(*˘︶˘*) Fertig.", + "cma_error": "Fehler: {}", + "RuntimeError": "Inline-Bot-Benutzername nicht gefunden", + "not_supported_platform": "(┬┬_┬┬) Leider unmöglich, dieses Modul auf dieser Plattform zu installieren.\n\n(〜^∇^)〜 Kein Fehler, Support nicht kontaktieren." + } + + strings_ua = { + "_cls_doc": "NoChess - Веб модуль для запуску веб-сторінки як HTML або Telegram Mini-App. Доповнення до Chess від @nullmod", + "starting": "( ノ・ェ・ )ノ Запуск NoChess...", + "online": "(*˘︶˘*) NoChess запущено", + "already_running": "ʕᵕᴥᵕʔ NoChess вже запущено", + "stopped": "・゚・(。>д<。)・゚・ NoChess зупинено", + "not_running": "(✿╹◡╹) NoChess не запущено", + "tunnel_error": "Помилка тунелю Serveo: {}", + "asset_read_error": "Не вдалося завантажити веб-ассети: {}", + "open_button": "Відкрити міні-застосунок", + "stop_button": "Зупинити", + "about_text": "Важливо прочитати:\nІноді сервер не запускається через забагато процесів. cma налаштовує за шаблоном — налаштуйте веб-застосунок самостійно.\nА ще:\n 1. Перший запуск — одразу посилання на сайт, не веб-застосунок\n 2. Використовуйте nochess, потім cma для налаштування\n 3. Перезапустіть процес: nochess -kill, потім nochess\nПре-альфа, пізніше 1.0.0 якщо ідея сподобається.", + "cma_start": "( ノ・ェ・ )ノ Створюю міні-застосунок через BotFather...", + "cma_need_url": "Спочатку вкажи URL або запусти .nochess", + "cma_done": "(*˘︶˘*) Готово.", + "cma_error": "Помилка: {}", + "RuntimeError": "юзернейм інлайн-бота не знайдено", + "not_supported_platform": "(┬┬_┬┬) На жаль, неможливо встановити цей модуль на цю платформу.\n\n(〜^∇^)〜 Це не помилка, не звертайтесь у підтримку." + } + + strings_jp = { + "_cls_doc": "NoChess - HTMLまたはTelegram Mini-Appとしてページを起動するモジュール。Chess(@nullmod)のアドオン", + "starting": "( ノ・ェ・ )ノ NoChessを起動中...", + "online": "(*˘︶˘*) NoChessは実行中です", + "already_running": "ʕᵕᴥᵕʔ NoChessはすでに実行中です", + "stopped": "・゚・(。>д<。)・゚・ NoChessを停止しました", + "not_running": "(✿╹◡╹) NoChessは実行されていません", + "tunnel_error": "Serveoトンネルエラー: {}", + "asset_read_error": "Webアセットの読み込みに失敗: {}", + "open_button": "ミニアプリを開く", + "stop_button": "停止", + "about_text": "重要なお知らせ:\nプロセスが多すぎてサーバーが起動しないことがあります。cmaはテンプレートでアプリをセットアップしますが歪なので自分で設定してください。\nさらに:\n 1. 最初の起動はWebアプリではなくサイトリンクで開始\n 2. nochessを使い、cmaで設定\n 3. nochess -killしてから再度nochess\nプレアルファ版、後で1.0.0に変更予定。", + "cma_start": "( ノ・ェ・ )ノ BotFatherでミニアプリを作成中...", + "cma_need_url": "最初にミニアプリのURLを設定するか、.nochessを実行してください", + "cma_done": "(*˘︶˘*) 完了。", + "cma_error": "エラー: {}", + "RuntimeError": "インラインボットのユーザー名が見つかりません", + "not_supported_platform": "(┬┬_┬┬) このプラットフォームにはインストールできません。\n\n(〜^∇^)〜 エラーではありません。サポートに連絡しないでください。" + } + + strings_neofit = { + "_cls_doc": "NoChess — web module fer launchin' a page as HTML or Telegram Mini-App. Add-on fer Chess by @nullmod", + "starting": "( ノ・ェ・ )ノ Spinnin' up NoChess...", + "online": "(*˘︶˘*) NoChess is live, fam", + "already_running": "ʕᵕᴥᵕʔ NoChess already cookin'", + "stopped": "・゚・(。>д<。)・゚・ NoChess iced", + "not_running": "(✿╹◡╹) NoChess ain't up", + "tunnel_error": "Serveo tunnel bricked: {}", + "asset_read_error": "Couldn't snag web assets: {}", + "open_button": "Pop the mini-app", + "stop_button": "Cut it", + "about_text": "RTFM:\nBox won't lift sometimes 'cause too many procs — just reboot. cma uses a jank template so tweak config yerself.\nAlso:\n 1. First run = site link, not web app\n 2. Hit nochess then cma to rig it\n 3. Bounce the proc with nochess -kill + nochess\nPre-alpha slop, gonna bump to 1.0.0 if peeps vibe.", + "cma_start": "( ノ・ェ・ )ノ Forgin' mini app via BotFather...", + "cma_need_url": "Drop a mini-app URL first or run .nochess", + "cma_done": "(*˘︶˘*) Ship it.", + "cma_error": "L + ratio: {}", + "RuntimeError": "inline bot handle MIA", + "not_supported_platform": "(┬┬_┬┬) No shot installin' here.\n\n(〜^∇^)〜 Not a bug, don't ping support." + } + + strings_tiktok = { + "_cls_doc": "NoChess — веб-модуль запускает страничку как HTML или мини-апп в телеге. Аддон к Chess от @nullmod", + "starting": "( ノ・ェ・ )ノ Газуем NoChess...", + "online": "(*˘︶˘*) NoChess на стиле", + "already_running": "ʕᵕᴥᵕʔ NoChess уже тащит", + "stopped": "・゚・(。>д<。)・゚・ NoChess слился", + "not_running": "(✿╹◡╹) NoChess не в теме", + "tunnel_error": "Serveo тунель крашнулся: {}", + "asset_read_error": "Не смог забрать ассеты: {}", + "open_button": "Открыть мини-апп", + "stop_button": "Стопэ", + "about_text": "Читни сюда:\nБывает серв не поднимается — процов дофига, ребутаю. cma сетапит криво, конфиг руками.\nИ ещё:\n 1. Первый запуск — сразу ссылка на сайт, не апп\n 2. Юзай nochess, потом cma\n 3. Дропни через nochess -kill и снова nochess\nПре-альфа дичь, потом 1.0.0 если залетит.", + "cma_start": "( ノ・ェ・ )ノ Делаю мини-апп через BotFather...", + "cma_need_url": "Сначала кинь URL или жмякни .nochess", + "cma_done": "(*˘︶˘*) Запилил.", + "cma_error": "Ой фейл: {}", + "RuntimeError": "юз бота не нашли", + "not_supported_platform": "(┬┬_┬┬) Сорян, на эту платформу модуль не встанет.\n\n(〜^∇^)〜 Не ошибка, в саппорт не пиши." + } + + strings_leet = { + "_cls_doc": "NoChess — w3b m0dul3 t0 l4unch p4g3 4s HTML 0r T3l3gr4m M1n1-4pp. 4dd-0n f0r Ch355 by @nullm0d", + "starting": "( ノ・ェ・ )ノ B00t1ng N0Ch355...", + "online": "(*˘︶˘*) N0Ch355 15 1n th3 m4tr1x", + "already_running": "ʕᵕᴥᵕʔ N0Ch355 4lr34dy 0n", + "stopped": "・゚・(。>д<。)・゚・ N0Ch355 t3rm1n4t3d", + "not_running": "(✿╹◡╹) N0Ch355 0ffl1n3", + "tunnel_error": "S3rv30 tunn3l f41l: {}", + "asset_read_error": "F41l3d t0 f3tch w3b 4553t5: {}", + "open_button": "0p3n m1n1-4pp", + "stop_button": "K1ll", + "about_text": "R34D TH15:\nB0x w0n't l1ft cuz 2 m4ny pr0c5 — r3b00t. cm4 j4nk t3mpl4t3, c0nf1g m4nu4lly.\n4l50:\n 1. F1r5t run = 51t3 l1nk, n0t w3b 4pp\n 2. U53 n0ch355 + cm4\n 3. B0unc3 w1th n0ch355 -k1ll + n0ch355\nPr3-4lph4, bump1n t0 1.0.0 1f p33p5 v1b3.", + "cma_start": "( ノ・ェ・ )ノ C0njur1n9 m1n1 4pp v14 B0tF4th3r...", + "cma_need_url": "Dr0p 4 URL f1r5t 0r run .n0ch355", + "cma_done": "(*˘︶˘*) 5h1pp3d.", + "cma_error": "F41l: {}", + "RuntimeError": "1nl1n3 b0t h4ndl3 n0t f0und", + "not_supported_platform": "(┬┬_┬┬) N0 5h0t 1n5t4ll1n' h3r3.\n\n(〜^∇^)〜 N0t 4 bu9, d0n't p1n9 5upp0rt." + } + + strings_uwu = { + "_cls_doc": "NoChess — web moduwe tuwu waunch a page as HTML owr Tewegwam Minyi-App. Add-on fowr Chess by @nuwwmod~", + "starting": "( ノ・ェ・ )ノ Spinning up NoChess-chan...", + "online": "(*˘︶˘*) NoChess is wunning, nyaa~", + "already_running": "ʕᵕᴥᵕʔ NoChess awweady wunning, hehe", + "stopped": "・゚・(。>д<。)・゚・ NoChess went bye-bye", + "not_running": "(✿╹◡╹) NoChess is sweepy...", + "tunnel_error": "Serveo tunnew-bun oopsie: {}", + "asset_read_error": "Couwdn't fetch the pwetty assets: {}", + "open_button": "Open minyi-app~", + "stop_button": "Stahp pwease", + "about_text": "Pwease wead cawefuwwy:\nSewvew won't wake up cuz too many pwocesses. cma setups fwom wonky tempwate, tweak config yuwsewf.\nAwso:\n 1. Fiwst waunch = site wink, not web app\n 2. Use nochess, den cma\n 3. Westawt wiff nochess -kill + nochess\nPwe-awpha, watew 1.0.0 if peopwe wike~. ", + "cma_start": "( ノ・ェ・ )ノ Making minyi-app in BotFather-chan...", + "cma_need_url": "Set URL fiwst owr wun .nochess, pwease~", + "cma_done": "(*˘︶˘*) Aww done, nya!", + "cma_error": "Oopsie woopsie: {}", + "RuntimeError": "inyine bot usewnyame nyot found", + "not_supported_platform": "(┬┬_┬┬) Unfowtunyatewy, can't instaww hewe.\n\n(〜^∇^)〜 Nyot an ewwow, don't contact suppowt." + } + async def client_ready(self): platform = utils.get_named_platform() if platform in ("HikkaHost"): @@ -81,10 +214,10 @@ class NoChess(loader.Module): def __init__(self): self.config = loader.ModuleConfig( loader.ConfigValue( - "ngrok_token", - None, - "Token from ngrok.com | Токен полученый на ngrok.com", - validator=loader.validators.Hidden(), + "serveo_subdomain", + "", + "Custom serveo subdomain (leave empty for random) | Кастомный поддомен serveo (оставь пустым для случайного)", + validator=loader.validators.String(), ), loader.ConfigValue( "mini_app_url", @@ -117,7 +250,7 @@ class NoChess(loader.Module): ), loader.ConfigValue( "result_win", - "#00BE16", + "#00BE16", "Winner color | Блок цвета победителя", validator=loader.validators.String() ), @@ -146,6 +279,11 @@ class NoChess(loader.Module): self.access_token = None self.games_cache = [] self.games_dump = "" + self._serveo_proc = None + self._assets_lock = asyncio.Lock() + self._assets_html = None + self._assets_css = None + self._assets_js = None def theme_config_dict(self): return { @@ -238,10 +376,18 @@ class NoChess(loader.Module): return await response.text() async def load_web_assets(self): - html = await self.read_remote_asset(html_raw) - css = await self.read_remote_asset(css_raw) - js = await self.read_remote_asset(js_raw) - return html, css, js + async with self._assets_lock: + if self._assets_html is not None: + return self._assets_html, self._assets_css, self._assets_js + html, css, js = await asyncio.gather( + self.read_remote_asset(html_raw), + self.read_remote_asset(css_raw), + self.read_remote_asset(js_raw), + ) + self._assets_html = html + self._assets_css = css + self._assets_js = js + return html, css, js def localication_script(self): return ( @@ -304,18 +450,90 @@ class NoChess(loader.Module): return web.json_response({"error": "Unauthorized"}, status=401) return web.json_response(await self.get_me_json()) + async def _kill_serveo(self): + proc = self._serveo_proc + if proc and proc.returncode is None: + try: + proc.terminate() + try: + await asyncio.wait_for(proc.wait(), timeout=3) + except asyncio.TimeoutError: + proc.kill() + await proc.wait() + except ProcessLookupError: + pass + self._serveo_proc = None + async def stop_server(self): was_running = bool(self.runner) - try: - ngrok.kill() - except Exception: - pass + await self._kill_serveo() if self.runner: await self.runner.cleanup() self.runner = None self.tunnel_url = None return was_running + @staticmethod + def _strip_ansi(s): + return re.sub(r'\x1b\[[0-?]*[ -/]*[@-~]', '', s) + + async def _start_serveo(self, port): + subdomain = (self.config["serveo_subdomain"] or "").strip() + if subdomain: + remote = f"{subdomain}:80:localhost:{port}" + else: + remote = f"80:localhost:{port}" + + cmd = [ + "ssh", "-T", + "-o", "StrictHostKeyChecking=accept-new", + "-o", "ServerAliveInterval=60", + "-o", "ExitOnForwardFailure=yes", + "-o", "ConnectTimeout=15", + "-R", remote, + "serveo.net", + ] + + proc = await asyncio.create_subprocess_exec( + *cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.STDOUT, + ) + + self._serveo_proc = proc + + url = None + deadline = asyncio.get_event_loop().time() + 20 + buf = b"" + while asyncio.get_event_loop().time() < deadline: + try: + line = await asyncio.wait_for(proc.stdout.readline(), timeout=0.5) + except asyncio.TimeoutError: + if proc.returncode is not None: + buf_str = self._strip_ansi(buf.decode(errors="replace")) + raise RuntimeError(f"SSH exited {proc.returncode}: {buf_str}") + continue + + if not line: + if proc.returncode is not None: + buf_str = self._strip_ansi(buf.decode(errors="replace")) + raise RuntimeError(f"SSH exited {proc.returncode}: {buf_str}") + await asyncio.sleep(0.5) + continue + + buf += line + line_str = self._strip_ansi(line.decode(errors="replace")) + match = re.search(r'https?://[\w.-]+\.serveo(?:usercontent)?\.(?:net|com)', line_str) + if match: + url = match.group(0).rstrip("/") + break + + if not url: + buf_str = self._strip_ansi(buf.decode(errors="replace")) + raise RuntimeError(f"No serveo URL received: {buf_str}") + + return url + async def send_form(self, message, url): await self.inline.form( self.strings["online"], @@ -340,7 +558,16 @@ class NoChess(loader.Module): except Exception: pass - @loader.command(ru_doc="[-kill] Вызываь веб интерфейс для просмотра партии") + @loader.command( + ru_doc="[-kill] Вызвать веб интерфейс для просмотра партии", + de_doc="[-kill] Webinterface zum Anzeigen der Partie aufrufen", + ua_doc="[-kill] Викликати веб інтерфейс для перегляду партії", + jp_doc="[-kill] チェスゲームを表示するウェブインターフェースを呼び出す", + neofit_doc="[-kill] Yeet the web ui 2 render a game", + tiktok_doc="[-kill] Открыть веб-вьюху шахмат, no 🧢", + leet_doc="[-kill] C4ll w3b 1nt3rf4c3 f0r ch355 v13w", + uwu_doc="[-kiww] Caww web intewface fow chess viewie~", + ) async def nochess(self, message: Message): """[-kill] Call web interface to view chess game""" try: @@ -349,7 +576,7 @@ class NoChess(loader.Module): await self.stop_server() return await utils.answer( message, - self.strings["ngrok_error"].format(utils.escape_html(str(error))), + self.strings["tunnel_error"].format(utils.escape_html(str(error))), ) async def nochess_args(self, message: Message): @@ -369,8 +596,6 @@ class NoChess(loader.Module): if access: await self.send_form(message, access) return - if not self.config["ngrok_token"] and (not mini_url or is_tg_direct): - return await utils.answer(message, self.strings["ngrok_missing"]) await self.refresh_games_cache() await utils.answer(message, self.strings["starting"]) self.ensure_access_token() @@ -386,17 +611,12 @@ class NoChess(loader.Module): await self.runner.setup() await web.TCPSite(self.runner, "127.0.0.1", port).start() try: - if self.config["ngrok_token"]: - conf.get_default().auth_token = self.config["ngrok_token"] - tunnel = ngrok.connect(port) - self.tunnel_url = tunnel.public_url.rstrip("/") - else: - self.tunnel_url = mini_url + self.tunnel_url = await self._start_serveo(port) except Exception as error: await self.stop_server() return await utils.answer( message, - self.strings["ngrok_error"].format(utils.escape_html(str(error))), + self.strings["tunnel_error"].format(utils.escape_html(str(error))), ) if is_tg_direct: access_url = mini_url @@ -405,7 +625,16 @@ class NoChess(loader.Module): access_url = f"{base}/?token={self.access_token}" if base and self.access_token else base await self.send_form(message, access_url) - @loader.command(ru_doc="Создает и настраивает эпку") + @loader.command( + ru_doc="Создаёт и настраивает мини-приложение через BotFather", + de_doc="Erstellt und konfiguriert Mini-App via BotFather", + ua_doc="Створює і налаштовує міні-застосунок через BotFather", + jp_doc="BotFather経由でミニアプリを作成・設定します", + neofit_doc="Sp00n-feed BotFather 2 forge ya mini app", + tiktok_doc="Делает мини-апп через BotFather, изи", + leet_doc="Cr34t3 & c0nf19 m1n1-4pp v14 B0tF4th3r", + uwu_doc="Cweates & configuwes mini-app via BotFathew~", + ) async def cma(self, message: Message): """Create and setup mini-app""" raw_args = (utils.get_args_raw(message) or "").strip() @@ -479,7 +708,16 @@ class NoChess(loader.Module): except Exception as error: await utils.answer(message, self.strings["cma_error"].format(utils.escape_html(str(error)))) - @loader.command(ru_doc="ВАЖНО К ПРОЧТЕНИЮ") + @loader.command( + ru_doc="ВАЖНО К ПРОЧТЕНИЮ", + de_doc="WICHTIG ZU LESEN", + ua_doc="ВАЖЛИВО ДО ПРОЧИТАННЯ", + jp_doc="重要なお知らせ", + neofit_doc="RTFM BRO", + tiktok_doc="ЧИТНИ СЮДА", + leet_doc="R34D TH15", + uwu_doc="WEAD ME!!", + ) async def about(self, message: Message): """IMPORTANT READING""" await utils.answer(message, self.strings["about_text"]) diff --git a/SunnexGB/Heroku-Modules/SpotifyLyrics.py b/SunnexGB/Heroku-Modules/SpotifyLyrics.py deleted file mode 100644 index 8c9fd4d..0000000 --- a/SunnexGB/Heroku-Modules/SpotifyLyrics.py +++ /dev/null @@ -1,285 +0,0 @@ -# meta developer: @SunnexGB -# requires: aiohttp -# meta pic: https://r2.fakecrime.bio/uploads/f49a9294-36ad-4fc4-801f-48cb049111d6.jpg -# meta banner: https://r2.fakecrime.bio/uploads/f49a9294-36ad-4fc4-801f-48cb049111d6.jpg -# meta fhsdesc: Spotify, music, музыка, спотифай,Lyrics, слова, текст, трек, песня -# все же я не знаю трек или сонг, так что пусть будет трек, а не сонг потому что интуитивнее поняттнее,наверное? -# крутой баннер да? -#current version -__version__ = (1, 1, 2) - -from herokutl.types import Message -from .. import loader, utils -from ..types import InlineCall -import aiohttp -import asyncio -import re - - -@loader.tds -class SpotifyLyrics(loader.Module): - """life lyrics current song""" - - strings = { - "name": "SpotifyLyrics", - "no_spotifymod": "💢 SpotifyMod not found,but u can install it. You can also support developer: @ke_mods", - "no_auth": "⁉️ You not authorized in SpotifyMod, visit you Saved Messages.", - "no_spotify": "😅 Nothing is playing on Spotify.", - "no_lyrics": "💢 Lyrics not found for: {}", - "not_synced": "⚠️ Lyrics are not synchronized.\n\n", - "finished": "‼️ Playback ended or track changed.", - "header": "🎤 {} - {}\n\n", - "timeout": " Oopsi, looks like we've got a timeout here.", - } - - strings_ru = { - "cls_doc": "Лайв слова текущей песни.", - "no_spotifymod": "💢 SpotifyMod не найден,но его можно установить. Вы также можете поддержать разработчика: @ke_mods", - "no_auth": "⁉️ Вы не авторизированы в SpotifyMod, перейдите в Избранное.", - "no_spotify": "😅 В Spotify ничего не играет.", - "no_lyrics": "💢 Текст не найден для: {}", - "not_synced": "⚠️ Текст не синхронизирован.\n\n", - "finished": "‼️ Воспроизведение завершено или трек сменился.", - "header": "🎤 {} - {}\n\n", - "timeout": " Упси, похоже кто то словил таймаут..", - } - - def __init__(self): - self._active_tasks: dict = {} - self.config = loader.ModuleConfig( - loader.ConfigValue( - "emoji_current", - "🤯", - "Emoji for current line", - validator=loader.validators.String(), - ), - loader.ConfigValue( - "dot", - "♪", - "instrumental_emoji or text", - validator=loader.validators.String(), - ), - loader.ConfigValue( - "lyrics_delay", - 0.5, - "delay in switching to a new timing sector with words", - ), - loader.ConfigValue( - "request_timeout", - 12, - "timeout value", - ), - ) - - async def install_spotifymod(self, call: InlineCall): - mod_url = "https://raw.githubusercontent.com/radiocycle/Modules/refs/heads/master/SpotifyMod.py" - try: - m = self.lookup("Modules") or self.lookup("loader") - await m.download_and_install(mod_url) - await call.answer("SpotifyMod installed!", show_alert=True) - mod = self.lookup("SpotifyMod") - acs_tkn = mod.get("acs_tkn") if mod else None - if not acs_tkn: - await self.invoke("sauth", " ", "me") - await call.edit( - self.strings("no_auth"), - reply_markup=[[{"text": "Хорошо", "callback": self.close}]], - ) - else: - await call.delete() - except Exception as e: - await call.answer(f"Error! Check logs.\n{e}", show_alert=True) - - def close(self, call: InlineCall): - return call.delete() - - async def _get_lyrics(self, artist: str, track: str): - clean_track = re.sub(r"\(.*?\)|\[.*?\]", "", track).strip() - try: - async with aiohttp.ClientSession() as session: - async with session.get( - "https://lrclib.net/api/search", - params={"track_name": clean_track, "artist_name": artist}, - timeout=aiohttp.ClientTimeout(total=(self.config["request_timeout"])), - ) as resp: - if resp.status == 200: - res = await resp.json() - return res[0] if res else None - except asyncio.TimeoutError: - return {"timeout": True} - except Exception: - pass - return None - - def _parse_synced(self, synced_text: str) -> list: - lines = [] - for line in synced_text.split("\n"): - m = re.search(r"\[(\d+):(\d+\.\d+)\](.*)", line) - if m: - mins, secs, text = m.groups() - lines.append({ - "time": (int(mins) * 60 + float(secs)) * 1000, - "text": text.strip(), - }) - return lines - - def _build_content(self, artist, track, lines, plain, progress_ms, not_synced_str): - header = self.strings("header").format( - utils.escape_html(artist), - utils.escape_html(track), - ) - if lines: - curr_idx = 0 - for i, line in enumerate(lines): - if progress_ms >= line["time"]: - curr_idx = i - win_start = max(0, curr_idx - 1) - win_end = min(len(lines), curr_idx + 6) - rows = [] - for i in range(win_start, win_end): - t = lines[i]["text"] or self.config["dot"] - if i == curr_idx: - rows.append( - f"{self.config['emoji_current']} {utils.escape_html(t)}" - ) - else: - rows.append(f"{utils.escape_html(t)}") - return header + "\n".join(rows) - else: - return header + not_synced_str + f"
{utils.escape_html((plain or '')[:4000])}
" - - def _markup(self, song_url): - return [ - [{"text": "🔗 song.link", "url": song_url}], - [{"text": "❌ Close", "callback": self._close_cb}], - ] - - async def _close_cb(self, call): - for track_id, task in list(self._active_tasks.items()): - task.cancel() - self._active_tasks.pop(track_id, None) - try: - await call.answer() - await call.delete() - except Exception: - pass - - async def run_loop(self, form, mod, track_id, artist_name, track_name, song_url, lines, plain, not_synced_str): - last_display = "" - try: - while True: - pb = mod.sp.current_playback() - if not pb or not pb.get("item") or pb["item"]["id"] != track_id: - try: - await form.edit( - self.strings("finished"), - reply_markup=[[{"text": "❌ Close", "callback": self._close_cb}]], - ) - except Exception: - pass - break - - prog = pb.get("progress_ms", 0) - content = self._build_content( - artist_name, track_name, lines, plain, prog, not_synced_str - ) - - if content != last_display: - try: - await form.edit(content, reply_markup=self._markup(song_url)) - last_display = content - except Exception: - break - - if not lines: - break - - await asyncio.sleep(self.config["lyrics_delay"]) - - except asyncio.CancelledError: - raise - except Exception: - pass - finally: - self._active_tasks.pop(track_id, None) - - @loader.command(ru_doc="- показать синхронизированный текст песни") - async def snowlcmd(self, message: Message): - """- show synchronized lyrics for current Spotify track""" - mod = self.lookup("SpotifyMod") - if not mod: - form = await self.inline.form("⏳", message=message) - await form.edit( - self.strings("no_spotifymod"), - reply_markup=[[{"text": "Install SpotifyMod", "callback": self.install_spotifymod}]], - ) - return - - acs_tkn = mod.get("acs_tkn") - if not acs_tkn: - await self.invoke("sauth", " ", "me") - form = await self.inline.form("⏳", message=message) - await form.edit( - self.strings("no_auth"), - reply_markup=[[{"text": "Хорошо", "callback": self.close}]], - ) - return - - playback = mod.sp.current_playback() - if not playback or not playback.get("item"): - return await utils.answer(message, self.strings("no_spotify")) - - track = playback["item"] - track_id = track["id"] - artist_name = track["artists"][0]["name"] - track_name = track["name"] - song_url = f"https://song.link/s/{track_id}" - - old = self._active_tasks.pop(track_id, None) - if old: - old.cancel() - - data = await self._get_lyrics(artist_name, track_name) - if data and data.get("timeout"): - return utils.answer( - message, - self.strings["timeout"] - ) - if not data or data.get("instrumental"): - track_and_artist = f"{utils.escape_html(track_name)} - {utils.escape_html(artist_name)}" - return await utils.answer( - message, - self.strings("no_lyrics").format(track_and_artist), - ) - - synced_raw = data.get("syncedLyrics") - plain = data.get("plainLyrics", "") - - lines = self._parse_synced(synced_raw) if synced_raw else [] - not_synced_str = self.strings("not_synced") - - prog = playback.get("progress_ms", 0) - initial_content = self._build_content( - artist_name, track_name, lines, plain, prog, not_synced_str - ) - - form = await self.inline.form( - text=initial_content, - message=message, - reply_markup=self._markup(song_url), - ) - - task = asyncio.ensure_future( - self.run_loop( - form=form, - mod=mod, - track_id=track_id, - artist_name=artist_name, - track_name=track_name, - song_url=song_url, - lines=lines, - plain=plain, - not_synced_str=not_synced_str, - ) - ) - self._active_tasks[track_id] = task \ No newline at end of file diff --git a/SunnexGB/Heroku-Modules/YandexLyrics.py b/SunnexGB/Heroku-Modules/YandexLyrics.py deleted file mode 100644 index d9d31aa..0000000 --- a/SunnexGB/Heroku-Modules/YandexLyrics.py +++ /dev/null @@ -1,352 +0,0 @@ -# meta developer: @SunnexGB -# requires: aiohttp -# meta pic: https://r2.fakecrime.bio/uploads/ab42b5e2-91f1-4ed1-8002-51b3184e3839.jpg -# meta banner: https://r2.fakecrime.bio/uploads/ab42b5e2-91f1-4ed1-8002-51b3184e3839.jpg -# meta fhsdesc: YaMusic, music, музыка, яндекс музыка,Lyrics, слова, текст, трек, песня -# все же я не знаю трек или сонг, так что пусть будет трек, а не сонг потому что интуитивнее поняттнее,наверное? -# крутой баннер да? -#current version -__version__ = (1, 1, 2) - -from herokutl.types import Message -from .. import loader, utils -from ..types import InlineCall -import aiohttp -import asyncio -import re - - -@loader.tds -class YandexLyrics(loader.Module): - """life lyrics current song""" - - strings = { - "name": "YandexLyrics", - "no_YaMusicMod": "💢 YaMusicMod not found,but u can install it. You can also support developer: @codrago_m", - "no_auth": "⁉️ You not authorized in SpotifyMod, visit you Saved Messages and setup token to continue.", - "no_ym": "😅 Nothing is playing on YaMusic.", - "no_lyrics": "💢 Lyrics not found for: {}", - "not_synced": "⚠️ Lyrics are not synchronized.\n\n", - "finished": "‼️ Playback ended or track changed.", - "header": "🎤 {} - {}\n\n", - "timeout": " Oopsi, looks like we've got a timeout here.", - - } - - strings_ru = { - "cls_doc": "Лайв слова текущей песни.", - "no_YaMusicMod": "💢 YaMusicMod не найден,но его можно установить. Вы также можете поддержать разработчика: @codrago_m", - "no_auth": "⁉️ Вы не авторизированы в YaMusicMod, перейдите в Избранное и установите токен для продолжения работы.", - "no_ym": "😅 В YaMusic ничего не играет.", - "no_lyrics": "💢 Текст не найден для: {}", - "not_synced": "⚠️ Текст не синхронизирован.\n\n", - "finished": "‼️ Воспроизведение завершено или трек сменился.", - "header": "🎤 {} - {}\n\n", - "timeout": " Упси, похоже кто то словил таймаут..", - } - - - def __init__(self): - self._active_tasks: dict = {} - self.config = loader.ModuleConfig( - loader.ConfigValue( - "emoji_current", - "🤯", - "Emoji for current line", - validator=loader.validators.String(), - ), - loader.ConfigValue( - "dot", - "♪", - "instrumental_emoji or text", - validator=loader.validators.String(), - ), - loader.ConfigValue( - "lyrics_delay", - 0.5, - "delay in switching to a new timing sector with words", - ), - loader.ConfigValue( - "request_timeout", - 12, - "timeout value", - ), - ) - - async def install_yamusic(self, call: InlineCall): - mod_url = "https://raw.githubusercontent.com/coddrago/modules/main/YaMusic.py" - try: - m = self.lookup("Modules") or self.lookup("loader") - await m.download_and_install(mod_url) - await call.answer("YaMusicMod installed!", show_alert=True) - mod = self.lookup("YaMusicMod") - acs_tkn = mod.get("__config__")["token"] if mod else "****" - if not acs_tkn: - await self.invoke("yguide", " ", "me") - await call.edit( - self.strings("no_auth"), - reply_markup=[[{"text": "Хорошо", "callback": self.close}]], - ) - else: - await call.delete() - except Exception as e: - await call.answer(f"Error! Check logs.\n{e}", show_alert=True) - - def close(self, call: InlineCall): - return call.delete() - - async def _get_lyrics(self, artist: str, track: str): - clean_track = re.sub(r"\(.*?\)|\[.*?\]", "", track).strip() - try: - async with aiohttp.ClientSession() as session: - async with session.get( - "https://lrclib.net/api/search", - params={"track_name": clean_track, "artist_name": artist}, - timeout=aiohttp.ClientTimeout(total=(self.config["request_timeout"])), - ) as resp: - if resp.status == 200: - res = await resp.json() - return res[0] if res else None - except asyncio.TimeoutError: - return {"timeout": True} - except Exception: - pass - return None - - def _parse_synced(self, synced_text: str) -> list: - lines = [] - for line in synced_text.split("\n"): - m = re.search(r"\[(\d+):(\d+\.\d+)\](.*)", line) - if m: - mins, secs, text = m.groups() - lines.append({ - "time": (int(mins) * 60 + float(secs)) * 1000, - "text": text.strip(), - }) - return lines - - def _build_content(self, artist, track, lines, plain, progress_ms, not_synced_str): - header = self.strings("header").format( - utils.escape_html(artist), - utils.escape_html(track), - ) - if lines: - curr_idx = 0 - for i, line in enumerate(lines): - if progress_ms >= line["time"]: - curr_idx = i - win_start = max(0, curr_idx - 1) - win_end = min(len(lines), curr_idx + 6) - rows = [] - for i in range(win_start, win_end): - t = lines[i]["text"] or self.config["dot"] - if i == curr_idx: - rows.append( - f"{self.config['emoji_current']} {utils.escape_html(t)}" - ) - else: - rows.append(f"{utils.escape_html(t)}") - return header + "\n".join(rows) - else: - return header + not_synced_str + f"
{utils.escape_html((plain or '')[:4000])}
" - - def _markup(self, song_url): - return [ - [{"text": "🔗 song.link", "url": song_url}], - [{"text": "❌ Close", "callback": self._close_cb}], - ] - - async def _close_cb(self, call): - for track_id, task in list(self._active_tasks.items()): - task.cancel() - self._active_tasks.pop(track_id, None) - try: - await call.answer() - await call.delete() - except Exception: - pass - - async def run_loop(self, form, mod, track_id, artist_name, track_name, song_url, lines, plain, not_synced_str): - last_display = "" - try: - while True: - pb = await mod._YaMusicMod__get_now_playing() - if not pb or not pb.get("track") or pb["track"]["track_id"] != track_id: - try: - await form.edit( - self.strings("finished"), - reply_markup=[[{"text": "❌ Close", "callback": self._close_cb}]], - ) - except Exception: - pass - break - - prog = pb.get("progress_ms", 0) - content = self._build_content( - artist_name, track_name, lines, plain, prog, not_synced_str - ) - - if content != last_display: - try: - await form.edit(content, reply_markup=self._markup(song_url)) - last_display = content - except Exception: - break - - if not lines: - break - - await asyncio.sleep(self.config["lyrics_delay"]) - - except asyncio.CancelledError: - raise - except Exception: - pass - finally: - self._active_tasks.pop(track_id, None) - - @loader.command(ru_doc="- показать синхронизированный текст песни") - async def ynowlcmd(self, message: Message): - """- show synchronized lyrics for current YaMusic track""" - mod = self.lookup("YaMusic") - if not mod: - form = await self.inline.form("⏳", message=message) - await form.edit( - self.strings("no_YaMusicMod"), - reply_markup=[[{"text": "Install YaMusic", "callback": self.install_yamusic}]], - ) - return - - ya_token = mod.get("__config__")["token"] - if not ya_token: - await self.invoke("yguide", " ", "me") - form = await self.inline.form("⏳", message=message) - await form.edit( - self.strings("no_auth"), - reply_markup=[[{"text": "Хорошо", "callback": self.close}]], - ) - return - - playback = await mod._YaMusicMod__get_now_playing() - if not playback or not playback.get("track"): - return await utils.answer(message, self.strings("no_ym")) - - track = playback["track"] - track_id = track["track_id"] - artist_name = ", ".join(track["artist"]) - track_name = track["title"] - song_url = f"https://song.link/s/{track_id}" - - old = self._active_tasks.pop(track_id, None) - if old: - old.cancel() - - data = await self._get_lyrics(artist_name, track_name) - if data and data.get("timeout"): - return utils.answer( - message, - self.strings["timeout"] - ) - if not data or data.get("instrumental"): - track_and_artist = f"{utils.escape_html(track_name)} - {utils.escape_html(artist_name)}" - return await utils.answer( - message, - self.strings("no_lyrics").format(track_and_artist), - ) - - synced_raw = data.get("syncedLyrics") - plain = data.get("plainLyrics", "") - - lines = self._parse_synced(synced_raw) if synced_raw else [] - not_synced_str = self.strings("not_synced") - - prog = playback.get("progress_ms", 0) - initial_content = self._build_content( - artist_name, track_name, lines, plain, prog, not_synced_str - ) - - form = await self.inline.form( - text=initial_content, - message=message, - reply_markup=self._markup(song_url), - ) - - task = asyncio.create_task( - self.run_loop( - form=form, - mod=mod, - track_id=track_id, - artist_name=artist_name, - track_name=track_name, - song_url=song_url, - lines=lines, - plain=plain, - not_synced_str=not_synced_str, - ) - ) - self._active_tasks[track_id] = task - - # Fan-fantasizing - # Fa-fa-fa-fantasizing - # You and I-I-I - # When I close my eyes (my eyes) - # Nothings real - # Fantasizing (fantasizing) - # Bout you and I - # Cos you only hit my line - # When you wanna waste time - # I know you're so busy - # But trust me baby I'm not blind (blind) - # Uh oh, you and I - # We could never be - # Uh oh you and I - # Cos we will never be - # Uh oh, you and I - # No, we will never be - # That pretty picture that I painted in my mind (mind) - # So tell me what (tell me, tell me) - # The view is like - # With your head in the clouds - # And tell me what (tell me, tell me) - # It feels like to be right all the time - # You say that you love me - # But you don't even love yourself (no) - # Wanna get in my head - # But I ain't gonna let you close (no) - # Tryna control me - # But I ain't gon' play your game - # No more - # No I won't - # When I close (when I close) - # My eyes (my eyes) - # Nothing's real (no) - # Fantasizing (fantasize) - # bout you-you-you-you-you - # You-you-you-you - # You-you-you-you - # You-you-you - # And I - # You-you-you-you-you-you-you - # You-you-you-you-you-you-you - # You-you-you-you-you - # And I - # This is the last time I tell you - # Don't come round my way if you're just gon' waste my time - # And no, I won't be there for the long run - # No, not I - # But you never get (never get) - # The message, do you? - # You never seem (never seem) - # To grip an understanding - # That you emulate a ghost - # I pointed out all of your flaws - # But you still came up with excuses for em all - # So typical (so typical) - # You know it all - # So of course, I'm the one that's wrong (right?) - # When I close my eyes - # Nothings real - # Fan- fantasize - # Fa-fa-fa-fa - # Fantasizing - # Fa-fantasizing bout you and I \ No newline at end of file diff --git a/SunnexGB/Heroku-Modules/spotify_ph.py b/SunnexGB/Heroku-Modules/spotify_ph.py index 22634f5..88ab015 100644 --- a/SunnexGB/Heroku-Modules/spotify_ph.py +++ b/SunnexGB/Heroku-Modules/spotify_ph.py @@ -14,8 +14,8 @@ class spotifyph(loader.Module): strings = { "name": "spotify_ph", - "start_duration": "🎶🎶", - "start_full_duration": "🎶🎶", + "start_duration": "🎶🎶", + "start_full_duration": "🎶🎶", "mid_duration": "🎶", "empty_mid": "🎶", "end_duration": "🎶", diff --git a/SunnexGB/Heroku-Modules/tgtheme.py b/SunnexGB/Heroku-Modules/tgtheme.py new file mode 100644 index 0000000..62c9d57 --- /dev/null +++ b/SunnexGB/Heroku-Modules/tgtheme.py @@ -0,0 +1,71 @@ +# requires: Pillow +# meta developer: @H_SunMods +# meta pic: https://r2.fakecrime.bio/uploads/47308ab9-6035-4e7d-bc96-6b58f864bb33.jpg +# meta banner: https://r2.fakecrime.bio/uploads/47308ab9-6035-4e7d-bc96-6b58f864bb33.jpg +# meta fhsdesc: Theme, Темы, Sunnex, SunnexGB, H_SunMods +# это прям жеске тест, все мега поносно,я буду стараяться это обновлять,чисто так коментарий для тех кто любит читать код или... +# сувать свой нос куда попало. Ну а потом планируеться там условно сделать для IOS и Desktop тоже самое. +# баннер я тоже переделаю,но мне лень пока что... + +import io +import logging +from .. import loader, utils + +logger = logging.getLogger(__name__) + +@loader.tds +class tgtheme(loader.Module): + """Module that creates an android-theme from a photo""" + + strings = { + "name": "TgTheme (Mega-BETA)", + "no_photo": "Reply to a photo/b>", + "no_lib": "Library not loaded", + } + + strings_ru = { + "_cls_doc": "Модуль который создает тг-тему по фото", + "no_photo": "Ответьте на фото", + "no_lib": "Библиотека не загружена", + } + + def __init__(self): + self.lib = None + + async def client_ready(self): + try: + self.lib = await self.import_lib( + "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/refs/heads/main/Assets/TgTheme/TgThemeLib.py", + suspend_on_error=True, + ) + except Exception: + logger.exception("Failed to load library") + self.lib = None + + @loader.command(ru_doc="- Создать тг-тему по фото") + async def android(self, message): + """- Create tg-theme from a photo""" + if not self.lib: + return await utils.answer(message, self.strings["no_lib"]) + args = utils.get_args_raw(message) + transparency = 100 + if args.strip().isdigit(): + transparency = max(0, min(100, int(args.strip()))) + alpha = f"{int(transparency / 100 * 255):02x}" + reply = await message.get_reply_message() or message + photo = None + if reply.photo: + photo = reply.photo + elif reply.document and reply.document.mime_type and reply.document.mime_type.startswith("image/"): + photo = reply.document + if photo is None: + await utils.answer(message, self.strings["no_photo"]) + return + try: + photo_bytes = await self.client.download_media(photo, bytes) + theme_bytes = self.lib.process_photo(photo_bytes, alpha) + file = io.BytesIO(theme_bytes) + file.name = "android.attheme" + await message.edit(file=file, text="<3") + except Exception: + pass