├── requirements.txt ├── generate.py ├── constants.py ├── README.md ├── setup.py └── bot.py /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Poolitzer/spotify_telegram_bio_updater/HEAD/requirements.txt -------------------------------------------------------------------------------- /generate.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | from constants import CLIENT_SECRET, CLIENT_ID, INITIAL_BIO, INITIAL_TOKEN 4 | 5 | from aiohttp import ClientSession 6 | 7 | body = { 8 | "client_id": CLIENT_ID, 9 | "client_secret": CLIENT_SECRET, 10 | "grant_type": "authorization_code", 11 | "redirect_uri": "http://localhost:1234/callback", 12 | "code": INITIAL_TOKEN, 13 | } 14 | 15 | async def generate(): 16 | async with ClientSession() as session: 17 | async with session.post( 18 | "https://accounts.spotify.com/api/token", data=body 19 | ) as post_response: 20 | save = await post_response.json() 21 | to_create = { 22 | "bio": INITIAL_BIO, 23 | "access_token": save["access_token"], 24 | "refresh_token": save["refresh_token"], 25 | "telegram_spam": False, 26 | "spotify_spam": False, 27 | } 28 | with open("./database.json", "w") as outfile: 29 | json.dump(to_create, outfile, indent=4, sort_keys=True) 30 | 31 | 32 | def main(): 33 | loop = asyncio.get_event_loop() 34 | loop.run_until_complete(generate()) 35 | loop.close() 36 | 37 | if __name__ == '__main__': 38 | main() 39 | -------------------------------------------------------------------------------- /constants.py: -------------------------------------------------------------------------------- 1 | CLIENT_ID = "" 2 | CLIENT_SECRET = "" 3 | API_ID = 0 4 | API_HASH = "" 5 | INITIAL_TOKEN = "" 6 | INITIAL_BIO = "" 7 | LOG = "me" 8 | # the escaping is necessary since we are testing against a regex pattern with it. 9 | SHUTDOWN_COMMAND = "\/\/stop" 10 | # The key which is used to determine if the current bio was generated from the bot or from the user. This means: 11 | # NEVER use whatever you put here in your original bio. NEVER. Don't do it! 12 | KEY = "🎶" 13 | # The bios MUST include the key. The bot will go though those and check if they are beneath telegrams character limit. 14 | BIOS = [ 15 | KEY + " Now Playing: {interpret} - {title} {progress}/{duration}", 16 | KEY + " Now Playing: {interpret} - {title}", 17 | KEY + " : {interpret} - {title}", 18 | KEY + " Now Playing: {title}", 19 | KEY + " : {title}", 20 | ] 21 | # Mind that some characters (e.g. emojis) count more in telegram more characters then in python. If you receive an 22 | # AboutTooLongError and get redirected here, you need to increase the offset. Check the special characters you either 23 | # have put in the KEY or in one of the BIOS with an official Telegram App and see how many characters they actually 24 | # count, then change the OFFSET below accordingly. Since the standard KEY is one emoji and I don't have more emojis 25 | # anywhere, it is set to one (One emoji counts as two characters, so I reduce 1 from the character limit). 26 | OFFSET = 1 27 | # reduce the OFFSET from our actual 70 character limit 28 | LIMIT = 70 - OFFSET 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spotify_telegram_bio_updater 2 | This userbot updates the biography of a telegram user according to their current spotify playback. If no playback is active, the bot changes the bio back to the original one from the user. 3 | 4 | ## Setup 5 | 6 | This script will do an interactive setup the first time you run bot.py. If you want to do it by yourself, you can follow the steps below, but that isn't necessary anymore. 7 | 8 | Just remember to run `pip install -r requirements.txt`. 9 | 10 | Depreciated manual steps: 11 | 12 | 0. Ensure you have python 3.6+ installed and then run `pip install -r requirements.txt`. 13 | 1. Get your spotify client_id and your client_secret from https://developer.spotify.com/dashboard/. If you need more help, have a look [here](https://developer.spotify.com/documentation/general/guides/app-settings/#register-your-app). Set a Rediret URI to http://localhost:1234/callback 14 | 2. Get your telegram app api_id and api_hash from https://my.telegram.org/. If you need more help, have a look [here](https://telethon.readthedocs.io/en/latest/extra/basic/creating-a-client.html#creating-a-client). 15 | 3. If you want to have a log channel or group or so, paste its invite link or id in the LOG variable. If you leave it at "me", you will see those in your saved chat. Only if errors occur ofc ;) 16 | 4. Paste all these values in their respective variables at [constants.py](/constants.py). While you are at it, you can also paste an initial biography there. Just take your current one. This is highly recommended. If you don't do this **and** have a currently playing track, the bot has at its first start no idea what your original biography is. Just do it, please. 17 | 5. You are going to generate a token here which is only valid for a short amount of time, so be sure to run generate.py quickly after this. Open the following link (change CLIENT_ID to you client_id): https://accounts.spotify.com/authorize?client_id=CLIENT_ID&response_type=code&redirect_uri=http://localhost:1234/callback&scope=user-read-playback-state%20user-read-currently-playing 18 | 6. After you grant permission, you get redirected to https://example.com/callback?code=_. Copy everything after the code, this is you initial token. 19 | 7. Now you can run [generate.py](/generate.py). This will generate a json file named database. 20 | 8. You are almost done. If you now run [bot.py](/bot.py), all you need to do is log into your telegram account. Follow the instructions on screen. 21 | 9. Now you are really done. 22 | 23 | ## Important Information 24 | 25 | You can shut the bot down if you want/need to. Write `//stop` anywhere you want. If you want to change the command, edit SHUTDOWN_COMMAND in the [constants](/constants.py). 26 | 27 | ## Warning 28 | 29 | This bot uses the emoji 🎶 to determine if the current bio is an active spotify biography or not. This means you mustn't use this emoji on your original biographies or the bot will probably break. Don't do it, thanks. 30 | 31 | ## But I want to 32 | 33 | Great news. Just change the KEY variable in [constants](/constants.py) file. Don't ever use your new KEY in your biographies though! 34 | 35 | ## Issues? Need help? Want to tell me something? 36 | 37 | Well, just create an issue here. You can also ping me on [telegram](https://t.me/poolitzer). 38 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | import asyncio 3 | import json 4 | 5 | from aiohttp import ClientSession, web 6 | 7 | import constants 8 | 9 | 10 | # a nice intro screen when an input is required 11 | intro_screen = ( 12 | "\n|----------------------------|\n" 13 | "|Spotify_Telegram_Bio_Updater|\n" 14 | "|----------------------------|\n" 15 | ) 16 | 17 | 18 | # this function rewrites constants.py and replaces the desired change_lines 19 | def update_constants(change_lines: [int], replacement: [str]): 20 | with open("./constants.py", "r", encoding="UTF-8") as file: 21 | # read a list of change_lines into data 22 | data = file.readlines() 23 | # now change the 2nd line, note that you have to add a newline 24 | for index, line in enumerate(change_lines): 25 | data[line] = replacement[index] + "\n" 26 | 27 | # and write everything back 28 | with open("./constants.py", "w", encoding="UTF-8") as file: 29 | file.writelines(data) 30 | 31 | 32 | async def setup(intro_printed: bool): 33 | if not intro_printed: 34 | print(intro_screen) 35 | print("Hello there, thanks for using this little project. ") 36 | replace_lines = [] 37 | replacement_strings = [] 38 | if constants.KEY == "🎶": 39 | new_key = input( 40 | "The default bio key is the emoji 🎶. The key is used to check if the current bio is set by you or from " 41 | "this script. This means you MUSTN'T use the key in your own bio.\nIf you want to change the key, enter " 42 | "the new one, otherwise just hit enter: " 43 | ) 44 | if new_key: 45 | constants.KEY = new_key 46 | replace_lines.append(11) 47 | replacement_strings.append(f'KEY = "{constants.KEY}"') 48 | if not constants.INITIAL_BIO: 49 | constants.INITIAL_BIO = input( 50 | "Please paste here your current Telegram Biography (remember to not have the key in there): " 51 | ) 52 | replace_lines.append(5) 53 | replacement_strings.append(f'INITIAL_BIO = "{constants.INITIAL_BIO}"') 54 | if constants.LOG == "me": 55 | new_log = input( 56 | "Every error will be logged in a telegram chat. The default one is your Saved Messages one, if you want to " 57 | "pick another, enter it here. The best working formats are either the username or invitation link of the " 58 | "chat. Otherwise, just hit enter and your Saved Messages chat will be used. " 59 | ) 60 | if new_log: 61 | constants.LOG = new_log 62 | replace_lines.append(6) 63 | replacement_strings.append(f'LOG = "{constants.LOG}"') 64 | if constants.SHUTDOWN_COMMAND == "\/\/stop": 65 | new_shutdown = input( 66 | "This project has a shutdown command, which every message you send anywhere is checked against. It " 67 | "defaults to //stop, if you want to change this, enter the new shutdown command here, otherwise " 68 | "just press enter: " 69 | ) 70 | if new_shutdown: 71 | constants.SHUTDOWN_COMMAND = re.escape(new_shutdown) 72 | replace_lines.append(8) 73 | replacement_strings.append( 74 | f'SHUTDOWN_COMMAND = "{constants.SHUTDOWN_COMMAND}"' 75 | ) 76 | if not constants.CLIENT_ID: 77 | constants.CLIENT_ID = input( 78 | "Now your Spotify Client ID is needed, you can get it from https://developer.spotify.com/dashboard/. " 79 | "Make sure to add the Redirect URI http://localhost:1234/callback, otherwise you will see the error " 80 | "INVALID_CLIENT: Invalid redirect URI. " 81 | ) 82 | replace_lines.append(0) 83 | replacement_strings.append(f'CLIENT_ID = "{constants.CLIENT_ID}"') 84 | if not constants.CLIENT_SECRET: 85 | constants.CLIENT_SECRET = input( 86 | "From the same site, copy paste your Client Secret: " 87 | ) 88 | replace_lines.append(1) 89 | replacement_strings.append(f'CLIENT_SECRET = "{constants.CLIENT_SECRET}"') 90 | if not constants.CLIENT_SECRET: 91 | constants.CLIENT_SECRET = input( 92 | "When you added the Spotify Client ID, you forgot to add the Client Secret.\n" 93 | "Make sure that you set the Redirect URI http://localhost:1234/callback, otherwise you will see the error " 94 | "INVALID_CLIENT: Invalid redirect URI. " 95 | ) 96 | replace_lines.append(1) 97 | replacement_strings.append(f'CLIENT_SECRET = "{constants.CLIENT_SECRET}"') 98 | update_constants(replace_lines, replacement_strings) 99 | if not constants.INITIAL_TOKEN: 100 | print( 101 | f"Great. Now you have to open the following URL, and connect your account to your Spotify Client. After " 102 | f"you have done that, you will be greeted by an Internal Server error, that is expected because the " 103 | f"local web server is killed immediately.\nJust return to this window for the next steps: " 104 | f"https://accounts.spotify.com/authorize?client_id={constants.CLIENT_ID}&response_type=code&" 105 | "redirect_uri=http://localhost:1234/callback&scope=user-read-playback-state%20" 106 | "user-read-currently-playing\nIf you see the INVALID_CLIENT: Invalid redirect URI, you need to set " 107 | "http://localhost:1234/callback as an URI in your client, and restart this project (your config has been " 108 | "saved no worries)." 109 | ) 110 | app = web.Application() 111 | runner = web.AppRunner(app) 112 | loop = asyncio.get_running_loop() 113 | running = loop.create_future() 114 | app["future"] = running 115 | app.add_routes([web.get("/callback", code_getter)]) 116 | await runner.setup() 117 | site = web.TCPSite(runner, host="localhost", port=1234) 118 | await site.start() 119 | try: 120 | await running 121 | except IndexError: 122 | pass 123 | async with ClientSession() as session: 124 | data = { 125 | "client_id": constants.CLIENT_ID, 126 | "client_secret": constants.CLIENT_SECRET, 127 | "grant_type": "authorization_code", 128 | "redirect_uri": "http://localhost:1234/callback", 129 | "code": constants.INITIAL_TOKEN, 130 | } 131 | async with session.post( 132 | "https://accounts.spotify.com/api/token", data=data 133 | ) as post_response: 134 | save = await post_response.json() 135 | to_create = { 136 | "bio": constants.INITIAL_BIO, 137 | "access_token": save["access_token"], 138 | "refresh_token": save["refresh_token"], 139 | "telegram_spam": False, 140 | "spotify_spam": False, 141 | } 142 | with open("./database.json", "w") as outfile: 143 | json.dump(to_create, outfile, indent=4, sort_keys=True) 144 | print( 145 | "\nThe initial setup run successfully, now you need to sign into your Telegram account.\n" 146 | ) 147 | 148 | 149 | async def code_getter(request): 150 | constants.INITIAL_TOKEN = request.rel_url.query["code"] 151 | await request.app["future"].set_exception(IndexError) 152 | -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os.path 3 | import json 4 | import logging 5 | 6 | from aiohttp import ClientSession 7 | from telethon import TelegramClient, events 8 | from telethon.errors import FloodWaitError, AboutTooLongError 9 | from telethon.tl.functions.account import UpdateProfileRequest 10 | from telethon.tl.functions.users import GetFullUserRequest 11 | 12 | import constants 13 | from setup import setup, intro_screen, update_constants 14 | 15 | # values supplied to telegram 16 | device_model = "spotify_bot" 17 | version = "1.6" 18 | system_version, app_version = version, version 19 | 20 | # if the intro screen has already been printed 21 | intro_printed = False 22 | 23 | # this check allows the user to not having to enter any constants into constants.py prior to using this script 24 | if constants.API_ID == 0: 25 | print(intro_screen) 26 | intro_printed = True 27 | constants.API_ID = int( 28 | input( 29 | "Hello there, thanks for using this little project. Please enter your Telegram App api_id. You can get" 30 | " it at https://my.telegran.org: " 31 | ) 32 | ) 33 | lines = [2] 34 | replacements = [f"API_ID = {constants.API_ID}"] 35 | # a bit better output strings, though its a bit weird if someone only enters one of the values 36 | if not constants.API_HASH: 37 | constants.API_HASH = input("\nGreat, now you need to copy paste the api_hash: ") 38 | lines.append(3) 39 | replacements.append(f'API_HASH = "{constants.API_HASH}"') 40 | update_constants(lines, replacements) 41 | # again, weird if this is triggered, but who am I to judge 42 | if not constants.API_HASH: 43 | print(intro_screen) 44 | intro_printed = True 45 | constants.API_HASH = input( 46 | "Hello there, thanks for using this little project. Please enter your Telegram App api_hash. You can get" 47 | " it at https://my.telegran.org: " 48 | ) 49 | lines = [3] 50 | replacements = [f'API_HASH = "{constants.API_HASH}"'] 51 | update_constants(lines, replacements) 52 | 53 | client = TelegramClient( 54 | "spotify", 55 | constants.API_ID, 56 | constants.API_HASH, 57 | device_model=device_model, 58 | system_version=system_version, 59 | app_version=app_version, 60 | ) 61 | 62 | logging.basicConfig( 63 | level=logging.ERROR, 64 | filename="log.log", 65 | format="[%(levelname) 5s/%(asctime)s] %(name)s: %(message)s", 66 | ) 67 | logger = logging.getLogger(__name__) 68 | 69 | aiohttp_server_logger = logging.getLogger("aiohttp.server") 70 | aiohttp_server_logger.disabled = True 71 | 72 | 73 | def ms_converter(millis): 74 | millis = int(millis) 75 | seconds = (millis / 1000) % 60 76 | seconds = int(seconds) 77 | if str(seconds) == "0": 78 | seconds = "00" 79 | if len(str(seconds)) == 1: 80 | seconds = "0" + str(seconds) 81 | minutes = (millis / (1000 * 60)) % 60 82 | minutes = int(minutes) 83 | return str(minutes) + ":" + str(seconds) 84 | 85 | 86 | class Database: 87 | def __init__(self): 88 | try: 89 | self.db = json.load(open("./database.json")) 90 | except FileNotFoundError: 91 | # this will be the case when someone hasn't run the setup yet, it will be recalled when the file exists 92 | self.db = {} 93 | 94 | def save_token(self, token): 95 | self.db["access_token"] = token 96 | self.save() 97 | 98 | def save_refresh(self, token): 99 | self.db["refresh_token"] = token 100 | self.save() 101 | 102 | def save_bio(self, bio): 103 | self.db["bio"] = bio 104 | self.save() 105 | 106 | def save_spam(self, which, what): 107 | self.db[which + "_spam"] = what 108 | 109 | def return_token(self): 110 | return self.db["access_token"] 111 | 112 | def return_refresh(self): 113 | return self.db["refresh_token"] 114 | 115 | def return_bio(self): 116 | return self.db["bio"] 117 | 118 | def return_spam(self, which): 119 | return self.db[which + "_spam"] 120 | 121 | def save(self): 122 | with open("./database.json", "w") as outfile: 123 | json.dump(self.db, outfile, indent=4, sort_keys=True) 124 | 125 | 126 | database = Database() 127 | 128 | 129 | # to stop unwanted spam, we sent these type of message only once. So we have a variable in our database which we check 130 | # for in return_info. When we send a message, we set this variable to true. After a successful update 131 | # (or a closing of spotify), we reset that variable to false. 132 | def save_spam(which, what): 133 | # see below why 134 | 135 | # this is if False is inserted, so if spam = False, so if everything is good. 136 | if not what: 137 | # if it wasn't normal before, we proceed 138 | if database.return_spam(which): 139 | # we save that it is normal now 140 | database.save_spam(which, False) 141 | # we return True so we can test against it and if it this function returns, we can send a fitting message 142 | return True 143 | # this is if True is inserted, so if spam = True, so if something went wrong 144 | else: 145 | # if it was normal before, we proceed 146 | if not database.return_spam(which): 147 | # we save that it is not normal now 148 | database.save_spam(which, True) 149 | # we return True so we can send a message 150 | return True 151 | # if True wasn't returned before, we can return False now so our test fails and we dont send a message 152 | return False 153 | 154 | 155 | async def work(session: ClientSession): 156 | while True: 157 | # SPOTIFY 158 | skip = False 159 | to_insert = {} 160 | oauth = {"Authorization": "Bearer " + database.return_token()} 161 | async with session.get( 162 | "https://api.spotify.com/v1/me/player/currently-playing", headers=oauth 163 | ) as response: 164 | status_code = response.status 165 | # 200 means user plays smth 166 | if status_code == 200: 167 | received = await response.json() 168 | if received["currently_playing_type"] == "track": 169 | to_insert["title"] = received["item"]["name"] 170 | to_insert["progress"] = ms_converter(received["progress_ms"]) 171 | to_insert["interpret"] = received["item"]["artists"][0]["name"] 172 | to_insert["duration"] = ms_converter( 173 | received["item"]["duration_ms"] 174 | ) 175 | if save_spam("spotify", False): 176 | stringy = ( 177 | "**[INFO]**\n\nEverything returned back to normal, the previous spotify issue has been " 178 | "resolved." 179 | ) 180 | await client.send_message(constants.LOG, stringy) 181 | else: 182 | if save_spam("spotify", True): 183 | # currently item is not passed when the user plays a podcast 184 | string = ( 185 | f"**[INFO]**\n\nThe playback {received['currently_playing_type']} didn't gave me any " 186 | f"additional information, so I skipped updating the bio." 187 | ) 188 | await client.send_message(constants.LOG, string) 189 | # 429 means flood limit, we need to wait 190 | elif status_code == 429: 191 | to_wait = response.headers["Retry-After"] 192 | logger.error(f"Spotify, have to wait for {str(to_wait)}") 193 | await client.send_message( 194 | constants.LOG, 195 | f"**[WARNING]**\n\nI caught a spotify api limit. I shall sleep for " 196 | f"{str(to_wait)} seconds until I refresh again", 197 | ) 198 | skip = True 199 | await asyncio.sleep(int(to_wait)) 200 | # 204 means user plays nothing, since to_insert is false, we dont need to change anything 201 | elif status_code == 204: 202 | if save_spam("spotify", False): 203 | stringy = ( 204 | "**[INFO]**\n\nEverything returned back to normal, the previous spotify issue has been " 205 | "resolved." 206 | ) 207 | await client.send_message(constants.LOG, stringy) 208 | pass 209 | # 401 means our access token is expired, so we need to refresh it 210 | elif status_code == 401: 211 | data = { 212 | "client_id": constants.CLIENT_ID, 213 | "client_secret": constants.CLIENT_SECRET, 214 | "grant_type": "refresh_token", 215 | "refresh_token": database.return_refresh(), 216 | } 217 | 218 | async with session.post( 219 | "https://accounts.spotify.com/api/token", data=data 220 | ) as post_response: 221 | received = await post_response.json() 222 | # if a new refresh is token as well, we save it here 223 | try: 224 | database.save_refresh(received["refresh_token"]) 225 | except KeyError: 226 | pass 227 | database.save_token(received["access_token"]) 228 | # since we didnt actually update our status yet, lets do this without the 30 seconds wait 229 | skip = True 230 | # 502 means bad gateway, its an issue on spotify site which we can do nothing about. 30 seconds wait 231 | # shouldn't put too much pressure on the spotify server, so we are just going to notify the user once 232 | elif status_code == 502: 233 | if save_spam("spotify", True): 234 | string = ( 235 | f"**[WARNING]**\n\nSpotify returned a Bad gateway, which means they have a problem on their " 236 | f"servers. The bot will continue to run but may not update the bio for a short time." 237 | ) 238 | await client.send_message(constants.LOG, string) 239 | # 503 means service unavailable, its an issue on spotify site which we can do nothing about. 30 seconds wait 240 | # shouldn't put too much pressure on the spotify server, so we are just going to notify the user once 241 | elif status_code == 503: 242 | if save_spam("spotify", True): 243 | string = ( 244 | f"**[WARNING]**\n\nSpotify said that the service is unavailable, which means they have a " 245 | f"problem on their servers. The bot will continue to run but may not update the bio for a " 246 | f"short time." 247 | ) 248 | await client.send_message(constants.LOG, string) 249 | # 404 is a spotify error which isn't supposed to happen (since our URL is correct). Track the issue here: 250 | # https://github.com/spotify/web-api/issues/1280 251 | elif status_code == 404: 252 | if save_spam("spotify", True): 253 | string = f"**[INFO]**\n\nSpotify returned a 404 error, which is a bug on their side." 254 | await client.send_message(constants.LOG, string) 255 | # catch anything else 256 | else: 257 | await client.send_message( 258 | constants.LOG, 259 | "**[ERROR]**\n\nOK, so something went reeeally wrong with spotify. The bot " 260 | "was stopped.\nStatus code: " 261 | + str(status_code) 262 | + "\n\nText: " 263 | + await response.text(), 264 | ) 265 | logger.error( 266 | f"Spotify, error {str(status_code)}, text: {await response.text()}" 267 | ) 268 | # stop the whole program since I dont know what happens here and this is the safest thing we can do 269 | asyncio.get_running_loop().stop() 270 | # TELEGRAM 271 | try: 272 | # full needed, since we dont get a bio with the normal request 273 | full = await client(GetFullUserRequest("me")) 274 | bio = full.about 275 | # to_insert means we have a successful playback 276 | if to_insert: 277 | # putting our collected information's into nice variables 278 | title = to_insert["title"] 279 | interpret = to_insert["interpret"] 280 | progress = to_insert["progress"] 281 | duration = to_insert["duration"] 282 | # we need this variable to see if actually one of the bios is below the character limit 283 | new_bio = "" 284 | for bio in constants.BIOS: 285 | temp = bio.format( 286 | title=title, 287 | interpret=interpret, 288 | progress=progress, 289 | duration=duration, 290 | ) 291 | # we try to not ignore for telegrams character limit here 292 | if len(temp) < constants.LIMIT: 293 | # this is short enough, so we put it in the variable and break our for loop 294 | new_bio = temp 295 | break 296 | # if we have a bio, one bio was short enough 297 | if new_bio: 298 | # test if the user changed his bio to blank, we save it before we override 299 | if not bio: 300 | database.save_bio(bio) 301 | # test if the user changed his bio in the meantime, if yes, we save it before we override 302 | elif "🎶" not in bio: 303 | database.save_bio(bio) 304 | # test if the bio isn't the same, otherwise updating it would be stupid 305 | if not new_bio == bio: 306 | try: 307 | await client(UpdateProfileRequest(about=new_bio)) 308 | if save_spam("telegram", False): 309 | stringy = ( 310 | "**[INFO]**\n\nEverything returned back to normal, the previous telegram " 311 | "issue has been resolved." 312 | ) 313 | await client.send_message(constants.LOG, stringy) 314 | # this can happen if our LIMIT check failed because telegram counts emojis twice and python 315 | # doesnt. Refer to the constants file to learn more about this 316 | except AboutTooLongError: 317 | if save_spam("telegram", True): 318 | stringy = ( 319 | f"**[WARNING]**\n\nThe biography I tried to insert was too long. In order " 320 | f"to not let that happen again in the future, please read the part about OFFSET" 321 | f" in the constants. Anyway, here is the bio I tried to insert:\n\n{new_bio}" 322 | ) 323 | await client.send_message(constants.LOG, stringy) 324 | # if we dont have a bio, everything was too long, so we tell the user that 325 | if not new_bio: 326 | if save_spam("telegram", True): 327 | to_send = ( 328 | f"**[INFO]**\n\nThe current track exceeded the character limit, so the bio wasn't " 329 | f"updated.\n\n Track: {title}\nInterpret: {interpret}" 330 | ) 331 | await client.send_message(constants.LOG, to_send) 332 | # not to_insert means no playback 333 | else: 334 | if save_spam("telegram", False): 335 | stringy = ( 336 | "**[INFO]**\n\nEverything returned back to normal, the previous telegram issue has " 337 | "been resolved." 338 | ) 339 | await client.send_message(constants.LOG, stringy) 340 | old_bio = database.return_bio() 341 | # this means the bio is blank, so we save that as the new one 342 | if not bio: 343 | database.save_bio(bio) 344 | # this means an old playback is in the bio, so we change it back to the original one 345 | elif "🎶" in bio: 346 | await client(UpdateProfileRequest(about=database.return_bio())) 347 | # this means a new original is there, lets save it 348 | elif not bio == old_bio: 349 | database.save_bio(bio) 350 | # this means the original one we saved is still valid 351 | else: 352 | pass 353 | except FloodWaitError as e: 354 | to_wait = e.seconds 355 | logger.error(f"to wait for {str(to_wait)}") 356 | await client.send_message( 357 | constants.LOG, 358 | f"**[WARNING]**\n\nI caught a telegram api limit. I shall sleep " 359 | f"{str(to_wait)} seconds until I refresh again", 360 | ) 361 | skip = True 362 | await asyncio.sleep(int(to_wait)) 363 | # skip means a flood error stopped the whole program, no need to wait another 30 seconds after that 364 | if not skip: 365 | await asyncio.sleep(30) 366 | 367 | 368 | # little message that the bot was started 369 | async def startup(): 370 | await client.send_message( 371 | constants.LOG, "**[INFO]**\n\nUserbot was successfully started." 372 | ) 373 | 374 | 375 | # shutdown handler in case the bot goes nuts (again) 376 | @client.on(events.NewMessage(outgoing=True, pattern=constants.SHUTDOWN_COMMAND)) 377 | async def shutdown_handler(_): 378 | logger.error("Initiating shut down") 379 | await client.send_message( 380 | constants.LOG, "**[INFO]**\n\nShutdown was successfully initiated." 381 | ) 382 | # just so everything is saved - it should be anyway, but just to be sure 383 | database.save() 384 | # this stops the whole loop 385 | await client.disconnect() 386 | 387 | 388 | async def main(): 389 | await client.start() 390 | loop = asyncio.get_running_loop() 391 | async with ClientSession() as session: 392 | loop.create_task(work(session)) 393 | loop.create_task(startup()) 394 | await client.run_until_disconnected() 395 | 396 | 397 | if __name__ == "__main__": 398 | if not os.path.isfile("./database.json"): 399 | asyncio.run(setup(intro_printed)) 400 | # Reinitialize because json is only now generated 401 | database = Database() 402 | asyncio.run(main()) 403 | logger.error("Shut down complete") 404 | --------------------------------------------------------------------------------