Загрузка данных


cat << 'EOF' > clean_friends.py
import sys
import time
import json
import re
import os
import requests

COOKIES_DATA = [
    {"name": "remixuacck", "value": "85dca311dce9d73521", "domain": ".vk.ru", "path": "/"},
    {"name": "remixdark_color_scheme", "value": "1", "domain": ".vk.ru", "path": "/"},
    {"name": "httoken", "value": "UPml6IBSupOdTjtR8mNZ7CDJH0Bb-N0esLGzqqiRx-VXuCx3cdQIwp4tWaJS0QIK1eTg2hEvQckCkMj-g6PVDbyyuB6rim7-ywQlzbVBKzDDZG9tixx4Fve5ABSGe4YKFX0", "domain": ".m.vk.ru", "path": "/"},
    {"name": "remixlang", "value": "0", "domain": ".vk.ru", "path": "/"},
    {"name": "remixff", "value": "10111111111101", "domain": ".vk.ru", "path": "/"},
    {"name": "remixstid", "value": "960128973_9LrUKD1VN6V9WuhzlVtB8KJD2S2pzXGDoIHpShgLIsH", "domain": ".vk.ru", "path": "/"},
    {"name": "remixua", "value": "17%7C621%7C86%7C677989468", "domain": ".vk.ru", "path": "/"},
    {"name": "remixdt", "value": "7200", "domain": ".vk.ru", "path": "/"},
    {"name": "remixmdevice", "value": "468/1037/2.3076923076923075/!!!!!!!!!!!/468", "domain": ".vk.ru", "path": "/"},
    {"name": "remixstlid", "value": "9088231374865086373_c7v7GTeuJsPheSw8dTr3KEjZauNZCcWfuNw37uYtLbc", "domain": ".vk.ru", "path": "/"},
    {"name": "remixcolor_scheme_mode", "value": "auto", "domain": ".vk.ru", "path": "/"},
    {"name": "remixsuc", "value": "1%3A", "domain": ".vk.ru", "path": "/"},
    {"name": "remixrefkey", "value": "46b62ba5d1b4d27b3a", "domain": ".vk.ru", "path": "/"},
    {"name": "remixmsts", "value": "%7B%22data%22%3A%5B%5B1791560891%2C%22entries_requests%22%2C1%2C%22core_spa%22%2C%22mvk%22%5D%2C%5B1791560891%2C%22entries_requests%22%2C1%2C%22audio%22%2C%22mvk%22%5D%2C%5B1791560891%2C%22entries_requests%22%2C1%2C%22audio_player_bottom%22%2C%22mvk%22%5D%2C%5B1791560892%2C%22counters_check%22%2C1%5D%2C%5B1791560893%2C%22counters_check%22%2C1%5D%5D%2C%22uniqueId%22%3A294709293.0240143%7D", "domain": ".vk.ru", "path": "/"},
    {"name": "remixnsid", "value": "vk1.a.q6PpoNhyuYE3JJqmSWsTYOjNJxeCyJDYsYfp0O8aQ4nR3S0EteEVgNcKePzlpaQmtVGNz7nsDaPTuGNcj5Mv5gf-N1lMQa9zYFJ6e_eKbIOWpI0gek2grh97jU-rynbk7wGWgyN0dApyzKyd_3Fs5PyRttZpWmvsbLAP42DIUcGSB5xyoCQiP7DzbCUootU7", "domain": "m.vk.ru", "path": "/"},
    {"name": "httoken", "value": "cPLne8Hv_rs7lMMlhSk5Nush2BP11DugSoU5Q5xxq6DGRJ20IJWDjl8EZY1oDOD7LNOYJNBHUirzMHgHvsTvnRzxgwcacp2i7VdVlUoRBVBsM_DB1aQx7Rq-vPNHo3gy-0E", "domain": ".vk.ru", "path": "/"},
    {"name": "remixcurr_audio", "value": "undefined", "domain": ".vk.ru", "path": "/"},
    {"name": "remixdmgr", "value": "e25cacfa8eaeb542a4121715292ac9dd063768a26d15aebc7cb1cfe13c66d5ae", "domain": ".vk.ru", "path": "/"},
    {"name": "remixdmgr_tmp", "value": "98790d44713e491054b0fc82cdca08eb8b0c74be713a266b4508dc0f31bcc204", "domain": ".vk.ru", "path": "/"},
    {"name": "remixgp", "value": "7e2ccff978f2c6f8eb16d61aa8712618", "domain": ".vk.ru", "path": "/"},
    {"name": "remixmdv", "value": "EdaHXljyjilXaPud", "domain": ".vk.ru", "path": "/"},
    {"name": "remixmvk-fp", "value": "6b40bf508d27d26aa011c5920d4b2951", "domain": ".vk.ru", "path": "/"},
    {"name": "remixsid", "value": "1_p_qDcaNIQbIQ0PUocdN0K9ZWuaHLofwBUSYNRFPu1MBN8XQXuAEeZk2vVKRa8BdXqOlX9gOWInY-yp-6b7RNBw", "domain": ".vk.ru", "path": "/"},
    {"name": "remixuas", "value": "M2QzYjI4YjlkYmFjOTdmZWU1NjdkNWIy", "domain": ".vk.ru", "path": "/"}
]

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
    "Accept": "*/*",
    "Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
    "Origin": "https://vk.com",
    "Referer": "https://vk.com/dev/execute",
    "X-Requested-With": "XMLHttpRequest"
}


