Di bawah tenda klien bot Yandex.Music

pengantar


Halo, Habr! Sekali lagi, saya dengan artikel kedua yang mempengaruhi API Yandex.Music. Kasus ini direncanakan dan disebutkan dalam artikel pertama .

Tangan terulur, selesai. Hari ini saya akan berbicara tentang hal-hal menarik, menurut saya, momen yang hadir dalam basis kode bot Telegram saya, yang memposisikan dirinya sebagai klien musik yang lengkap. Kami juga akan menyentuh API pengenalan musik Yandex.

Sebelum memulai cerita selangkah demi selangkah dari implementasi suatu hal tertentu, akan bermanfaat untuk memiliki gagasan tentang bot itu sendiri dan kemampuan fungsionalnya.

Demo video klien


Pada bagian utama saya akan berbicara tentang hal berikut:

  1. Masuk ke akun Anda melalui situs di Halaman GitHub (mengapa dan mengapa).
  2. Format data, kemasannya, dan digunakan dalam data tombol.
  3. Perbarui perutean, versi data, konteks melempar ke penangan.
  4. Jasa:
    • Reload layanan di Telegram.
    • Layanan "langganan" untuk menerima trek dengan mengirimkan status unduhan.
  5. Implementasi caching query yang paling sederhana dan paling elegan.
  6. Pengakuan trek melalui pesan suara dan bagaimana umumnya muncul di bot.
  7. Catatan kecil.

Jika Anda tertarik pada setidaknya satu poin - selamat datang ke kucing.
Bot bekerja dengan pengguna yang telah masuk ke akun mereka, tetapi tidak. Semuanya berputar di sekitar empat kelas utama layanan: album, artis, daftar putar, dan trek. Semua data entitas didukung, ada pagination dengan pemilihan halaman. Untuk berinteraksi dengan mereka, pengguna yang berwenang dapat menggunakan menu pribadi mereka, yang meliputi: daftar putar cerdas, daftar putar "Saya suka" dan daftar putar mereka sendiri. Untuk pengguna yang tidak sah, pencarian tersedia - mengirim permintaan pencarian sebagai pesan biasa. Sekarang hanya ada daftar kering dari apa lagi yang ada: menerima trek dan objek lain melalui tautan langsung, menerima lirik, kemampuan untuk menyukai / tidak menyukai trek dan artis, melihat trek yang sama, mengenali trek melalui pesan suara dan banyak lagi.

Bagian utama


1. Masuk ke akun


Awalnya, bot tidak direncanakan untuk bekerja tanpa otorisasi, oleh karena itu, bahkan selama desain, diputuskan bagaimana otorisasi harus terjadi - melalui situs webnya . Argumen utama adalah keamanan dan transparansi. Dalam kasus apa pun tidak ingin menerima login dan kata sandi pengguna dalam pesan yang jelas. Dan otorisasi dari komputer pengguna adalah dasar. Oleh karena itu, sebuah situs ditulis untuk otorisasi pada Bereaksi . Ini adalah bentuk otorisasi dengan pengalihan kembali ke bot. Sepotong dengan otorisasi dan pemrosesan captcha diambil dari perpustakaan dengan Python dan ditulis ulang dalam JavaScript .

Untuk otorisasi, token OAuth yang diterima digunakan, yang ditransfer kembali ke bot melalui tautan dalam.

Tautan terakhir untuk pengalihan terlihat seperti ini: t.me/music_yandex_bot?start={oauth_token}

Saya menginginkan yang terbaik, tetapi para pengguna tidak memiliki kepercayaan. Hanya setiap pengguna kesepuluh yang diotorisasi (pada saat itu wajib). Karena itu, dengan pembaruan berikut, saya harus menjelaskan mengapa Anda harus percaya. Semua item tentang HTTPS , secara eksklusif tentang permintaan dari sisi klien dan kode sumber terbuka diterbitkan di situs untuk otorisasi dan digandakan dalam pesan selamat datang di bot. Menjadi lebih baik.

