commit b81681b9d7466379d907dca79c3fc85297244fcf Author: Alexandr Karpov Date: Mon Feb 20 13:56:01 2023 +0300 added podcast script diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d71613f --- /dev/null +++ b/.gitignore @@ -0,0 +1,229 @@ +.idea +.env + +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +staticfiles/ + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# Environments +.venv +venv/ +ENV/ + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + + +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + + +### Linux template +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + + +### VisualStudioCode template +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + + +# Provided default Pycharm Run/Debug Configurations should be tracked by git +# In case of local modifications made by Pycharm, use update-index command +# for each changed file, like this: +# git update-index --assume-unchanged .idea/akarpov.iml +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + + + +### Windows template +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db diff --git a/podcasts/.env.template b/podcasts/.env.template new file mode 100644 index 0000000..c518a04 --- /dev/null +++ b/podcasts/.env.template @@ -0,0 +1,4 @@ +YANDEX_TOKEN= +CHAT_ID= +BOT_TOKEN= +TELEGRAM_SERVER= diff --git a/podcasts/README.md b/podcasts/README.md new file mode 100644 index 0000000..73df3bd --- /dev/null +++ b/podcasts/README.md @@ -0,0 +1,28 @@ +# Podcast loader script + +Script to load current listening track from yandex music and send it to telegram chat. + +### Configuration +Obtain yandex music token - https://music-yandex-bot.ru/ + +Obtain telegram api id and hash for local telegram image - https://my.telegram.org/ + +### Installation +OPTIONAL: start local telegram bot server for file upload +```shell +$ docker run -d -p 8081:8081 --name=telegram-bot-api --restart=always -v telegram-bot-api-data:/var/lib/telegram-bot-api -e TELEGRAM_API_ID= -e TELEGRAM_API_HASH= aiogram/telegram-bot-api:latest +``` + +```shell +$ python3 -m venv venv +$ source venv/bin/activate +$ pip install -r requirement.txt +``` + +### Run +program runs via python-daemon +```shell +$ python3 podcasts.py +``` + +Note: can be modified to send all(unique) tracks, just remove ```if "podcast" in last_track.type``` check diff --git a/podcasts/podcasts.py b/podcasts/podcasts.py new file mode 100644 index 0000000..afad4c2 --- /dev/null +++ b/podcasts/podcasts.py @@ -0,0 +1,107 @@ +import asyncio +import os +import daemon +from io import BytesIO + +from time import sleep + +from aiogram import Bot +from aiogram.bot.api import TelegramAPIServer +from mutagen.easyid3 import EasyID3 +from mutagen.mp3 import MP3 +from mutagen.id3 import APIC, ID3, TORY +from pydub import AudioSegment +from yandex_music import Client, Track +from dotenv import load_dotenv + +load_dotenv(dotenv_path=".env") + +YANDEX_TOKEN = os.getenv("YANDEX_TOKEN") +CHAT_ID = os.getenv("CHAT_ID") +TOKEN = os.getenv("BOT_TOKEN") +TELEGRAM_SERVER = os.getenv("TELEGRAM_SERVER", default=None) + +if TELEGRAM_SERVER: + local_server = TelegramAPIServer.from_base(TELEGRAM_SERVER) + bot = Bot(TOKEN, server=local_server) +else: + bot = Bot(TOKEN) + + +client = Client(YANDEX_TOKEN).init() +latest_podcast = None +latest_sent = True +podcasts_listened = [] + + +with daemon.DaemonContext(): + while True: + try: + queues = client.queues_list() + last_queue = client.queue(queues[0].id) + + last_track_id = last_queue.get_current_track() + last_track: Track = last_track_id.fetch_track() + + if "podcast" in last_track.type: + if last_track_id not in podcasts_listened: + if last_track_id == latest_podcast and not latest_sent: + latest_sent = True + podcasts_listened.append(last_track_id) + + title = last_track.title + album = last_track.albums[0] + url = f"https://music.yandex.ru/track/{last_track.id}" + desc = last_track.short_description.split("\n")[0] + + last_track.download_cover(filename="cover.png") + img_path = os.path.abspath("cover.png") + + last_track.download(filename="file", codec="mp3") + orig_path = os.path.abspath("file") + path = os.path.abspath("file.mp3") + + AudioSegment.from_file(orig_path).export(path) + os.remove(orig_path) + + # set music meta + tag = MP3(path, ID3=ID3) + tag.tags.add( + APIC( + encoding=3, # 3 is for utf-8 + mime="image/png", # image/jpeg or image/png + type=3, # 3 is for the cover image + desc="Cover", + data=open(img_path, "rb").read(), + ) + ) + tag.tags.add(TORY(text=str(album.year))) + tag.save() + tag = EasyID3(path) + + tag["title"] = title + tag["album"] = album.title + + tag.save() + + with open(path, "rb") as tmp: + obj = BytesIO(tmp.read()) + obj.name = f"{title}.mp3" + loop = asyncio.get_event_loop() + coroutine = bot.send_audio( + chat_id=CHAT_ID, + audio=obj, + caption=f"{title} - {album.title}\n{desc}\n\n{url}", + title=title, + performer=album.title, + ) + loop.run_until_complete(coroutine) + + else: + latest_podcast = last_track_id + latest_sent = False + except BaseException as e: + loop = asyncio.get_event_loop() + coroutine = bot.send_message(CHAT_ID, text=str(e)) + loop.run_until_complete(coroutine) + sleep(5 * 60) diff --git a/podcasts/requirement.txt b/podcasts/requirement.txt new file mode 100644 index 0000000..ee95e13 --- /dev/null +++ b/podcasts/requirement.txt @@ -0,0 +1,23 @@ +aiofiles==23.1.0 +aiogram==2.25.1 +aiohttp==3.8.4 +aiosignal==1.3.1 +async-timeout==4.0.2 +attrs==22.2.0 +Babel==2.9.1 +certifi==2022.12.7 +charset-normalizer==3.0.1 +frozenlist==1.3.3 +idna==3.4 +magic-filter==1.0.9 +multidict==6.0.4 +mutagen==1.45.1 +pydub==0.25.1 +PySocks==1.7.1 +python-dotenv==0.21.1 +pytz==2022.7.1 +requests==2.28.2 +urllib3==1.26.14 +yandex-music==2.0.1 +yarl==1.8.2 +python-daemon==2.3.2