├── runtime.txt ├── Procfile ├── requirements.txt ├── .gitattributes ├── heroku.yml ├── app.json ├── generate.py ├── constants.py ├── LICENSE ├── .gitignore ├── README.md └── bot.py /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.10.8 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: python3 bot.py 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | telethon 2 | pyaesni 3 | telethon-tgcrypto 4 | requests 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | setup: 2 | addons: 3 | - plan: heroku-postgresql 4 | as: DATABASE 5 | config: 6 | WEBHOOK: ANYTHING, 7 | API_ID: "", 8 | API_HASH: "", 9 | APP_HASH: "", 10 | CLIENT_ID: "", 11 | CLIENT_SECRET: "", 12 | SHUTDOWN_COMMAND : "", 13 | KEY: "" 14 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Spotify Bio Changer", 3 | "description": "Telegram Spotify Biography Changer", 4 | "keywords": [ 5 | "telegram", 6 | "spotify", 7 | "music", 8 | "good" 9 | ], 10 | "repository": "https://github.com/c311at/spotify_telegram_bio_updater", 11 | "env": { 12 | "WEBHOOK": { 13 | "description": "Setting this to ANYTHING will enable webhooks when in env mode", 14 | "value": "ANYTHING" 15 | }, 16 | "API_ID": { 17 | "description": "Get this value from https://my.telegram.org", 18 | "value": "" 19 | }, 20 | "API_HASH": { 21 | "description": "Get this value from https://my.telegram.org", 22 | "value": "" 23 | }, 24 | "CLIENT_ID": { 25 | "description": "spotify client ID", 26 | "value": "" 27 | }, 28 | "CLIENT_SECRET": { 29 | "description": "spotify client scret key", 30 | "value": "" 31 | }, 32 | "SHUTDOWN_COMMAND": { 33 | "description": "stop command", 34 | "value": "\/\/stop", 35 | "required": false 36 | }, 37 | "KEY": { 38 | "description": "Key :)", 39 | "value": "🎶", 40 | "required": false 41 | } 42 | }, 43 | "addons": [], 44 | "buildpacks": [ 45 | { 46 | "url": "heroku/python" 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /generate.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | 4 | from aiohttp import ClientSession 5 | 6 | from constants import CLIENT_ID, CLIENT_SECRET, INITIAL_BIO, INITIAL_TOKEN 7 | 8 | body = { 9 | "client_id": CLIENT_ID, 10 | "client_secret": CLIENT_SECRET, 11 | "grant_type": "authorization_code", 12 | "redirect_uri": "https://example.com/callback", 13 | "code": INITIAL_TOKEN, 14 | } 15 | 16 | 17 | async def generate(): 18 | async with ClientSession() as session: 19 | async with session.post( 20 | "https://accounts.spotify.com/api/token", data=body 21 | ) as post_response: 22 | save = await post_response.json() 23 | to_create = { 24 | "bio": INITIAL_BIO, 25 | "access_token": save["access_token"], 26 | "refresh_token": save["refresh_token"], 27 | "telegram_spam": False, 28 | "spotify_spam": False, 29 | } 30 | with open("./database.json", "w") as outfile: 31 | json.dump(to_create, outfile, indent=4, sort_keys=True) 32 | 33 | 34 | def main(): 35 | loop = asyncio.get_event_loop() 36 | loop.run_until_complete(generate()) 37 | loop.close() 38 | 39 | 40 | if __name__ == "__main__": 41 | main() 42 | -------------------------------------------------------------------------------- /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 ot 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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, M.Furkan 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # App specific 2 | *.session* 3 | database.json 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | pip-wheel-metadata/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # celery beat schedule file 97 | celerybeat-schedule 98 | 99 | # SageMath parsed files 100 | *.sage.py 101 | 102 | # Environments 103 | .env 104 | .venv 105 | env/ 106 | venv/ 107 | ENV/ 108 | env.bak/ 109 | venv.bak/ 110 | 111 | # Spyder project settings 112 | .spyderproject 113 | .spyproject 114 | 115 | # Rope project settings 116 | .ropeproject 117 | 118 | # mkdocs documentation 119 | /site 120 | 121 | # mypy 122 | .mypy_cache/ 123 | .dmypy.json 124 | dmypy.json 125 | 126 | # Pyre type checker 127 | .pyre/ 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # installation via using Heroku 2 | 3 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy/?template=https://github.com/muhammedfurkan/Spotify-Telegram-Bio-Updater) 4 | 5 | 6 | # spotify telegram bio updater 7 | 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. 8 | 9 | ## Setup 10 | 11 | If you follow this steps, you can use this project easily for yourself. 12 | 13 | 0. Ensure you have python 3.6+ installed and then install telethon and request via pip: `pip install telethon requests` 14 | 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). 15 | 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). 16 | 3. 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=https%3A%2F%2Fexample.com%2Fcallback&scope=user-read-playback-state%20user-read-currently-playing 17 | 4. After you grant permission, you get redirected to https://example.com/callback?code=_. Copy everything after the code, this is you initial token. 18 | 5. 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. 19 | 6. 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 ;) 20 | 7. Now you can run [generate.py](/generate.py). This will generate a json file named database. 21 | 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. 22 | 9. Now you are really done. 23 | 24 | ## Important Information 25 | 26 | 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). 27 | 28 | ## Warning 29 | 30 | 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. 31 | 32 | ## But I want to 33 | 34 | Great news. Just change the KEY variable in [constants](/constants.py) file. Don't ever use your new KEY in your biographies though! 35 | 36 | ## Issues? Need help? Want to tell me something? 37 | 38 | Well, just create an issue here. You can also ping me on [telegram](https://t.me/By_Azade). 39 | -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import logging 4 | 5 | import requests 6 | from telethon import TelegramClient, events 7 | from telethon.errors import AboutTooLongError, FloodWaitError 8 | from telethon.tl.functions.account import UpdateProfileRequest 9 | from telethon.tl.functions.users import GetFullUserRequest 10 | 11 | from constants import (API_HASH, API_ID, BIOS, CLIENT_ID, CLIENT_SECRET, LIMIT, 12 | LOG, SHUTDOWN_COMMAND) 13 | 14 | device_model = "spotify_bot" 15 | version = "1.5" 16 | system_version, app_version = version, version 17 | client = TelegramClient( 18 | "spotify", 19 | API_ID, 20 | API_HASH, 21 | device_model=device_model, 22 | system_version=system_version, 23 | app_version=app_version, 24 | ) 25 | logging.basicConfig( 26 | level=logging.ERROR, 27 | filename="log.log", 28 | format="[%(levelname) 5s/%(asctime)s] %(name)s: %(message)s", 29 | ) 30 | logger = logging.getLogger(__name__) 31 | 32 | 33 | def ms_converter(millis): 34 | millis = int(millis) 35 | seconds = (millis / 1000) % 60 36 | seconds = int(seconds) 37 | if str(seconds) == "0": 38 | seconds = "00" 39 | if len(str(seconds)) == 1: 40 | seconds = "0" + str(seconds) 41 | minutes = (millis / (1000 * 60)) % 60 42 | minutes = int(minutes) 43 | return str(minutes) + ":" + str(seconds) 44 | 45 | 46 | class Database: 47 | def __init__(self): 48 | try: 49 | self.db = json.load(open("./database.json")) 50 | except FileNotFoundError: 51 | print("You need to run generate.py first, please read the Readme.") 52 | loop.stop() 53 | 54 | def save_token(self, token): 55 | self.db["access_token"] = token 56 | self.save() 57 | 58 | def save_refresh(self, token): 59 | self.db["refresh_token"] = token 60 | self.save() 61 | 62 | def save_bio(self, bio): 63 | self.db["bio"] = bio 64 | self.save() 65 | 66 | def save_spam(self, which, what): 67 | self.db[which + "_spam"] = what 68 | 69 | def return_token(self): 70 | return self.db["access_token"] 71 | 72 | def return_refresh(self): 73 | return self.db["refresh_token"] 74 | 75 | def return_bio(self): 76 | return self.db["bio"] 77 | 78 | def return_spam(self, which): 79 | return self.db[which + "_spam"] 80 | 81 | def save(self): 82 | with open("./database.json", "w") as outfile: 83 | json.dump(self.db, outfile, indent=4, sort_keys=True) 84 | 85 | 86 | database = Database() 87 | 88 | # to stop unwanted spam, we sent these type of message only once. So we have a variable in our database which we check 89 | # for in return_info. When we send a message, we set this variable to true. After a successful update 90 | # (or a closing of spotify), we reset that variable to false. 91 | 92 | 93 | def save_spam(which, what): 94 | # see below why 95 | 96 | # this is if False is inserted, so if spam = False, so if everything is good. 97 | if not what: 98 | # if it wasn't normal before, we proceed 99 | if database.return_spam(which): 100 | # we save that it is normal now 101 | database.save_spam(which, False) 102 | # we return True so we can test against it and if it this function returns, we can send a fitting message 103 | return True 104 | # this is if True is inserted, so if spam = True, so if something went wrong 105 | else: 106 | # if it was normal before, we proceed 107 | if not database.return_spam(which): 108 | # we save that it is not normal now 109 | database.save_spam(which, True) 110 | # we return True so we can send a message 111 | return True 112 | # if True wasn't returned before, we can return False now so our test fails and we dont send a message 113 | return False 114 | 115 | 116 | async def work(): 117 | while True: 118 | # SPOTIFY 119 | skip = False 120 | to_insert = {} 121 | oauth = {"Authorization": "Bearer " + database.return_token()} 122 | r = requests.get( 123 | "https://api.spotify.com/v1/me/player/currently-playing", headers=oauth 124 | ) 125 | # 200 means user plays smth 126 | if r.status_code == 200: 127 | received = r.json() 128 | if received["currently_playing_type"] == "track": 129 | to_insert["title"] = received["item"]["name"] 130 | to_insert["progress"] = ms_converter(received["progress_ms"]) 131 | to_insert["interpret"] = received["item"]["artists"][0]["name"] 132 | to_insert["duration"] = ms_converter( 133 | received["item"]["duration_ms"]) 134 | if save_spam("spotify", False): 135 | stringy = ( 136 | "**[INFO]**\n\nEverything returned back to normal, the previous spotify issue has been " 137 | "resolved." 138 | ) 139 | await client.send_message(LOG, stringy) 140 | else: 141 | if save_spam("spotify", True): 142 | # currently item is not passed when the user plays a podcast 143 | string = ( 144 | f"**[INFO]**\n\nThe playback {received['currently_playing_type']} didn't gave me any " 145 | f"additional information, so I skipped updating the bio." 146 | ) 147 | await client.send_message(LOG, string) 148 | # 429 means flood limit, we need to wait 149 | elif r.status_code == 429: 150 | to_wait = r.headers["Retry-After"] 151 | logger.error(f"Spotify, have to wait for {str(to_wait)}") 152 | await client.send_message( 153 | LOG, 154 | f"**[WARNING]**\n\nI caught a spotify api limit. I shall sleep for " 155 | f"{str(to_wait)} seconds until I refresh again", 156 | ) 157 | skip = True 158 | await asyncio.sleep(int(to_wait)) 159 | # 204 means user plays nothing, since to_insert is false, we dont need to change anything 160 | elif r.status_code == 204: 161 | if save_spam("spotify", False): 162 | stringy = ( 163 | "**[INFO]**\n\nEverything returned back to normal, the previous spotify issue has been " 164 | "resolved." 165 | ) 166 | await client.send_message(LOG, stringy) 167 | pass 168 | # 401 means our access token is expired, so we need to refresh it 169 | elif r.status_code == 401: 170 | data = { 171 | "client_id": CLIENT_ID, 172 | "client_secret": CLIENT_SECRET, 173 | "grant_type": "refresh_token", 174 | "refresh_token": database.return_refresh(), 175 | } 176 | r = requests.post( 177 | "https://accounts.spotify.com/api/token", data=data) 178 | received = r.json() 179 | # if a new refresh is token as well, we save it here 180 | try: 181 | database.save_refresh(received["refresh_token"]) 182 | except KeyError: 183 | pass 184 | database.save_token(received["access_token"]) 185 | # since we didnt actually update our status yet, lets do this without the 30 seconds wait 186 | skip = True 187 | # 502 means bad gateway, its an issue on spotify site which we can do nothing about. 30 seconds wait shouldn't 188 | # put too much pressure on the spotify server, so we are just going to notify the user once 189 | elif r.status_code == 502: 190 | if save_spam("spotify", True): 191 | string = ( 192 | f"**[WARNING]**\n\nSpotify returned a Bad gateway, which means they have a problem on their " 193 | f"servers. The bot will continue to run but may not update the bio for a short time." 194 | ) 195 | await client.send_message(LOG, string) 196 | # 503 means service unavailable, its an issue on spotify site which we can do nothing about. 30 seconds wait 197 | # shouldn't put too much pressure on the spotify server, so we are just going to notify the user once 198 | elif r.status_code == 503: 199 | if save_spam("spotify", True): 200 | string = ( 201 | f"**[WARNING]**\n\nSpotify said that the service is unavailable, which means they have a " 202 | f"problem on their servers. The bot will continue to run but may not update the bio for a " 203 | f"short time." 204 | ) 205 | await client.send_message(LOG, string) 206 | # 404 is a spotify error which isn't supposed to happen (since our URL is correct). Track the issue here: 207 | # https://github.com/spotify/web-api/issues/1280 208 | elif r.status_code == 404: 209 | if save_spam("spotify", True): 210 | string = f"**[INFO]**\n\nSpotify returned a 404 error, which is a bug on their side." 211 | await client.send_message(LOG, string) 212 | # catch anything else 213 | else: 214 | await client.send_message( 215 | LOG, 216 | "**[ERROR]**\n\nOK, so something went reeeally wrong with spotify. The bot " 217 | "was stopped.\nStatus code: " 218 | + str(r.status_code) 219 | + "\n\nText: " 220 | + r.text, 221 | ) 222 | logger.error( 223 | f"Spotify, error {str(r.status_code)}, text: {r.text}") 224 | # stop the whole program since I dont know what happens here and this is the safest thing we can do 225 | loop.stop() 226 | # TELEGRAM 227 | try: 228 | # full needed, since we dont get a bio with the normal request 229 | full = await client(GetFullUserRequest("me")) 230 | bio = full.full_user.about 231 | # to_insert means we have a successful playback 232 | if to_insert: 233 | # putting our collected information's into nice variables 234 | title = to_insert["title"] 235 | interpret = to_insert["interpret"] 236 | progress = to_insert["progress"] 237 | duration = to_insert["duration"] 238 | # we need this variable to see if actually one of the bios is below the character limit 239 | new_bio = "" 240 | for bio in BIOS: 241 | temp = bio.format( 242 | title=title, 243 | interpret=interpret, 244 | progress=progress, 245 | duration=duration, 246 | ) 247 | # we try to not ignore for telegrams character limit here 248 | if len(temp) < LIMIT: 249 | # this is short enough, so we put it in the variable and break our for loop 250 | new_bio = temp 251 | break 252 | # if we have a bio, one bio was short enough 253 | if new_bio: 254 | # test if the user changed his bio to blank, we save it before we override 255 | if not bio: 256 | database.save_bio(bio) 257 | # test if the user changed his bio in the meantime, if yes, we save it before we override 258 | elif "🎶" not in bio: 259 | database.save_bio(bio) 260 | # test if the bio isn't the same, otherwise updating it would be stupid 261 | if not new_bio == bio: 262 | try: 263 | await client(UpdateProfileRequest(about=new_bio)) 264 | if save_spam("telegram", False): 265 | stringy = ( 266 | "**[INFO]**\n\nEverything returned back to normal, the previous telegram " 267 | "issue has been resolved." 268 | ) 269 | await client.send_message(LOG, stringy) 270 | # this can happen if our LIMIT check failed because telegram counts emojis twice and python 271 | # doesnt. Refer to the constants file to learn more about this 272 | except AboutTooLongError: 273 | if save_spam("telegram", True): 274 | stringy = ( 275 | f"**[WARNING]**\n\nThe biography I tried to insert was too long. In order " 276 | f"to not let that happen again in the future, please read the part about OFFSET " 277 | f"in the constants. Anyway, here is the bio I tried to insert:\n\n{new_bio}" 278 | ) 279 | await client.send_message(LOG, stringy) 280 | # if we dont have a bio, everything was too long, so we tell the user that 281 | if not new_bio: 282 | if save_spam("telegram", True): 283 | to_send = ( 284 | f"**[INFO]**\n\nThe current track exceeded the character limit, so the bio wasn't " 285 | f"updated.\n\n Track: {title}\nInterpret: {interpret}" 286 | ) 287 | await client.send_message(LOG, to_send) 288 | # not to_insert means no playback 289 | else: 290 | if save_spam("telegram", False): 291 | stringy = ( 292 | "**[INFO]**\n\nEverything returned back to normal, the previous telegram issue has " 293 | "been resolved." 294 | ) 295 | await client.send_message(LOG, stringy) 296 | old_bio = database.return_bio() 297 | # this means the bio is blank, so we save that as the new one 298 | if not bio: 299 | database.save_bio(bio) 300 | # this means an old playback is in the bio, so we change it back to the original one 301 | elif "🎶" in bio: 302 | await client(UpdateProfileRequest(about=database.return_bio())) 303 | # this means a new original is there, lets save it 304 | elif not bio == old_bio: 305 | database.save_bio(bio) 306 | # this means the original one we saved is still valid 307 | else: 308 | pass 309 | except FloodWaitError as e: 310 | to_wait = e.seconds 311 | logger.error(f"to wait for {str(to_wait)}") 312 | await client.send_message( 313 | LOG, 314 | f"**[WARNING]**\n\nI caught a telegram api limit. I shall sleep " 315 | f"{str(to_wait)} seconds until I refresh again", 316 | ) 317 | skip = True 318 | await asyncio.sleep(int(to_wait)) 319 | # skip means a flood error stopped the whole program, no need to wait another 30 seconds after that 320 | if not skip: 321 | await asyncio.sleep(30) 322 | 323 | 324 | # little message that the bot was started 325 | async def startup(): 326 | await client.send_message(LOG, "**[INFO]**\n\nUserbot was successfully started.") 327 | 328 | 329 | # shutdown handler in case the bot goes nuts (again) 330 | @client.on(events.NewMessage(outgoing=True, pattern=SHUTDOWN_COMMAND)) 331 | async def shutdown_handler(_): 332 | logger.error("SHUT DOWN") 333 | await client.send_message(LOG, "**[INFO]**\n\nShutdown was successfully initiated.") 334 | # just so everything is saved - it should be anyway, but just to be sure 335 | database.save() 336 | # this stops the whole loop 337 | await client.disconnect() 338 | 339 | 340 | client.start() 341 | loop = asyncio.get_event_loop() 342 | loop.create_task(work()) 343 | loop.create_task(startup()) 344 | client.run_until_disconnected() 345 | --------------------------------------------------------------------------------