Added and updated repositories 2026-06-21 02:58:54

This commit is contained in:
github-actions[bot]
2026-06-21 02:58:54 +00:00
parent 2770ae1ccc
commit 7c11ce7c6e
7 changed files with 1545 additions and 677 deletions

View File

@@ -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()

View File

@@ -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": "<tg-emoji emoji-id=5431402435497181911>💢</tg-emoji> <b>SpotifyMod not found,but u can install it. You can also support developer: </b> @ke_mods",
"no_yamusic": "<tg-emoji emoji-id=5431402435497181911>💢</tg-emoji> <b>YaMusicMod not found,but u can install it. You can also support developer: </b> @codrago_m",
"no_auth_spotify": "<tg-emoji emoji-id=5429225166250984904>⁉️</tg-emoji><b> You not authorized in SpotifyMod, visit you Saved Messages.</b>",
"no_auth_yamusic": "<tg-emoji emoji-id=5429225166250984904>⁉️</tg-emoji><b> You not authorized in SpotifyMod, visit you Saved Messages and setup token to continue.</b>",
"no_spotify": "<tg-emoji emoji-id=5429164207780152924>😅</tg-emoji> <b>Nothing is playing on Spotify.</b>",
"no_ym": "<tg-emoji emoji-id=5429164207780152924>😅</tg-emoji> <b>Nothing is playing on YaMusic.</b>",
"no_lyrics": "<tg-emoji emoji-id=5431402435497181911>💢</tg-emoji> <b>Lyrics not found for:</b> <code>{}</code>",
"not_synced": "<i><tg-emoji emoji-id=5431445849026611010>⚠️</tg-emoji> Lyrics are not synchronized.</i>\n\n",
"TrackEnded": "<tg-emoji emoji-id=5429638011392377649>‼️</tg-emoji> Playback ended or track changed.",
"header": "<tg-emoji emoji-id=5429413328768224565>🎤</tg-emoji> <b>{} - {}</b>\n\n",
"timeout": "<b><tg-emoji emoji-id=5429455831764584284>⏳</tg-emoji></b><b> Oopsi, looks like we've got a timeout here</b>.",
"yamusic_installed": "YaMusic installed!",
"spotify_installed": "SpotifyMod installed!",
"song_link": "🔗 song.link",
"close": "❌ Close",
"ok": "OK",
}
strings_ru = {
"_cls_doc": "Лайв слова текущей песни.",
"no_spotifymod": "<tg-emoji emoji-id=5431402435497181911>💢</tg-emoji> <b>SpotifyMod не найден,но его можно установить. Вы также можете поддержать разработчика: </b> @ke_mods",
"no_yamusic": "<tg-emoji emoji-id=5431402435497181911>💢</tg-emoji> <b>YaMusicMod не найден, но его можно установить. Вы также можете поддержать разработчика: </b> @codrago_m",
"no_auth_spotify": "<tg-emoji emoji-id=5429225166250984904>⁉️</tg-emoji> <b>Вы не авторизованы в SpotifyMod. Перейдите в Избранное.</b>",
"no_auth_yamusic": "<tg-emoji emoji-id=5429225166250984904>⁉️</tg-emoji><b> Вы не авторизированы в YaMusicMod, перейдите в Избранное и установите токен для продолжения работы.</b>",
"no_spotify": "<tg-emoji emoji-id=5429164207780152924>😅</tg-emoji> <b>В Spotify ничего не играет.</b>",
"no_ym": "<tg-emoji emoji-id=5429164207780152924>😅</tg-emoji> <b>В YaMusic ничего не играет.</b>",
"no_lyrics": "<tg-emoji emoji-id=5431402435497181911>💢</tg-emoji> <b>Текст не найден для:</b> <code>{}</code>",
"not_synced": "<i><tg-emoji emoji-id=5431445849026611010>⚠️</tg-emoji> Текст не синхронизирован.</i>\n\n",
"TrackEnded": "<tg-emoji emoji-id=5429638011392377649>‼️</tg-emoji> Воспроизведение завершено или трек сменился.",
"header": "<tg-emoji emoji-id=5429413328768224565>🎤</tg-emoji> <b>{} - {}</b>\n\n",
"timeout": "<b><tg-emoji emoji-id=5429455831764584284>⏳</tg-emoji></b><b> Упси, похоже кто то словил таймаут.</b>.",
"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",
"<tg-emoji emoji-id='5215679757366089921'>🤯</tg-emoji>",
"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"<b>{self.config['emoji_current']} {utils.escape_html(t)}</b>")
else:
rows.append(f"<code>{utils.escape_html(t)}</code>")
return header + "\n".join(rows)
return header + not_synced_str + f"<blockquote expandable>{utils.escape_html((plain or '')[:4000])}</blockquote>"
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")
)

