Omitimos la prohibición de mensajes API Vkontakte a través de Python

Hola Habr En mi artículo anterior, hablé sobre la posibilidad de acceder a los métodos de la sección de mensajes a través de la documentación, para lo cual fue suficiente para iniciar sesión en el sitio VK. Muchos dijeron que esto no es una amenaza para los datos personales de los usuarios, y la incapacidad de transmitir sus mensajes es la falta de una plataforma. También en los comentarios me dejaron un enlace a la biblioteca node.js , que puede iniciar sesión con inicio de sesión / contraseña y proporcionar acceso a la API de mensajes, pretendiendo ser una aplicación oficial.

Descargo de responsabilidad:


El artículo y todo el código escrito se crearon solo con fines educativos y de investigación y nunca se utilizaron para actividades ilegales. El autor no le insta a repetir ninguna de las acciones descritas aquí y no tiene ninguna responsabilidad por ellas.


Pero no todas las personas están familiarizadas con javascript y node.js, así que decidí escribir mi biblioteca en python, que muchas personas usan ahora, lo que nos permite proporcionar la funcionalidad completa de la API de mensajes a través de las "solicitudes de prueba" de la documentación. Inmediatamente le pido que no se enoje conmigo en lugares donde repetiré aspectos del "discurso" pasado, porque quiero organizar este artículo en forma de documentación independiente.

¿Cómo usarlo?


La biblioteca en sí se encuentra en el repositorio github-a (en el mismo lugar, en la carpeta de ejemplos, hay un script con ejemplos de uso de este artículo). Para instalarlo en una computadora, puede usar el comando en la terminal:
pip install vk-messages

Ahora podemos importar la clase principal de este paquete y crear una instancia del mismo, especificando el inicio de sesión, la contraseña, el tipo de autorización que utiliza esta cuenta, así como el directorio donde queremos guardar las cookies de autorización del usuario. Esto es necesario para que los usuarios con autorización de dos factores no tengan que ingresar constantemente el código del mensaje cada vez que se ejecuta el script.

from vk_messages import MessagesAPI

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

Y de hecho, eso es todo. Ahora solo tenemos que abrir la documentación y utilizar los métodos que nos interesan. Quiero señalar de inmediato que este enfoque nos permite usar casi cualquier método de la documentación, incluso no relacionado con la sección de mensajes:

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

También podemos combinar esta biblioteca con otras, por ejemplo, a través de vk_api podemos subir fotos desde una computadora (el código para esta acción se proporciona en su sección de ejemplos), y a través de vk_messages adjunte estos archivos adjuntos al mensaje:

from vk_messages.utils import get_random

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

Por curiosidad, implementé la función clásica, que en una carpeta determinada crea subcarpetas de personas con las que una persona estaba hablando y trata de bombear los últimos mensajes y las URL absolutas de las fotos. Afortunadamente, todo funcionó como un reloj y no hubo errores innecesarios:

from vk_messages.utils import fast_parser

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

Ahora quiero pasar a una de las partes más interesantes de esta biblioteca: al tener cookies de autorización, podemos realizar absolutamente cualquier acción. Permíteme darte un ejemplo personal, cuando para las publicaciones del grupo del que soy miembro, tuve que hacer una tabla que consta de la ID de la publicación y su autor . Pero cuál fue el problema: la API oficial devuelve solo la persona que publicó el artículo. Usando el sniffer, vi que cuando pasas el mouse sobre la fecha de publicación posterior, estos datos se descargan del servidor. Y después de eso, escribí un contenedor que le permitía enviar tantas solicitudes similares como quisiera, utilizando solo el enlace de publicación y las cookies de autorización para obtener los autores. En el siguiente ejemplo, solo queda deshacerse de las etiquetas innecesarias:

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)   

Pero, ¿qué nos prueba el código superior? Así es, incluso si VC cierra las solicitudes de prueba en su documentación, siempre podemos simular las acciones del usuario y obtener la información necesaria. Como experimento, hice una pequeña función que, a través de solicitudes de "desplazamiento" de la página, puede recibir enlaces a fotos sin usar la API oficial.

El código era demasiado grande, así que decidí ocultarlo.
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]


¿Se ve voluminoso? Si. ¿Funciona mucho más lento que la API oficial? Si. Pero si VC le quita la última oportunidad de acceder a los mensajes, siempre podemos encontrar una salida.

También noto que intenté agregar a todos los lugares de la biblioteca donde son posibles errores, excepciones con explicaciones, pero si encuentra algún evento que no muestre una explicación, infórmeme sobre esto.
, , - , , , , , . , , .

?


Para aquellos que estén interesados ​​en lo que está sucediendo bajo el capó de este guión, explicaré brevemente los puntos principales. Durante la autorización, se realizan solicitudes de solicitud simples que simulan el inicio de sesión del usuario, que solo cambia ligeramente según el tipo de autorización, y después de iniciar sesión correctamente, las cookies se guardan en un archivo pickle. Al solicitar una API a través de la documentación, se agrega "param_" a todos los parámetros personalizados, es decir, el valor de compensación se convertirá en param_offset. Además, el código hash se transmite en la solicitud, que está contenida en el atributo de hash de datos de la etiqueta del botón "Ejecutar". Hasta donde noté, este valor es constante para cada método.

También noto un punto importante: la contraseña se envía en codificación ANSI, donde los caracteres del alfabeto ruso están separados por un "%", y este código es suficiente para implementar dicha decodificación. Esto puede ser un problema para algunos usuarios de Linux, porque, por lo que recuerdo, esta codificación no está incluida por defecto en python en este sistema operativo.

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

Además, uno de los problemas para mí fue el comportamiento extraño de algunos métodos. Por ejemplo, si intercambié los parámetros, el script podría devolver una respuesta 10 veces más pequeña que la solicitada o no devolver nada en absoluto. Para resolver este problema, decidí analizar y enviar los parámetros en un orden estricto, como se indica en la documentación. Quizás esta sea una simple coincidencia, pero después de estos problemas de este tipo no tuve:

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


Bueno, para mí, esta biblioteca fue la primera experiencia en escribir proyectos "abiertos", por lo que le pido que no la juzgue severamente. Solo quería ayudar a las personas que enfrentan el mismo problema que yo: limitación de API de mensajes. También realmente quiero agradecer a mis amigos que me ayudaron a escribir este artículo y probar el código.

All Articles