Kami melewati larangan pesan API Vkontakte melalui Python

Halo Habr. Dalam artikel saya sebelumnya, saya berbicara tentang kemungkinan mengakses metode bagian pesan melalui dokumentasi, yang cukup untuk masuk ke situs VK. Banyak yang kemudian mengatakan bahwa ini bukan ancaman bagi data pribadi pengguna, dan ketidakmampuan untuk memompa pesan mereka adalah kurangnya platform. Juga di komentar saya dibiarkan tautan ke perpustakaan node.js , yang dapat login menggunakan login / kata sandi dan menyediakan akses ke pesan API, berpura-pura menjadi aplikasi resmi.

Penolakan:


Artikel dan semua kode tertulis dibuat hanya untuk tujuan pendidikan dan penelitian dan tidak pernah digunakan untuk kegiatan ilegal. Penulis tidak mendesak Anda untuk mengulangi tindakan yang dijelaskan di sini dan tidak memikul tanggung jawab apa pun atas tindakan tersebut.


Tetapi tidak semua orang terbiasa dengan javascript dan node.js, jadi saya memutuskan untuk menulis pustaka saya dengan python, yang digunakan banyak orang sekarang, yang memungkinkan kami untuk menyediakan fungsionalitas penuh dari pesan API melalui "permintaan pengujian" dokumentasi. Segera saya meminta Anda untuk tidak marah kepada saya di tempat-tempat di mana saya akan mengulangi aspek "pidato" masa lalu, karena saya ingin mengatur artikel ini dalam bentuk dokumentasi independen.

Bagaimana cara menggunakannya?


Pustaka itu sendiri terletak di repositori github-a (di tempat yang sama, di folder contoh, ada skrip dengan contoh penggunaan dari artikel ini). Untuk menginstalnya di komputer, Anda dapat menggunakan perintah di terminal:
pip instal vk-messages

Sekarang kita dapat mengimpor kelas utama dari paket ini dan membuat turunannya, menentukan login, kata sandi, jenis otorisasi apa yang digunakan akun ini, serta direktori tempat kami ingin menyimpan cookie otorisasi pengguna. Ini diperlukan agar pengguna dengan otorisasi dua faktor tidak harus terus-menerus memasukkan kode dari pesan setiap kali skrip dijalankan.

from vk_messages import MessagesAPI

login, password = 'login', 'password'                                
messages = MessagesAPI(login=login, password=password,
                                two_factor=True, cookies_save_path='sessions/')

Dan faktanya, itu saja. Sekarang kita hanya perlu membuka dokumentasi dan menggunakan metode yang kita minati. Saya ingin segera mencatat bahwa pendekatan ini memungkinkan kami untuk menggunakan hampir semua metode dari dokumentasi, bahkan tidak terkait dengan bagian pesan:

history = messages.method('messages.getHistory', user_id='1234567', count=5)

Kami juga dapat menggabungkan perpustakaan ini dengan yang lain, misalnya, melalui vk_api kita dapat mengunduh foto dari komputer (kode untuk tindakan ini diberikan di bagian contohnya), dan melalui vk_messages lampirkan lampiran ini ke pesan:

from vk_messages.utils import get_random

messages.method('messages.send', user_id=peer_id, message='Hello',
            attachment='photo123456_7891011', random_id=get_random())

Karena penasaran, saya mengimplementasikan fungsi klasik, yang dalam folder yang diberikan membuat subfolder dari orang-orang yang berbicara dengan seseorang, dan mencoba untuk memompa keluar pesan terbaru dan url absolut foto. Untungnya, semuanya bekerja seperti jam, dan tidak ada kesalahan yang tidak perlu:

from vk_messages.utils import fast_parser

fast_parser(messages, path='parsing/',                    
       count_conv=10, messages_deep=400, photos_deep=100) 

Sekarang saya ingin beralih ke salah satu bagian paling menarik dari perpustakaan ini: memiliki cookie otorisasi, kita dapat melakukan tindakan apa pun. Biarkan saya memberi Anda contoh pribadi, ketika untuk posting grup saya adalah anggota, saya harus membuat tabel yang terdiri dari ID posting dan penulisnya . Tapi apa masalahnya: api resmi hanya mengembalikan orang yang menerbitkan artikel. Menggunakan sniffer, saya melihat bahwa ketika Anda mengarahkan kursor pada tanggal publikasi pos, data ini diunduh dari server. Dan setelah itu saya menulis pembungkus yang memungkinkan Anda untuk mengirim sebanyak mungkin permintaan yang sama seperti Anda hanya menggunakan tautan posting dan cookie otorisasi untuk menerima penulis. Dalam contoh di bawah ini, itu tetap hanya untuk menghilangkan tag yang tidak perlu:

def get_creators(post, cookies):
    group = -int(post.split('_')[0])
    response = requests.post('https://vk.com/al_page.php', cookies=cookies,
               data=f"_ads_group_id={group}&act=post_author_data_tt&al=1&raw={post}")

    response_json = json.loads(response.text[4:])['payload'][1]
    return response_json[0]

authors = get_creators(post='-12345_67890',
                    cookies=messages.get_cookies())
print(authors)   

Tetapi apa yang dibuktikan oleh bagian atas kode itu bagi kita? Itu benar, bahkan jika VC menutup permintaan pengujian pada dokumentasinya, kami selalu dapat mensimulasikan tindakan pengguna dan mendapatkan informasi yang diperlukan. Sebagai percobaan, saya membuat fungsi kecil yang, melalui permintaan untuk "menggulir" halaman, dapat menerima tautan ke foto tanpa menggunakan API resmi.