View File

@@ -1,25 +1,21 @@
# requires: aiohttp pyngrok # requires: aiohttp
# meta developer: @H_SunMods # meta developer: @H_SunMods
# meta banner: https://r2.fakecrime.bio/uploads/965a3206-4609-4dff-beb0-6831f8b90e12.jpg # meta banner: https://r2.fakecrime.bio/uploads/965a3206-4609-4dff-beb0-6831f8b90e12.jpg
# current ver # current ver
__version__ = (0, 1, 0) __version__ = (0, 1, 1)
import json import json
import socket import socket
import asyncio import asyncio
import secrets import secrets
import logging import logging
import re
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
from aiohttp import ClientSession, ClientTimeout, web from aiohttp import ClientSession, ClientTimeout, web
from herokutl.types import Message from herokutl.types import Message
from pyngrok import conf, ngrok
from .. import loader, utils from .. import loader, utils
from ..inline.types import InlineCall 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" 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" 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" 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): 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""" """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 = { strings = {
"name": "NoChess", "name": "NoChess",
"starting": "( ノ・ェ・ )ノ <b>Starting NoChess...</b>", "starting": "( ノ・ェ・ )ノ <b>Starting NoChess...</b>",
@@ -38,8 +33,7 @@ class NoChess(loader.Module):
"already_running": "ʕᵕᴥᵕʔ <b>NoChess is already running</b>", "already_running": "ʕᵕᴥᵕʔ <b>NoChess is already running</b>",
"stopped": "・゚・(。>д<。)・゚・ NoChess stopped", "stopped": "・゚・(。>д<。)・゚・ NoChess stopped",
"not_running": "(✿╹◡╹) NoChess is not running", "not_running": "(✿╹◡╹) NoChess is not running",
"ngrok_missing": "Set a <code>ngrok_token</code>", "tunnel_error": "Serveo tunnel error: <code>{}</code>",
"ngrok_error": "Ngrok start error: <code>{}</code>",
"asset_read_error": "Failed to load web assets: <code>{}</code>", "asset_read_error": "Failed to load web assets: <code>{}</code>",
"open_button": "Open mini-app", "open_button": "Open mini-app",
"stop_button": "Stop", "stop_button": "Stop",
@@ -59,8 +53,7 @@ class NoChess(loader.Module):
"already_running": "ʕᵕᴥᵕʔ <b>NoChess уже запущен</b>", "already_running": "ʕᵕᴥᵕʔ <b>NoChess уже запущен</b>",
"stopped": "・゚・(。>д<。)・゚・ NoChess остановлен", "stopped": "・゚・(。>д<。)・゚・ NoChess остановлен",
"not_running": "(✿╹◡╹) NoChess не запущен", "not_running": "(✿╹◡╹) NoChess не запущен",
"ngrok_missing": "Укажи <code>ngrok_token</code>", "tunnel_error": "Ошибка туннеля Serveo: <code>{}</code>",
"ngrok_error": "Ошибка запуска ngrok: <code>{}</code>",
"asset_read_error": "Не удалось загрузить веб-ассеты: <code>{}</code>", "asset_read_error": "Не удалось загрузить веб-ассеты: <code>{}</code>",
"open_button": "Открыть мини-приложение", "open_button": "Открыть мини-приложение",
"stop_button": "Остановить", "stop_button": "Остановить",
@@ -73,6 +66,146 @@ class NoChess(loader.Module):
"not_supported_platform": "(┬┬_┬┬) К сожалению, на эту платформу невозможно установить этот модуль.\n\n(〜^∇^)〜 Это не ошибка, пожалуйста, не обращайтесь в поддержку." "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": "( ノ・ェ・ )ノ <b>Starte NoChess...</b>",
"online": "(*˘︶˘*) <b>NoChess läuft</b>",
"already_running": "ʕᵕᴥᵕʔ <b>NoChess läuft bereits</b>",
"stopped": "・゚・(。>д<。)・゚・ NoChess gestoppt",
"not_running": "(✿╹◡╹) NoChess läuft nicht",
"tunnel_error": "Serveo-Tunnel-Fehler: <code>{}</code>",
"asset_read_error": "Fehler beim Laden der Web-Assets: <code>{}</code>",
"open_button": "Mini-App öffnen",
"stop_button": "Stopp",
"about_text": "<b>Wichtig zu lesen:</b>\nManchmal startet der Server nicht, weil zu viele Prozesse laufen. <code>cma</code> 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 <code>nochess</code> und dann <code>cma</code> zum Einrichten\n 3. Danach Prozess neustarten mit <code>nochess -kill</code> und nochmal <code>nochess</code>\nPre-Alpha, später 1.0.0 falls die Leute die Idee mögen.",
"cma_start": "( ノ・ェ・ )ノ <b>Erstelle Mini-App im BotFather...</b>",
"cma_need_url": "Setze zuerst die Mini-App-Web-URL oder führe <code>.nochess</code> aus.",
"cma_done": "(*˘︶˘*) <b>Fertig.</b>",
"cma_error": "Fehler: <code>{}</code>",
"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": "( ノ・ェ・ )ノ <b>Запуск NoChess...</b>",
"online": "(*˘︶˘*) <b>NoChess запущено</b>",
"already_running": "ʕᵕᴥᵕʔ <b>NoChess вже запущено</b>",
"stopped": "・゚・(。>д<。)・゚・ NoChess зупинено",
"not_running": "(✿╹◡╹) NoChess не запущено",
"tunnel_error": "Помилка тунелю Serveo: <code>{}</code>",
"asset_read_error": "Не вдалося завантажити веб-ассети: <code>{}</code>",
"open_button": "Відкрити міні-застосунок",
"stop_button": "Зупинити",
"about_text": "<b>Важливо прочитати:</b>\nІноді сервер не запускається через забагато процесів. <code>cma</code> налаштовує за шаблоном — налаштуйте веб-застосунок самостійно.\nА ще:\n 1. Перший запуск — одразу посилання на сайт, не веб-застосунок\n 2. Використовуйте <code>nochess</code>, потім <code>cma</code> для налаштування\n 3. Перезапустіть процес: <code>nochess -kill</code>, потім <code>nochess</code>\nПре-альфа, пізніше 1.0.0 якщо ідея сподобається.",
"cma_start": "( ノ・ェ・ )ノ <b>Створюю міні-застосунок через BotFather...</b>",
"cma_need_url": "Спочатку вкажи URL або запусти <code>.nochess</code>",
"cma_done": "(*˘︶˘*) <b>Готово.</b>",
"cma_error": "Помилка: <code>{}</code>",
"RuntimeError": "юзернейм інлайн-бота не знайдено",
"not_supported_platform": "(┬┬_┬┬) На жаль, неможливо встановити цей модуль на цю платформу.\n\n(〜^∇^)〜 Це не помилка, не звертайтесь у підтримку."
}
strings_jp = {
"_cls_doc": "NoChess - HTMLまたはTelegram Mini-Appとしてページを起動するモジュール。Chess@nullmodのアドオン",
"starting": "( ノ・ェ・ )ノ <b>NoChessを起動中...</b>",
"online": "(*˘︶˘*) <b>NoChessは実行中です</b>",
"already_running": "ʕᵕᴥᵕʔ <b>NoChessはすでに実行中です</b>",
"stopped": "・゚・(。>д<。)・゚・ NoChessを停止しました",
"not_running": "(✿╹◡╹) NoChessは実行されていません",
"tunnel_error": "Serveoトンネルエラー: <code>{}</code>",
"asset_read_error": "Webアセットの読み込みに失敗: <code>{}</code>",
"open_button": "ミニアプリを開く",
"stop_button": "停止",
"about_text": "<b>重要なお知らせ:</b>\nプロセスが多すぎてサーバーが起動しないことがあります。<code>cma</code>はテンプレートでアプリをセットアップしますが歪なので自分で設定してください。\nさらに:\n 1. 最初の起動はWebアプリではなくサイトリンクで開始\n 2. <code>nochess</code>を使い、<code>cma</code>で設定\n 3. <code>nochess -kill</code>してから再度<code>nochess</code>\nプレアルファ版、後で1.0.0に変更予定。",
"cma_start": "( ノ・ェ・ )ノ <b>BotFatherでミニアプリを作成中...</b>",
"cma_need_url": "最初にミニアプリのURLを設定するか、<code>.nochess</code>を実行してください",
"cma_done": "(*˘︶˘*) <b>完了。</b>",
"cma_error": "エラー: <code>{}</code>",
"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": "( ノ・ェ・ )ノ <b>Spinnin' up NoChess...</b>",
"online": "(*˘︶˘*) <b>NoChess is live, fam</b>",
"already_running": "ʕᵕᴥᵕʔ <b>NoChess already cookin'</b>",
"stopped": "・゚・(。>д<。)・゚・ NoChess iced",
"not_running": "(✿╹◡╹) NoChess ain't up",
"tunnel_error": "Serveo tunnel bricked: <code>{}</code>",
"asset_read_error": "Couldn't snag web assets: <code>{}</code>",
"open_button": "Pop the mini-app",
"stop_button": "Cut it",
"about_text": "<b>RTFM:</b>\nBox won't lift sometimes 'cause too many procs — just reboot. <code>cma</code> uses a jank template so tweak config yerself.\nAlso:\n 1. First run = site link, not web app\n 2. Hit <code>nochess</code> then <code>cma</code> to rig it\n 3. Bounce the proc with <code>nochess -kill</code> + <code>nochess</code>\nPre-alpha slop, gonna bump to 1.0.0 if peeps vibe.",
"cma_start": "( ノ・ェ・ )ノ <b>Forgin' mini app via BotFather...</b>",
"cma_need_url": "Drop a mini-app URL first or run <code>.nochess</code>",
"cma_done": "(*˘︶˘*) <b>Ship it.</b>",
"cma_error": "L + ratio: <code>{}</code>",
"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": "( ノ・ェ・ )ノ <b>Газуем NoChess...</b>",
"online": "(*˘︶˘*) <b>NoChess на стиле</b>",
"already_running": "ʕᵕᴥᵕʔ <b>NoChess уже тащит</b>",
"stopped": "・゚・(。>д<。)・゚・ NoChess слился",
"not_running": "(✿╹◡╹) NoChess не в теме",
"tunnel_error": "Serveo тунель крашнулся: <code>{}</code>",
"asset_read_error": "Не смог забрать ассеты: <code>{}</code>",
"open_button": "Открыть мини-апп",
"stop_button": "Стопэ",
"about_text": "<b>Читни сюда:</b>\nБывает серв не поднимается — процов дофига, ребутаю. <code>cma</code> сетапит криво, конфиг руками.\nИ ещё:\n 1. Первый запуск — сразу ссылка на сайт, не апп\n 2. Юзай <code>nochess</code>, потом <code>cma</code>\n 3. Дропни через <code>nochess -kill</code> и снова <code>nochess</code>\nПре-альфа дичь, потом 1.0.0 если залетит.",
"cma_start": "( ノ・ェ・ )ノ <b>Делаю мини-апп через BotFather...</b>",
"cma_need_url": "Сначала кинь URL или жмякни <code>.nochess</code>",
"cma_done": "(*˘︶˘*) <b>Запилил.</b>",
"cma_error": "Ой фейл: <code>{}</code>",
"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": "( ノ・ェ・ )ノ <b>B00t1ng N0Ch355...</b>",
"online": "(*˘︶˘*) <b>N0Ch355 15 1n th3 m4tr1x</b>",
"already_running": "ʕᵕᴥᵕʔ <b>N0Ch355 4lr34dy 0n</b>",
"stopped": "・゚・(。>д<。)・゚・ N0Ch355 t3rm1n4t3d",
"not_running": "(✿╹◡╹) N0Ch355 0ffl1n3",
"tunnel_error": "S3rv30 tunn3l f41l: <code>{}</code>",
"asset_read_error": "F41l3d t0 f3tch w3b 4553t5: <code>{}</code>",
"open_button": "0p3n m1n1-4pp",
"stop_button": "K1ll",
"about_text": "<b>R34D TH15:</b>\nB0x w0n't l1ft cuz 2 m4ny pr0c5 — r3b00t. <c0d3>cm4</c0d3> j4nk t3mpl4t3, c0nf1g m4nu4lly.\n4l50:\n 1. F1r5t run = 51t3 l1nk, n0t w3b 4pp\n 2. U53 <c0d3>n0ch355</c0d3> + <c0d3>cm4</c0d3>\n 3. B0unc3 w1th <c0d3>n0ch355 -k1ll</c0d3> + <c0d3>n0ch355</c0d3>\nPr3-4lph4, bump1n t0 1.0.0 1f p33p5 v1b3.",
"cma_start": "( ノ・ェ・ )ノ <b>C0njur1n9 m1n1 4pp v14 B0tF4th3r...</b>",
"cma_need_url": "Dr0p 4 URL f1r5t 0r run <c0d3>.n0ch355</c0d3>",
"cma_done": "(*˘︶˘*) <b>5h1pp3d.</b>",
"cma_error": "F41l: <code>{}</code>",
"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": "( ノ・ェ・ )ノ <b>Spinning up NoChess-chan...</b>",
"online": "(*˘︶˘*) <b>NoChess is wunning, nyaa~</b>",
"already_running": "ʕᵕᴥᵕʔ <b>NoChess awweady wunning, hehe</b>",
"stopped": "・゚・(。>д<。)・゚・ NoChess went bye-bye",
"not_running": "(✿╹◡╹) NoChess is sweepy...",
"tunnel_error": "Serveo tunnew-bun oopsie: <code>{}</code>",
"asset_read_error": "Couwdn't fetch the pwetty assets: <code>{}</code>",
"open_button": "Open minyi-app~",
"stop_button": "Stahp pwease",
"about_text": "<b>Pwease wead cawefuwwy:</b>\nSewvew won't wake up cuz too many pwocesses. <code>cma</code> setups fwom wonky tempwate, tweak config yuwsewf.\nAwso:\n 1. Fiwst waunch = site wink, not web app\n 2. Use <code>nochess</code>, den <code>cma</code>\n 3. Westawt wiff <code>nochess -kill</code> + <code>nochess</code>\nPwe-awpha, watew 1.0.0 if peopwe wike~. ",
"cma_start": "( ノ・ェ・ )ノ <b>Making minyi-app in BotFather-chan...</b>",
"cma_need_url": "Set URL fiwst owr wun <code>.nochess</code>, pwease~",
"cma_done": "(*˘︶˘*) <b>Aww done, nya!</b>",
"cma_error": "Oopsie woopsie: <code>{}</code>",
"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): async def client_ready(self):
platform = utils.get_named_platform() platform = utils.get_named_platform()
if platform in ("HikkaHost"): if platform in ("HikkaHost"):
@@ -81,10 +214,10 @@ class NoChess(loader.Module):
def __init__(self): def __init__(self):
self.config = loader.ModuleConfig( self.config = loader.ModuleConfig(
loader.ConfigValue( loader.ConfigValue(
"ngrok_token", "serveo_subdomain",
None, "",
"Token from ngrok.com | Токен полученый на ngrok.com", "Custom serveo subdomain (leave empty for random) | Кастомный поддомен serveo (оставь пустым для случайного)",
validator=loader.validators.Hidden(), validator=loader.validators.String(),
), ),
loader.ConfigValue( loader.ConfigValue(
"mini_app_url", "mini_app_url",
@@ -146,6 +279,11 @@ class NoChess(loader.Module):
self.access_token = None self.access_token = None
self.games_cache = [] self.games_cache = []
self.games_dump = "" 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): def theme_config_dict(self):
return { return {
@@ -238,9 +376,17 @@ class NoChess(loader.Module):
return await response.text() return await response.text()
async def load_web_assets(self): async def load_web_assets(self):
html = await self.read_remote_asset(html_raw) async with self._assets_lock:
css = await self.read_remote_asset(css_raw) if self._assets_html is not None:
js = await self.read_remote_asset(js_raw) 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 return html, css, js
def localication_script(self): def localication_script(self):
@@ -304,18 +450,90 @@ class NoChess(loader.Module):
return web.json_response({"error": "Unauthorized"}, status=401) return web.json_response({"error": "Unauthorized"}, status=401)
return web.json_response(await self.get_me_json()) 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): async def stop_server(self):
was_running = bool(self.runner) was_running = bool(self.runner)
try: await self._kill_serveo()
ngrok.kill()
except Exception:
pass
if self.runner: if self.runner:
await self.runner.cleanup() await self.runner.cleanup()
self.runner = None self.runner = None
self.tunnel_url = None self.tunnel_url = None
return was_running 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): async def send_form(self, message, url):
await self.inline.form( await self.inline.form(
self.strings["online"], self.strings["online"],
@@ -340,7 +558,16 @@ class NoChess(loader.Module):
except Exception: except Exception:
pass 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): async def nochess(self, message: Message):
"""[-kill] Call web interface to view chess game""" """[-kill] Call web interface to view chess game"""
try: try:
@@ -349,7 +576,7 @@ class NoChess(loader.Module):
await self.stop_server() await self.stop_server()
return await utils.answer( return await utils.answer(
message, 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): async def nochess_args(self, message: Message):
@@ -369,8 +596,6 @@ class NoChess(loader.Module):
if access: if access:
await self.send_form(message, access) await self.send_form(message, access)
return 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 self.refresh_games_cache()
await utils.answer(message, self.strings["starting"]) await utils.answer(message, self.strings["starting"])
self.ensure_access_token() self.ensure_access_token()
@@ -386,17 +611,12 @@ class NoChess(loader.Module):
await self.runner.setup() await self.runner.setup()
await web.TCPSite(self.runner, "127.0.0.1", port).start() await web.TCPSite(self.runner, "127.0.0.1", port).start()
try: try:
if self.config["ngrok_token"]: self.tunnel_url = await self._start_serveo(port)
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
except Exception as error: except Exception as error:
await self.stop_server() await self.stop_server()
return await utils.answer( return await utils.answer(
message, 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: if is_tg_direct:
access_url = mini_url 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 access_url = f"{base}/?token={self.access_token}" if base and self.access_token else base
await self.send_form(message, access_url) 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): async def cma(self, message: Message):
"""Create and setup mini-app""" """Create and setup mini-app"""
raw_args = (utils.get_args_raw(message) or "").strip() raw_args = (utils.get_args_raw(message) or "").strip()
@@ -479,7 +708,16 @@ class NoChess(loader.Module):
except Exception as error: except Exception as error:
await utils.answer(message, self.strings["cma_error"].format(utils.escape_html(str(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): async def about(self, message: Message):
"""IMPORTANT READING""" """IMPORTANT READING"""
await utils.answer(message, self.strings["about_text"]) await utils.answer(message, self.strings["about_text"])

View File

@@ -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": "<tg-emoji emoji-id=5431402435497181911>💢</tg-emoji> <b>SpotifyMod not found,but u can install it. You can also support developer: </b> @ke_mods",
"no_auth": "<tg-emoji emoji-id=5429225166250984904>⁉️</tg-emoji><b> You not authorized in SpotifyMod, visit you Saved Messages.</b>",
"no_spotify": "<tg-emoji emoji-id=5429164207780152924>😅</tg-emoji> <b>Nothing is playing on Spotify.</b>",
"no_lyrics": "<tg-emoji emoji-id=5431402435497181911>💢</tg-emoji> <b>Lyrics not found for:</b> <code>{}</code>",
"not_synced": "<i><tg-emoji emoji-id=5431445849026611010>⚠️</tg-emoji> Lyrics are not synchronized.</i>\n\n",
"finished": "<tg-emoji emoji-id=5429638011392377649>‼️</tg-emoji> Playback ended or track changed.",
"header": "<tg-emoji emoji-id=5429413328768224565>🎤</tg-emoji> <b>{} - {}</b>\n\n",
"timeout": "<b><tg-emoji emoji-id=5429455831764584284>⏳</tg-emoji></b><b> Oopsi, looks like we've got a timeout here</b>.",
}
strings_ru = {
"cls_doc": "Лайв слова текущей песни.",
"no_spotifymod": "<tg-emoji emoji-id=5431402435497181911>💢</tg-emoji> <b>SpotifyMod не найден,но его можно установить. Вы также можете поддержать разработчика: </b> @ke_mods",
"no_auth": "<tg-emoji emoji-id=5429225166250984904>⁉️</tg-emoji><b> Вы не авторизированы в SpotifyMod, перейдите в Избранное.</b>",
"no_spotify": "<tg-emoji emoji-id=5429164207780152924>😅</tg-emoji> <b>В Spotify ничего не играет.</b>",
"no_lyrics": "<tg-emoji emoji-id=5431402435497181911>💢</tg-emoji> <b>Текст не найден для:</b> <code>{}</code>",
"not_synced": "<i><tg-emoji emoji-id=5431445849026611010>⚠️</tg-emoji> Текст не синхронизирован.</i>\n\n",
"finished": "<tg-emoji emoji-id=5429638011392377649>‼️</tg-emoji> Воспроизведение завершено или трек сменился.",
"header": "<tg-emoji emoji-id=5429413328768224565>🎤</tg-emoji> <b>{} - {}</b>\n\n",
"timeout": "<b><tg-emoji emoji-id=5429455831764584284>⏳</tg-emoji></b><b> Упси, похоже кто то словил таймаут.</b>.",
}
def __init__(self):
self._active_tasks: dict = {}
self.config = loader.ModuleConfig(
loader.ConfigValue(
"emoji_current",
"<tg-emoji emoji-id='5215679757366089921'>🤯</tg-emoji>",
"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"<b>{self.config['emoji_current']} {utils.escape_html(t)}</b>"
)
else:
rows.append(f"<code>{utils.escape_html(t)}</code>")
return header + "\n".join(rows)
else:
return header + not_synced_str + f"<blockquote expandable>{utils.escape_html((plain or '')[:4000])}</blockquote>"
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

View File

@@ -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": "<tg-emoji emoji-id=5431402435497181911>💢</tg-emoji> <b>YaMusicMod not found,but u can install it. You can also support developer: </b> @codrago_m",
"no_auth": "<tg-emoji emoji-id=5429225166250984904>⁉️</tg-emoji><b> You not authorized in SpotifyMod, visit you Saved Messages and setup token to continue.</b>",
"no_ym": "<tg-emoji emoji-id=5429164207780152924>😅</tg-emoji> <b>Nothing is playing on YaMusic.</b>",
"no_lyrics": "<tg-emoji emoji-id=5431402435497181911>💢</tg-emoji> <b>Lyrics not found for:</b> <code>{}</code>",
"not_synced": "<i><tg-emoji emoji-id=5431445849026611010>⚠️</tg-emoji> Lyrics are not synchronized.</i>\n\n",
"finished": "<tg-emoji emoji-id=5429638011392377649>‼️</tg-emoji> Playback ended or track changed.",
"header": "<tg-emoji emoji-id=5429413328768224565>🎤</tg-emoji> <b>{} - {}</b>\n\n",
"timeout": "<b><tg-emoji emoji-id=5429455831764584284>⏳</tg-emoji></b><b> Oopsi, looks like we've got a timeout here</b>.",
}
strings_ru = {
"cls_doc": "Лайв слова текущей песни.",
"no_YaMusicMod": "<tg-emoji emoji-id=5431402435497181911>💢</tg-emoji> <b>YaMusicMod не найден,но его можно установить. Вы также можете поддержать разработчика: </b> @codrago_m",
"no_auth": "<tg-emoji emoji-id=5429225166250984904>⁉️</tg-emoji><b> Вы не авторизированы в YaMusicMod, перейдите в Избранное и установите токен для продолжения работы.</b>",
"no_ym": "<tg-emoji emoji-id=5429164207780152924>😅</tg-emoji> <b>В YaMusic ничего не играет.</b>",
"no_lyrics": "<tg-emoji emoji-id=5431402435497181911>💢</tg-emoji> <b>Текст не найден для:</b> <code>{}</code>",
"not_synced": "<i><tg-emoji emoji-id=5431445849026611010>⚠️</tg-emoji> Текст не синхронизирован.</i>\n\n",
"finished": "<tg-emoji emoji-id=5429638011392377649>‼️</tg-emoji> Воспроизведение завершено или трек сменился.",
"header": "<tg-emoji emoji-id=5429413328768224565>🎤</tg-emoji> <b>{} - {}</b>\n\n",
"timeout": "<b><tg-emoji emoji-id=5429455831764584284>⏳</tg-emoji></b><b> Упси, похоже кто то словил таймаут.</b>.",
}
def __init__(self):
self._active_tasks: dict = {}
self.config = loader.ModuleConfig(
loader.ConfigValue(
"emoji_current",
"<tg-emoji emoji-id='5215679757366089921'>🤯</tg-emoji>",
"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"<b>{self.config['emoji_current']} {utils.escape_html(t)}</b>"
)
else:
rows.append(f"<code>{utils.escape_html(t)}</code>")
return header + "\n".join(rows)
else:
return header + not_synced_str + f"<blockquote expandable>{utils.escape_html((plain or '')[:4000])}</blockquote>"
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

View File

@@ -14,8 +14,8 @@ class spotifyph(loader.Module):
strings = { strings = {
"name": "spotify_ph", "name": "spotify_ph",
"start_duration": "<tg-emoji emoji-id=5386793861483894694>🎶</tg-emoji><tg-emoji emoji-id=5384507693341906264>🎶</tg-emoji>", "start_duration": "<tg-emoji emoji-id=5255953176074425678>🎶</tg-emoji><tg-emoji emoji-id=5384507693341906264>🎶</tg-emoji>",
"start_full_duration": "<tg-emoji emoji-id=5386793861483894694>🎶</tg-emoji><tg-emoji emoji-id=5384401375721463792>🎶</tg-emoji>", "start_full_duration": "<tg-emoji emoji-id=5255953176074425678>🎶</tg-emoji><tg-emoji emoji-id=5384401375721463792>🎶</tg-emoji>",
"mid_duration": "<tg-emoji emoji-id=5386623003389888935>🎶</tg-emoji>", "mid_duration": "<tg-emoji emoji-id=5386623003389888935>🎶</tg-emoji>",
"empty_mid": "<tg-emoji emoji-id=5384267235302870604>🎶</tg-emoji>", "empty_mid": "<tg-emoji emoji-id=5384267235302870604>🎶</tg-emoji>",
"end_duration": "<tg-emoji emoji-id=5386826786703186322>🎶</tg-emoji>", "end_duration": "<tg-emoji emoji-id=5386826786703186322>🎶</tg-emoji>",

View File

@@ -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": "<b>Reply to a photo/b>",
"no_lib": "<b>Library not loaded</b>",
}
strings_ru = {
"_cls_doc": "Модуль который создает тг-тему по фото",
"no_photo": "<b>Ответьте на фото</b>",
"no_lib": "<b>Библиотека не загружена</b>",
}
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