class DevPortalCleaner:
    def __init__(self):
        self.session = requests.Session()
        self.session.headers.update(HEADERS)
        self.dev_hash = None
        self.init_cookies()

    def init_cookies(self):
        for c in COOKIES_DATA:
            val = c["value"]
            # Устанавливаем куки на все домены, включая vk.com для портала разработчиков
            for dom in [".vk.ru", "vk.ru", ".m.vk.ru", "m.vk.ru", ".vk.com", "vk.com"]:
                self.session.cookies.set(c["name"], val, domain=dom, path="/")

    def auth_dev_portal(self) -> bool:
        print("[*] Подключение к официальной API-консоли ВКонтакте (vk.com/dev)...")
        try:
            resp = self.session.get("https://vk.com/dev/execute", timeout=15)
            html = resp.text

            if "login" in resp.url or "act=blocked" in resp.url:
                print("[!] Ошибка: Сессия недействительна для vk.com.")
                return False

            # Извлекаем хэш для выполнения API-методов
            match = re.search(r"Dev\.methodRun\s*\([^,]+,\s*['\"]([^'\"]+)['\"]\)", html)
            if not match:
                match = re.search(r"Dev\.runMethod\s*\([^,]+,\s*['\"]([^'\"]+)['\"]\)", html)
            if not match:
                match = re.search(r'data-hash=["\']([a-zA-Z0-9_\-]+)["\']', html)

            if match:
                self.dev_hash = match.group(1)
                print(f"[+] Ключ API-консоли успешно получен: {self.dev_hash}")
                return True
            else:
                print("[!] Ошибка: Не удалось найти системный ключ API на странице разработчика.")
                return False
        except Exception as e:
            print(f"[!] Сетевая ошибка при входе в консоль: {e}")
            return False

    def delete_friend_api(self, target_id: int) -> tuple:
        """
        Отправляет честный API запрос friends.delete через консоль разработчика.
        Возвращает (успех: bool, ответ: str)
        """
        api_code = f'return API.friends.delete({{"user_id": {target_id}}});'
        payload = {
            "act": "a_run_method",
            "al": "1",
            "hash": self.dev_hash,
            "method": "execute",
            "param_code": api_code,
            "param_v": "5.131"
        }

        try:
            resp = self.session.post("https://vk.com/dev", data=payload, timeout=15)
            text = resp.text.strip()
            
            # Очистка системного мусора AJAX-ответа ВК
            if text.startswith("<!--"):
                text = text[4:]
            if text.endswith("-->"):
                text = text[:-3]

            # Ищем чистый JSON ответ от API
            match = re.search(r'(\{"response":.*?\})', text, re.DOTALL)
            if not match:
                match = re.search(r'(\{"error":.*?\})', text, re.DOTALL)

            if match:
                json_str = match.group(1)
                res_obj = json.loads(json_str)

                # Обработка успешного ответа
                if "response" in res_obj:
                    r = res_obj["response"]
                    if isinstance(r, dict):
                        if r.get("success") == 1 or r.get("friend_deleted") == 1:
                            return True, "API Success: 1 (Реально удален)"
                        elif r.get("out_request_deleted") == 1:
                            return True, "Отменена исходящая заявка"
                    elif isinstance(r, int) and r == 1:
                        return True, "API Success: 1"
                    
                    return True, f"Успешно (Сырой ответ API: {r})"

                # Обработка API ошибок (например, капчи)
                if "error" in res_obj:
                    err = res_obj["error"]
                    err_code = err.get("error_code")
                    err_msg = err.get("error_msg", "")
                    
                    if err_code == 14:
                        return False, "КАПЧА! ВКонтакте временно заблокировал частые действия."
                    elif err_code == 29:
                        return False, "Достигнут лимит API ВКонтакте."
                    
                    return False, f"Ошибка API [{err_code}]: {err_msg}"

            # Если ВК вернул HTML с ошибкой, а не JSON
            return False, f"Не удалось прочитать ответ сервера: {text[:60]}"
            
        except Exception as e:
            return False, f"Ошибка соединения: {e}"