Juga, di antara faktor-faktor dari otorisasi yang rendah, tidak dapat diaksesnya mirror untuk t.me di Rusia ternyata dan mendukung hanya pengguna dengan berlangganan (lebih lanjut tentang ini dalam catatan, paragraf 7).

Item pertama diselesaikan menggunakan URI ( tg: // ), dan dalam keadaan darurat, sudah ada tautan ke mirror (jika pengalihan otomatis tidak berfungsi, dan tombol tidak membantu), dan yang kedua lagi di item 7.


2. Format data


Untuk bot ini, saya memutuskan untuk pertama kalinya menggunakan NoSQL DB - MongoDB . Dipahami: ketika menanamkan dokumen, kapan tidak. Suka bekerja dengan mongoengine. Tetapi saya benar-benar tidak ingin menyimpan semua data tombol di rumah, dan klien hanya memiliki ID rekaman dalam database. Saya takut dengan jumlah data, tetapi saya ingin mengambil server Mongo gratis dengan batas 512 MB untuk penyimpanan data. Menghasilkan rekaman yang indah jika data cocok dengan beberapa tombol dan untuk membersihkan yang sudah usang jauh lebih sulit daripada menyimpan semua yang ada di tombol itu sendiri. Setelah menganalisis ukuran data yang akan disimpan, saya menyimpulkan bahwa itu cocok.

Pada awalnya, saya hanya menggunakan JSON, tapi dia dengan cepat menolaknya ketika dia berlari ke batas. Di Telegram, isi data tombol hanya bisa tidak lebih dari 64 byte di UTF-8 .

Oleh karena itu, dengan permintaan teman, saya mulai melihat paket dari modul struct . Jadi jenis pertanyaan lahir, format primitif, pengemasan dan pembongkaran. Sekarang digunakan di bot benar-benar di mana-mana.

Formatnya sangat sederhana. Byte pertama adalah tipe, byte kedua adalah versi. Yang lainnya adalah data untuk jenis tertentu. Jenis disimpan sebagai Enum, memiliki ID , yang merupakan byte pertama. Selain ID , setiap jenis memiliki format untuk mengemas dan membongkar data. Misalnya: ketik SHOW_TRACK_MENUformat siapa yang memiliki nilai "s?", di mana "s" adalah pengidentifikasi unik trek, dan "?" - Apakah trek memiliki teks.

Pada trek digunakan tipe string karena: pertama, ID trek dapat merupakan gabungan ID dan album ID trek melalui titik dua, dan kedua, itu mungkin UUID . Trek dengan UUID - trek yang dimuat sendiri, hanya tersedia untuk pengguna yang mengunduhnya.

Karena data tidak selalu sesuai dengan format, misalnya, ID trek yang sama dapat diwakili hanya dengan angka, sebelum pengemasannya harus dilemparkan ke dalam jenis untuk format. Dalam hal ini, s. Oleh karena itu, di dalam kelas terdapat metode yang menormalkan data yang ditransfer untuk pengemasan, agar tidak melakukannya sendiri saat meneruskan ke konstruktor.

String bersifat swasembada dan dapat menunjukkan panjangnya saat pengemasan dan memperhitungkan panjang ini saat membongkar.

Dukungan untuk versi yang lebih lama tidak direncanakan, jadi pengecualian dilemparkan jika versi tidak cocok. Saat memproses pembaruan, yang akan saya bahas pada paragraf berikutnya, logika yang diperlukan disebut.

Karena Telegram makan secara eksklusif UTF-8 , data yang dikemas dikodekan dalam base85 . Ya, saya kehilangan kecepatan di sini dan menyimpan ukuran terkecil tanpa menggunakan base64 , tetapi mengingat data kecil, saya menganggap menggunakan base85 yang sesuai.

Sumber. File callback_data.py
import struct
import base64

from ext.query_types import QueryType


class BadDataVersion(Exception):
    pass


class CallbackData:
    ACTUAL_VERSION = 7
    BASE_FORMAT = '<BB'

    def __init__(self, type_: QueryType, data=None, version=None):
        self.type = type_
        self.version = version or CallbackData.ACTUAL_VERSION
        self.data = data

        if self.data is not None and not isinstance(self.data, list):
            self.data = [self.data]

        if self.data is not None:
            self.data = self._normalize_data_to_format(self.data)

    def __repr__(self):
        return f'<CallbackData> Type: {self.type} Version: {self.version} Data: {self.data}'

    def _normalize_data_to_format(self, data, bytes_object=False):
        normalized_data = data.copy()
        for i, c in enumerate(self.type.format):
            cast = str
            if c.lower() in 'bhilqn':
                cast = int
            elif c in 'efd':
                cast = float
            elif c == '?':
                cast = bool

            casted = cast(data[i])
            if bytes_object and cast == str:
                casted = casted.encode('utf-8')
            normalized_data[i] = casted

        return normalized_data

    @staticmethod
    def decode_type(callback_data):
        decoded = base64.b85decode(callback_data.encode('utf-8'))
        type_, version = struct.unpack(CallbackData.BASE_FORMAT, decoded[:2])

        if CallbackData.ACTUAL_VERSION != version:
            raise BadDataVersion()

        return QueryType(type_), version, decoded

    @classmethod
    def decode(cls, type_, version, decoded):
        start, data = 2, []

        if start < len(decoded):
            format_iter = iter(type_.format)

            while True:
                if start >= len(decoded):
                    break

                format_ = next(format_iter, type_.format[-1])

                decode_str = format_ in 'ps'
                if decode_str:
                    # struct.calcsize('b') = 1
                    length = list(struct.unpack('b', decoded[start: start + 1]))[0]
                    start += 1

                    format_ = f'{length}{format_}'

                step = struct.calcsize(format_)

                unpacked = list(struct.unpack(f'{format_}', decoded[start: start + step]))
                if decode_str:
                    unpacked[0] = unpacked[0].decode('UTF-8')

                data += unpacked
                start += step

        return cls(type_, data if data else None, version)

    def encode(self):
        encode = struct.pack(self.BASE_FORMAT, self.type.value, self.version)

        if self.data is not None:
            format_iter = iter(self.type.format)
            normalized_data = self._normalize_data_to_format(self.data, bytes_object=True)

            for data in normalized_data:
                format_ = next(format_iter, self.type.format[-1])

                if format_ in 'ps':
                    #        .  'b'  
                    # -      ,    > 36 
                    encode += struct.pack('b', len(data))
                    encode += struct.pack(f'{len(data)}{format_}', data)
                else:
                    encode += struct.pack(f'{format_}', data)

        return base64.b85encode(encode).decode('utf-8')



3. Pembaruan dan konteks perutean


Proyek ini menggunakan pustaka python-telegram-bot untuk bekerja dengan API Bot Telegram . Itu sudah memiliki sistem untuk mendaftarkan penangan untuk jenis pembaruan tertentu yang telah tiba, filter untuk ekspresi reguler, perintah, dan sebagainya. Tetapi, mengingat format data dan tipe saya sendiri, saya harus mewarisi dari TelegramHandler dan mengimplementasikan Handler saya .

Pembaruan dan konteks diteruskan ke setiap penangan melalui argumen. Dalam hal ini, saya memiliki konteks sendiri dan di Handler'esedang dibentuk, dan ini adalah: menerima dan / atau menambahkan pengguna ke database, memeriksa relevansi token untuk mendapatkan akses ke musik, menginisialisasi klien Yandex.Music, tergantung pada status otorisasi dan ketersediaan langganan.

Lebih jauh dari Handler'a saya ada penangan yang lebih spesifik, misalnya, CallbackQueryHandler . Dengannya, pawang terdaftar untuk jenis pembaruan tertentu (tipe saya, dengan format data). Untuk memeriksa apakah pembaruan ini cocok untuk penangan saat ini, tidak semua data dibongkar, tetapi hanya dua byte pertama. Pada tahap ini, kebutuhan untuk meluncurkan panggilan balik diverifikasi. Hanya jika meluncurkan panggilan balik diperlukan - data benar-benar dibongkar dan ditransfer sebagai kwargske penangan akhir. Segera ada pengiriman data analitik ke ChatBase .

Registrasi dilakukan secara berurutan, dan prioritasnya lebih tinggi untuk siapa yang akan didaftarkan lebih awal (pada kenyataannya, seperti dalam rute Django , dan dalam proyek lain). Oleh karena itu, mendaftarkan penangan untuk versi usang adalah yang pertama di antara penangan CallBackQuery .

Logika pemrosesan versi yang lama adalah sederhana - beri tahu pengguna tentang ini dan kirim data yang diperbarui, jika mungkin.

4. Layanan


Semua layanan diinisialisasi ketika bot diluncurkan dalam satu kelas kontrol, yang kemudian digunakan secara universal di mana saja di bot ( DJ ).

Setiap layanan memiliki ThreadPoolExecutor sendiri dengan sejumlah pekerja tertentu yang menjadi tugasnya.

Memuat ulang trek di Telegram


Saat ini, layanan ini belum ditulis ulang untuk Bot Pengguna untuk memotong batas pada ukuran file yang diunduh di Telegram. Ternyata, di Yandex.Music ada file yang lebih besar dari 50 mb - podcast.

Layanan memeriksa ukuran file dan, jika kelebihan, melemparkan peringatan kepada pengguna. Berkat sistem caching yang dijelaskan dalam paragraf 5, ini memeriksa ketersediaan dan penerimaan lirik. Lagu-lagu juga di-cache. Hash file disimpan dalam database. Jika ada, audio dengan cache yang dikenal sedang dikirim.

Dengan tidak adanya file dalam database, tautan langsung diterima dari Yandex.Music. Meskipun saat ini, pengguna tidak memiliki kemampuan untuk mengubah pengaturan kualitas, tetapi semua diatur ke nilai standar. File dicari oleh bitrate dan codec dari pengaturan pengguna.

File dan sampulnya diunduh sebagai tempfile.TentaraFile () , setelah itu mereka diunggah ke Telegram. Patut dicatat bahwa TG tidak selalu dengan benar mengenali durasi trek, tetapi saya biasanya diam tentang artis dan judulnya. Oleh karena itu, data ini diambil dari Yandex, untungnya, dimungkinkan untuk mengirimkannya ke troli.

Ketika file audio dikirim oleh layanan ini, selesai_callback () dipanggil , menandakan layanan dengan berlangganan tentang akhir pengunduhan.

Layanan berlangganan untuk menerima trek dan mengirim status unduhan


Trek tidak dimuat secara instan. Mungkin saja beberapa pengguna meminta trek yang sama. Dalam hal ini, pengguna yang meminta trek terlebih dahulu adalah penggagas dan pemilik trek. Saat memuat ulang lebih dari satu detik, status unduhan dimulai: "Pengguna mengirim pesan suara". Pengguna lain yang meminta trek yang sama adalah pelanggan tetap. Mereka, seperti pemiliknya, dikirim status unduhan sekali setiap ~ 4 detik sehingga pesan tidak mengganggu (status hang selama 5 detik). Segera setelah unduhan trek untuk pemilik selesai, selesai_callback () dipanggil dari layanan di atas. Setelah itu, semua pelanggan dihapus dari daftar status dan menerima trek yang diunduh.

Dalam solusi arsitektur, pemilik trek juga pelanggan, tetapi dengan tanda tertentu, karena cara pengiriman trek berbeda.

5. Caching kueri


Seperti yang kita ingat dari artikel terakhir saya, permintaan ke Yandex.Music API sangat berat. Daftar trek bisa 3 atau 5 mb. Apalagi hanya ada banyak permintaan. Dengan setiap pemrosesan pembaruan, setidaknya 2 permintaan dikirim ke Yandex: untuk menginisialisasi klien dan untuk tindakan tertentu. Di beberapa tempat, untuk mengumpulkan informasi yang cukup (misalnya, untuk daftar putar), Anda perlu membuat permintaan daftar putar, untuk menerima treknya, untuk informasi dari halaman arahan (jika ini adalah daftar putar yang cerdas), dan jangan lupa tentang inisialisasi klien. Secara umum, horor yang tenang dalam hal jumlah permintaan.

Saya menginginkan sesuatu yang sangat universal, dan tidak membuat penyimpanan apa pun untuk objek tertentu, klien yang sama.

Karena perpustakaan memungkinkan Anda untuk menentukan contoh Anda sendiri untuk eksekusi permintaan, di atas permintaan, lalu saya manfaatkan ini.

Intinya sederhana. Kelas cache itu sendiri adalah singleton. Hanya memiliki dua parameter: masa pakai cache, ukuran. Ketika permintaan dieksekusi, pembungkus dipanggil. Dia ditimpa. Memeriksa cache terjadi dengan hash args dan quarts yang dibekukan. Cache punya waktu untuk ditambahkan. Saat memeriksa kebutuhan memperbarui data, baik data dari LimitedSizeDict diperoleh, atau permintaan nyata dibuat dan ditambahkan ke cache.

Beberapa permintaan tidak dapat di-cache, misalnya, pengaturan suka / tidak suka. Jika pengguna menekan urutan berikut: suka, tidak suka, suka, maka pada akhirnya suka tidak akan dikirimkan. Untuk kasus seperti itu, saat mengirim permintaan, Anda harus memberikan argumen use_cache dengan nilai yang sama dengan False. Sebenarnya, ini adalah satu-satunya tempat di mana cache tidak digunakan.

Berkat ini, saya membuat permintaan paling berani sehingga mereka di-cache. Saya tidak mencoba memecahnya menjadi kecil dan hanya diperlukan untuk halaman saat ini. Saya mengambil semuanya sekaligus, dan ketika berpindah antar halaman saya memiliki kecepatan switching yang sangat besar (dibandingkan dengan pendekatan yang lama).

Bagi saya, kelas permintaan cache ternyata indah dan hanya terintegrasi.

Sumber
import copy
import time

from typing import Union
from collections import OrderedDict

import requests

from yandex_music.utils.request import Request as YandexMusicRequest


class LimitedSizeDict(OrderedDict):
    def __init__(self, *args, **kwargs):
        self.size_limit = kwargs.pop("size_limit", None)
        OrderedDict.__init__(self, *args, **kwargs)
        self._check_size_limit()

    def __setitem__(self, key, value):
        OrderedDict.__setitem__(self, key, value)
        self._check_size_limit()

    def _check_size_limit(self):
        if self.size_limit is not None:
            while len(self) > self.size_limit:
                self.popitem(last=False)


class CachedItem:
    def __init__(self, response: requests.Response):
        self.timestamp = time.time()
        self.response = response


class Cache:
    __singleton: 'Cache' = None

    def __init__(self, lifetime: Union[str, int], size: Union[str, int]):
        Cache.__singleton = self

        self.lifetime = int(lifetime) * 60
        self.size = int(size)

        self.storage: LimitedSizeDict = LimitedSizeDict(size_limit=int(size))

    def get(self, *args, **kwargs):
        hash_ = Cache.get_hash(*args, **kwargs)

        return self.storage[hash_].response

    def update(self, response: requests.Response, *args, **kwargs):
        hash_ = Cache.get_hash(*args, **kwargs)
        self.storage.update({hash_: CachedItem(response)})

    def need_to_fetch(self, *args, **kwargs):
        hash_ = Cache.get_hash(*args, **kwargs)
        cached_item = self.storage.get(hash_)

        if not cached_item:
            return True

        if time.time() - cached_item.timestamp > self.lifetime:
            return True

        return False

    @classmethod
    def get_instance(cls) -> 'Cache':
        if cls.__singleton is not None:
            return cls.__singleton
        else:
            raise RuntimeError(f'{cls.__name__} not initialized')

    @staticmethod
    def freeze_dict(d: dict) -> Union[frozenset, tuple, dict]:
        if isinstance(d, dict):
            return frozenset((key, Cache.freeze_dict(value)) for key, value in d.items())
        elif isinstance(d, list):
            return tuple(Cache.freeze_dict(value) for value in d)

        return d

    @staticmethod
    def get_hash(*args, **kwargs):
        return hash((args, Cache.freeze_dict(kwargs)))


class Request(YandexMusicRequest):
    def _request_wrapper(self, *args, **kwargs):
        use_cache = kwargs.get('use_cache', True)

        if 'use_cache' in kwargs:
            kwargs.pop('use_cache')

        if not use_cache:
            response = super()._request_wrapper(*args, **copy.deepcopy(kwargs))
        elif use_cache and Cache.get_instance().need_to_fetch(*args, **kwargs):
            response = super()._request_wrapper(*args, **copy.deepcopy(kwargs))
            Cache.get_instance().update(response, *args, **kwargs)
        else:
            response = Cache.get_instance().get(*args, **kwargs)

        return response



6. Lacak pengenalan melalui pesan suara


Pada awalnya, tidak ada pemikiran untuk menambahkan ini ke bot, tetapi situasi yang menarik terjadi. Bot memiliki obrolan (ini ditunjukkan dalam deskripsi bot). Setelah beberapa waktu, saya perhatikan bahwa orang masuk ke dalamnya dan mengirim pesan suara dengan musik. Pada awalnya, saya pikir itu adalah jenis spam baru sehingga seseorang mengotak dan bercanda. Tapi, ketika sudah ada 10 orang seperti itu dan semua orang melakukan hal yang sama, teman saya (yang sama disarankan oleh struct ) menyarankan agar pengguna mengendarai Yandex.Music mencari bot resmi untuk mengenali musik dari Yandex, mereka melihat ruang obrolan di sana dan mengirim pesan suara dengan serius! Itu hanya asumsi yang cemerlang dan benar. Lalu aku bercanda mengatakan bahwa sudah waktunya untuk membuat pengakuan dan menambahkan bot ke obrolan. Untuk bersenang-senang ... Setelah beberapa saat, ini selesai!

Sekarang tentang API . Baru-baru ini, Yandex semakin banyak menggunakan soket web. Saya bertemu penggunaannya dalam pengelolaan i.module dan i.station. Layanan pengenalan musik juga berfungsi. Saya melemparkan solusi kerja minimum di bot saya, tetapi saya tidak menambahkan implementasinya ke perpustakaan.

WS terletak di alamat berikut: wss: //voiceservices.yandex.net/uni.ws
Kami hanya membutuhkan dua pesan - otorisasi dan permintaan untuk dikenali .
Yandex sendiri, dalam aplikasi resminya, mengirim file pendek dalam satu atau tiga detik. Sebagai tanggapan, Anda bisa mendapatkan kebutuhan untuk mengirim lebih banyak data atau hasilnya - ditemukan atau tidak. Jika hasilnya ditemukan, maka ID trek akan dikembalikan. File .Ogg

dikirim bersamaENCODER = SpeechKit Mobile SDK v3.28.0 . Saya tidak memeriksa cara kerjanya dengan encoders lain, saya hanya mengubahnya dalam file yang direkam oleh Telegram.

Ketika membalikkan dengan soket web, terkadang keajaiban terjadi. Kadang-kadang saya tidak dapat menemukan trek, tetapi ketika saya mengubah bahasa dalam pesan dengan permintaan pengenalan, saya melakukannya. Atau pada awalnya ditemukan, dan kemudian berhenti, meskipun file tersebut sama. Saya pikir bahasa trek diatur oleh SpeechKit mereka pada klien. Tidak memiliki kesempatan untuk melakukannya sendiri, saya melakukan pencarian dengan kekerasan.

Implementasi pengenalan suara buatan saya dengan pesan suara dari Telegram
import uuid

import lomond


def get_auth_data():
    return {
        "event": {
            "header": {
                "messageId": str(uuid.uuid4()),
                "name": "SynchronizeState",
                "namespace": "System"
            },
            "payload": {
                "accept_invalid_auth": True,
                "auth_token": "5983ba91-339e-443c-8452-390fe7d9d308",
                "uuid": str(uuid.uuid4()).replace('-', ''),
            }
        }
    }


def get_asr_data():
    return {
        "event": {
            "header": {
                "messageId": str(uuid.uuid4()),
                "name": "Recognize",
                "namespace": "ASR",
                "streamId": 1
            },
            "payload": {
                "advancedASROptions": {
                    "manual_punctuation": False,
                    "partial_results": False
                },
                "disableAntimatNormalizer": False,
                "format": "audio/opus",
                "music_request2": {
                    "headers": {
                        "Content-Type": "audio/opus"
                    }
                },
                "punctuation": False,
                "tags": "PASS_AUDIO;",
                "topic": "queries"
            }
        }
    }


class Recognition:
    URI = 'wss://voiceservices.yandex.net/uni.ws'
    LANGS = ['', 'ru-RU', 'en-US']
    POLL_DELAY = 0.3

    def __init__(self, binary_data, status_msg):
        self.status_msg = status_msg
        self.websocket = lomond.WebSocket(self.URI)
        self.chunks = self.get_chunks_and_replace_encoder(binary_data)

    def get_track_id(self):
        for lang in Recognition.LANGS:
            asr_data = get_asr_data()
            if lang:
                asr_data['event']['payload'].update({'lang': lang})
                self.status_msg.edit_text(f'     {lang}...')
            else:
                self.status_msg.edit_text(f'      ...')

            for msg in self.websocket.connect(poll=self.POLL_DELAY):
                if msg.name == 'ready':
                    self.websocket.send_json(get_auth_data())
                    self.websocket.send_json(asr_data)

                    for chunk in self.chunks:
                        self.websocket.send_binary(chunk)

                if msg.name == 'text':
                    response = msg.json.get('directive')

                    if self.is_valid_response(response):
                        self.websocket.close()

                        return self.parse_track_id(response)
                    elif self.is_fatal_error(response):
                        self.websocket.close()

                        break

    def is_valid_response(self, response):
        if response.get('header', {}).get('name') == 'MusicResult' and \
                response.get('payload', {}).get('result') == 'success':
            self.status_msg.edit_text(f' ,  !')
            return True
        return False

    @staticmethod
    def is_fatal_error(response):
        if response.get('header', {}).get('name') == 'MusicResult' and \
                response.get('payload', {}).get('result') == 'music':
            return False
        return True

    @staticmethod
    def parse_track_id(response):
        return response['payload']['data']['match']['id']

    @staticmethod
    def get_chunks_and_replace_encoder(binary_data):
        chunks = []

        for chunk in binary_data.split(b'OggS')[1:]:
            if b'OpusTags' in chunk:
                pos = chunk.index(b'OpusTags') + 12
                size = len(chunk)
                chunk = chunk[:pos] + b'#\x00\x00\x00\x00ENCODER=SpeechKit Mobile SDK v3.28.0'
                chunk += b"\x00" * (size - len(chunk))

            chunks.append(b'\x00\x00\x00\x01OggS' + chunk)

        return chunks


, .

7. Catatan kecil


Pada awalnya, hanya pengguna dengan langganan yang dapat menggunakan bot karena fakta bahwa layanan dapat digunakan di sejumlah negara terbatas (tanpa berlangganan), dan server dengan bot tersebut berada di Eropa. Masalahnya diselesaikan dengan menggunakan proxy untuk mengeksekusi permintaan dari pengguna tanpa berlangganan. Server proxy terletak di Moskow.

Ada pilihan halaman, tetapi dibatasi hingga 100 (tidak ada tombol lagi yang dapat ditambahkan, pembatasan Telegram). Beberapa permintaan halaman umum memiliki lebih banyak halaman.

Dalam pencarian Yandex, jumlah elemen pada halaman tersebut di-hardcode. Berapa banyak trek, berapa banyak daftar putar. Kadang-kadang ini bahkan tidak sesuai dengan jumlah data yang ditampilkan di bagian depan. Ada perubahan halaman, jumlah elemen mengambang. Karena itu, di bot itu juga melompat, yang tidak terlalu indah. Dan untuk menggabungkan paginator mereka dengan miliknya - sesuatu seperti itu. Di tempat lain di mana dimungkinkan untuk meminta sejumlah elemen per halaman, semuanya sempurna. Pesan suara setelah menerapkan pencarian di bot adalah t.me/MarshalC/416 .

Saat meneruskan audio di Telegram, penulis hilang dan ditugaskan ke orang yang membuat penerusan. Karena itu, semua trek dikirim dengan tanda tangan dari nama pengguna bot.

Pesan suara dengan semua yang saya temui setelah implementasi radio di perpustakaan - t.me/MarshalC/422(tentang rantai lintasan, melaluinya dengan mengirimkan tumpukan umpan balik, batch_id ).

Kesimpulan


Terlepas dari kenyataan bahwa ini adalah artikel lain tentang bot Telegram, Anda membacanya sampai di sini, kemungkinan besar karena Anda tertarik pada salah satu item dalam pemotongan dan ini luar biasa, terima kasih banyak !

Sayangnya, saya tidak membuka kode sumber bot sepenuhnya (karena di beberapa tempat saya perlu refactor). Banyak yang telah dijelaskan dalam artikel ini, tetapi beberapa aspek, misalnya, dengan keyboard virtual dan generasinya tidak terpengaruh. Sebagian besar, apa yang tidak ada dalam artikel hanya bekerja dengan perpustakaan saya, tidak ada yang menarik.

Kelas-kelas di mana semuanya berputar, saya tunjukkan dalam bentuk di mana mereka sekarang. Saya akui ada bug, tetapi semuanya bekerja untuk waktu yang lama. Di beberapa tempat saya suka kode saya, di beberapa tempat saya tidak - dan ini normal. Jangan lupa bahwa bekerja dengan WS untuk pengakuan adalah solusi pada lutut. Siap membaca kritik beralasan dalam komentar.

Meskipun bot itu direncanakan bahkan ketika saya mulai menulis perpustakaan, maka saya menolak ide ini, tetapi, ternyata, saya kembali (itu membosankan).

Yandex.Music Bot - proyek yang membuktikan kesesuaian menggunakan perpustakaan untuk bekerja dengan API dalam proyek.

Banyak terima kasih kepada Ibu, Yana, Sana'a, Glory. Seseorang untuk mengoreksi kesalahan, beberapa untuk petunjuk, yang tanpanya beberapa poin dalam artikel ini mungkin tidak ada, dan untuk beberapa hanya untuk mengevaluasi artikel sebelum publikasi. Arthur untuk picci untuk artikel, Lyod untuk logo.

PS Sekarang saya punya masalah akut dengan distribusi setelah belajar. Jika Anda siap menelepon saya - beri tahu saya ke mana harus mengirim CV, wawancarai saya. Semua kontak saya ada di profil. Waktu sedang menyala, saya harap untuk Anda. Menurut undang-undang, saya hanya bisa berolahraga di Republik Belarus.

PPS Ini adalah artikel kedua dari tiga, tetapi tanpa ide sedikit pun saya akan punya waktu untuk melakukan proyek lain tentang topik ini dan apakah itu diperlukan. Saya akan mengungkapkan topiknya kepada publik - klien lintas platform Yandex.Music.

Source: https://habr.com/ru/post/undefined/


All Articles