Nous contournons l'interdiction des messages API Vkontakte via Python

Bonjour, Habr. Dans mon précédent article, j'ai évoqué la possibilité d'accéder aux méthodes de la section messages via la documentation, pour laquelle il suffisait de se connecter au site VK. Beaucoup ont ensuite déclaré que cela ne constituait pas une menace pour les données personnelles des utilisateurs et que l'incapacité de diffuser leurs messages était le manque de plate-forme. Dans les commentaires, j'ai également laissé un lien vers la bibliothèque node.js , qui peut se connecter en utilisant un identifiant / mot de passe et fournir un accès à l'API de message, faisant semblant d'être une application officielle.

Avertissement:


L'article et tout le code écrit ont été créés uniquement à des fins éducatives et de recherche et n'ont jamais été utilisés pour des activités illégales. L'auteur ne vous invite pas à répéter les actions décrites ici et n'assume aucune responsabilité à leur égard.


Mais tout le monde ne connaît pas javascript et node.js, j'ai donc décidé d'écrire ma bibliothèque en python, que beaucoup de gens utilisent maintenant, ce qui nous permet de fournir toutes les fonctionnalités de l'API de messages à travers les "demandes de test" de la documentation. Je vous demande immédiatement de ne pas vous fâcher contre moi dans les endroits où je vais répéter des aspects du «discours» passé, car je veux organiser cet article sous forme de documentation indépendante.

Comment l'utiliser?


La bibliothèque elle-même est située dans le référentiel github-a (au même endroit, dans le dossier des exemples, il y a un script avec des exemples d'utilisation de cet article). Pour l'installer sur un ordinateur, vous pouvez utiliser la commande dans le terminal:
pip install vk-messages

Nous pouvons maintenant importer la classe principale de ce package et en créer une instance, en spécifiant le login, le mot de passe, le type d'autorisation utilisé par ce compte, ainsi que le répertoire dans lequel nous voulons enregistrer les cookies d'autorisation utilisateur. Cela est nécessaire pour que les utilisateurs disposant d'une autorisation à deux facteurs n'aient pas à saisir constamment le code du message à chaque exécution du script.

from vk_messages import MessagesAPI

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

Et en fait, c'est tout. Il ne nous reste plus qu'à ouvrir la documentation et utiliser les méthodes qui nous intéressent. Je tiens à noter immédiatement que cette approche nous permet d'utiliser presque toutes les méthodes de la documentation, même sans rapport avec la section des messages:

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

Nous pouvons également combiner cette bibliothèque avec d'autres, par exemple, via vk_api, nous pouvons télécharger des photos depuis un ordinateur (le code de cette action est donné dans leur section d'exemples), et via vk_messages, joignez ces pièces jointes au message:

from vk_messages.utils import get_random

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

Par curiosité, j'ai implémenté la fonction classique, qui dans un dossier donné crée des sous-dossiers de personnes avec qui une personne parlait, et essaie de pomper les derniers messages et les URL absolues des photos. Heureusement, tout fonctionnait comme une horloge, et il n'y avait pas d'erreurs inutiles:

from vk_messages.utils import fast_parser

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

Maintenant, je veux passer à l'une des parties les plus intéressantes de cette bibliothèque: ayant des cookies d'autorisation, nous pouvons effectuer absolument n'importe quelle action. Permettez-moi de vous donner un exemple personnel, lorsque pour les postes du groupe dont je suis membre, j'ai dû créer un tableau composé de l'identifiant du poste et de son auteur . Mais quel a été le piège: l'API officielle ne renvoie que la personne qui a publié l' article. À l'aide du renifleur, j'ai vu que lorsque vous survolez à la date de publication, ces données sont téléchargées depuis le serveur. Et après cela, j'ai écrit un wrapper qui vous a permis d'envoyer autant de demandes similaires que vous le souhaitez, en utilisant uniquement le lien de publication et les cookies d'autorisation pour obtenir les auteurs. Dans l'exemple ci-dessous, il ne reste plus qu'à se débarrasser des balises inutiles:

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)   

Mais que nous prouve le code supérieur? C'est vrai, même si VC ferme les demandes de test sur sa documentation, nous pouvons toujours simuler les actions des utilisateurs et obtenir les informations nécessaires. À titre expérimental, j'ai créé une petite fonction qui, grâce à des demandes de «défilement» de la page, peut recevoir des liens vers des photos sans utiliser l'API officielle.

Le code était trop gros, j'ai donc décidé de le cacher
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]


Semble-t-il volumineux? Oui. Cela fonctionne-t-il beaucoup plus lentement que l'API officielle? Oui. Mais si VC enlève la dernière occasion d'accéder aux messages, nous pouvons toujours trouver une issue.

Je note également que j'ai essayé d'ajouter à tous les endroits de la bibliothèque où des erreurs sont possibles, des exceptions avec des explications, mais si vous trouvez des événements qui n'affichent pas d'explication, veuillez m'en informer.
, , - , , , , , . , , .

?


Pour ceux qui sont intéressés par ce qui se passe sous le capot de ce script, je passerai brièvement en revue les points principaux. Lors de l'autorisation, de simples demandes sont faites pour simuler la connexion de l'utilisateur, qui ne change que légèrement en fonction du type d'autorisation, et après une connexion réussie, les cookies sont enregistrés dans un fichier pickle. Lorsque vous demandez une API via la documentation, «param_» est ajouté à tous les paramètres personnalisés, c'est-à-dire que la valeur de décalage se transforme en param_offset. En outre, le code de hachage est transmis dans la demande, qui est contenue dans l'attribut de hachage de données de la balise de bouton "Exécuter". Pour autant que je l'ai remarqué, cette valeur est constante pour chaque méthode.

Je noterai également un point important: le mot de passe est envoyé en encodage ANSI, où les caractères de l'alphabet russe sont séparés par le signe "%", et ce code suffit pour implémenter un tel décodage. Cela peut être un problème pour certains utilisateurs Linux, car, pour autant que je m'en souvienne, cet encodage n'est pas inclus par défaut en python sur ce système d'exploitation.

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

De plus, l'un des problèmes pour moi était le comportement étrange de certaines méthodes. Par exemple, si j'ai échangé les paramètres, le script pourrait retourner une réponse 10 fois plus petite que celle demandée ou ne rien retourner du tout. Pour résoudre ce problème, j'ai juste décidé d'analyser et d'envoyer les paramètres dans un ordre strict, comme ils sont indiqués dans la documentation. C'est peut-être une simple coïncidence, mais après ces problèmes de ce genre, je n'ai pas eu:

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


Eh bien, pour moi, cette bibliothèque a été la première expérience d'écriture de projets "ouverts", donc je vous demande de ne pas la juger sévèrement. Je voulais juste aider les gens qui sont confrontés au même problème que moi: la limitation de l'API des messages. Je tiens également à remercier mes amis qui m'ont aidé à écrire cet article et à tester le code.

All Articles