Kode itu terlalu besar, jadi saya memutuskan untuk menyembunyikannya
def get_attachments(attachment_type, peer_id, count, offset, cookies_final):
    if attachment_type == 'photo':
        session = requests.Session()
        parsed = []
        
        response = session.post(f'https://vk.com/wkview.php',
                            data=f'act=show&al=1&dmcah=&loc=im&ref=&w=history{peer_id}_photo', cookies=cookies_final)
        
        response_json = json.loads(response.text[4:])
        
        try:
            last_offset = response_json['payload'][1][2]['offset']
            count_all = response_json['payload'][1][2]['count']
        except:
            last_offset = response_json['payload'][1][0]['offset']
            count_all = response_json['payload'][1][0]['count']

        while (len(parsed) < count + offset)  and (last_offset != count_all):
            response_json = json.loads(response.text[4:])
        
            try:
                last_offset = response_json['payload'][1][2]['offset']
            except:
                last_offset = response_json['payload'][1][0]['offset']

            photos_vk =  re.findall(r'<a href="/photo(\S*)?all=1"', response_json['payload'][1][1])
            mails =  re.findall(r"'(\S*)', {img: this ,", response_json['payload'][1][1])

            for photo, mail in zip(photos_vk, mails):
                if len(parsed) < offset:
                    parsed.append(photo)
                    continue
                
                response = session.post(f'https://vk.com/al_photos.php', cookies=cookies_final,
                            data=f'act=show&al=1&al_ad=0&dmcah=&gid=0&list={mail}&module=im&photo={photo}')
                
                response_json = json.loads(response.text[4:])
                photo_size = list(response_json['payload'][1][3][0].items())
                photo_size.reverse()
                
                for i in range(len(photo_size)):
                    if 'attached_tags' in photo_size[i][0]:
                        photo_size = photo_size[:i]
                        break
            
                parsed.append(photo_size) 

            response = session.post(f'https://vk.com/wkview.php', cookies=cookies_final,
                    data=f'act=show&al=1&offset={last_offset}&part=1&w=history{peer_id}_photo')
        
        return parsed[offset + 3 : offset + 3 + count]


Apakah ini terlihat besar? Iya. Apakah ini bekerja jauh lebih lambat daripada api resmi? Iya. Tetapi jika VC menghilangkan kesempatan terakhir untuk mengakses pesan, kami selalu dapat menemukan jalan keluar.

Saya juga mencatat bahwa saya mencoba menambahkan ke semua tempat di perpustakaan di mana kesalahan, pengecualian dengan penjelasan adalah mungkin, tetapi jika Anda menemukan peristiwa yang tidak menampilkan penjelasan, tolong informasikan kepada saya tentang hal ini.
, , - , , , , , . , , .

?


Bagi mereka yang tertarik dengan apa yang terjadi di bawah kap skrip ini, saya akan secara singkat membahas poin-poin utama. Selama otorisasi, permintaan permintaan sederhana dibuat yang mensimulasikan login pengguna, yang hanya sedikit berubah tergantung pada jenis otorisasi, dan setelah login berhasil, cookie disimpan dalam file acar. Ketika meminta api melalui dokumentasi, "param_" ditambahkan ke semua parameter khusus, yaitu nilai offset akan berubah menjadi param_offset. Juga, kode hash ditransmisikan dalam permintaan, yang terkandung dalam atribut data-hash dari tag tombol "Run". Sejauh yang saya perhatikan, nilai ini konstan untuk setiap metode.

Saya juga akan mencatat satu poin penting: kata sandi dikirim dalam penyandian ANSI, di mana karakter-karakter alfabet Rusia dipisahkan oleh tanda “%”, dan kode ini cukup untuk mengimplementasikan decoding tersebut. Ini bisa menjadi masalah bagi beberapa pengguna Linux, karena, sejauh yang saya ingat, pengkodean ini tidak termasuk secara default dalam python pada sistem operasi ini.

self.password = str(password.encode('ANSI')).replace('\\x', '%')[2:-1]

Juga, salah satu masalah bagi saya adalah perilaku aneh beberapa metode. Misalnya, jika saya menukar parameter, skrip dapat mengembalikan respons 10 kali lebih kecil dari yang diminta atau tidak mengembalikan apa pun sama sekali. Untuk mengatasi masalah ini, saya baru saja memutuskan untuk mengurai dan mengirim parameter dalam urutan yang ketat, seperti yang ditunjukkan dalam dokumentasi. Mungkin ini kebetulan yang sederhana, tetapi setelah masalah seperti ini saya tidak punya:

response = session.get(f'https://vk.com/dev/{name}', cookies=self.cookies_final)
hash_data =  re.findall(r'data-hash="(\S*)"', response.text)

soup = BeautifulSoup(response.text, features="html.parser")
params = soup.findAll("div", {"class": "dev_const_param_name"})
params = [cleanhtml(str(i)) for i in params]

payload, checker = '', 0
for param in params:
   if param in kwargs:
       checker += 1
       payload += '&{}={}'.format('param_' + \
   param, quote(str(kwargs[param]) if type(kwargs[param]) != bool else str(int(kwargs[param]))))
        
if checker != len(kwargs):
   raise Exception_MessagesAPI('Some of the parametrs invalid', 'InvalidParameters')

Total


Bagi saya, perpustakaan ini adalah pengalaman pertama dalam menulis proyek "terbuka", jadi saya meminta Anda untuk tidak menghakimi dengan parah. Saya hanya ingin membantu orang yang menghadapi masalah yang sama dengan saya: batasan API pesan. Saya juga sangat ingin berterima kasih kepada teman-teman saya yang membantu saya menulis artikel ini dan menguji kodenya.

All Articles