def main():
    print("=" * 60)
    print("      НАСТОЯЩЕЕ API-УДАЛЕНИЕ ДРУЗЕЙ (DEV CONSOLE)")
    print("=" * 60)

    cleaner = DevPortalCleaner()
    if not cleaner.auth_dev_portal():
        sys.exit(1)

    backup_file = "deleted_friends_backup.txt"
    if not os.path.exists(backup_file):
        print(f"[!] Файл списка {backup_file} не найден!")
        sys.exit(1)

    targets = []
    with open(backup_file, "r", encoding="utf-8") as f:
        for line in f:
            match = re.search(r"id(\d+)\s*-\s*(.*)", line.strip())
            if match:
                targets.append((int(match.group(1)), match.group(2).strip()))

    print(f"[+] Загружено {len(targets)} пользователей из резервного списка.")
    if not targets:
        print("[!] Список пуст.")
        sys.exit(0)

    # --- ТЕСТ НА 1-М ПОЛЬЗОВАТЕЛЕ ---
    test_id, test_name = targets[0]
    print(f"\n[*] Контрольный тест API на пользователе: {test_name} (id{test_id})...")
    
    ok, response_text = cleaner.delete_friend_api(test_id)
    print(f"    Ответ API ВКонтакте: {response_text}")

    if not ok:
        print("\n" + "!" * 60)
        print("[!] ВНИМАНИЕ: API отклонил запрос на удаление!")
        print("    Процесс остановлен, чтобы не работать вхолостую.")
        print("!" * 60)
        sys.exit(1)

    print(f"\n[+] ТЕСТ ПРОЙДЕН! Сервер ВКонтакте подтвердил удаление (success: 1).")

    confirm = input(f"\nПродолжить удаление остальных {len(targets) - 1} пользователей? (yes/no): ").strip().lower()
    if confirm != "yes":
        print("[-] Процесс остановлен пользователем.")
        sys.exit(0)

    print("\n[*] Запуск массового удаления через API...")
    deleted_count = 1

    for fid, name in targets[1:]:
        ok, resp_str = cleaner.delete_friend_api(fid)

        if ok:
            deleted_count += 1
            print(f"[{deleted_count}/{len(targets)}] Удален: {name} (id{fid})")
        else:
            print(f"[x] Ошибка: {name} (id{fid}) -> {resp_str}")
            
            # Останавливаем процесс, если выскочила капча
            if "КАПЧА" in resp_str or "лимит" in resp_str.lower():
                print("\n[!] Обнаружена блокировка от ВК. Остановка скрипта для безопасности.")
                break

        time.sleep(1.3)

    print(f"\n[+] Процесс завершен! Успешно отработано: {deleted_count} из {len(targets)}.")


if __name__ == "__main__":
    main()
EOF