From f490e134f64055e29b53bb5424a95f69d28a01141abf286dfe7bdfa888462662 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Wed, 15 Apr 2026 14:04:49 +0200 Subject: [PATCH] =?UTF-8?q?Bot=20commit:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D1=84=D0=B0=D0=B9=D0=BB=20vk.ipynb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vk.ipynb | 1555 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1555 insertions(+) create mode 100644 vk.ipynb diff --git a/vk.ipynb b/vk.ipynb new file mode 100644 index 0000000..b59eb01 --- /dev/null +++ b/vk.ipynb @@ -0,0 +1,1555 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4e3a145b", + "metadata": {}, + "source": [ + "# VK" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44794e2f", + "metadata": {}, + "outputs": [], + "source": [ + "# ignore\n", + "import os\n", + "\n", + "def load_env(file_path):\n", + " \"\"\"Загружает переменные окружения из файла .env с поддержкой булевых значений.\"\"\"\n", + " with open(file_path) as f:\n", + " for line in f:\n", + " line = line.strip()\n", + " if not line or line.startswith('#'):\n", + " continue\n", + " \n", + " try:\n", + " key, value = line.split('=', 1)\n", + " key = key.strip()\n", + " value = value.strip('\"\\' ')\n", + " \n", + " # Преобразование строковых значений в булевые\n", + " if value.lower() in ['true', '1', 'yes']:\n", + " value = True\n", + " elif value.lower() in ['false', '0', 'no']:\n", + " value = False\n", + " \n", + " # Проверка, что ключ не пустой\n", + " if key:\n", + " os.environ[key] = str(value) # Храним как строку\n", + "\n", + " except ValueError:\n", + " print(f\"Ошибка: неверный формат в строке '{line}'\")\n", + "\n", + "try:\n", + " load_env('env')\n", + "except:\n", + " load_env('env_example')" + ] + }, + { + "cell_type": "markdown", + "id": "dd08738b", + "metadata": {}, + "source": [ + "## Class" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49f00c66", + "metadata": {}, + "outputs": [], + "source": [ + "import httpx\n", + "import asyncio\n", + "from db import *\n", + "from config import *\n", + "from auth import *\n", + "from token_manager import TokenManager\n", + "import logging\n", + "from PIL import Image\n", + "from io import BytesIO\n", + "from celery import Celery\n", + "from bson import ObjectId\n", + "from fastapi import status\n", + "from fastapi import APIRouter,Depends\n", + "from fastapi.responses import RedirectResponse\n", + "from images import insert_logo" + ] + }, + { + "cell_type": "markdown", + "id": "28509a4b", + "metadata": {}, + "source": [ + "### Конфигурация" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cd75fe35", + "metadata": {}, + "outputs": [], + "source": [ + "celery_app_vk = Celery('vk ', broker=f'amqps://admin:{RABBITMQ_PASS}@{RABBITMQ_HOST}/vk', task_track_started=True )\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f33750b5", + "metadata": {}, + "outputs": [], + "source": [ + "token_manager = TokenManager()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d94f31bd", + "metadata": {}, + "outputs": [], + "source": [ + "router = APIRouter(prefix=\"/vk\", tags=[\"VK OAuth\"])\n", + "@router.get(\"/auth\")\n", + "async def auth_redirect(\n", + " user=Depends(get_current_user)\n", + "):\n", + " url = await token_manager.get_auth_url(user_id=user['id'])\n", + " return RedirectResponse(url=url, status_code=status.HTTP_303_SEE_OTHER)\n", + "\n", + "\n", + "@router.get(\"/token\")\n", + "async def oauth_callback(code: str, state: str, device_id: str = None):\n", + " token_data = await token_manager.get_token(code=code, state=state, device_id=device_id)\n", + " return RedirectResponse(url='/profile/social', status_code=status.HTTP_303_SEE_OTHER)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05a93b5", + "metadata": {}, + "outputs": [], + "source": [ + "async def get_images(db_name: str, file_ids: list, sign_avatar: bool = True):\n", + " images = {}\n", + " photo_bytes_list= []\n", + "\n", + " if sign_avatar:\n", + " user = await users_collection.find_one({'_id': ObjectId(file_ids[0])},{'img':1})\n", + " stream = await gridfs_map['users'].open_download_stream(ObjectId(user['img']))\n", + " avatar_bytes = await stream.read()\n", + " logo = Image.open(BytesIO(avatar_bytes)).convert(\"RGBA\")\n", + " file_ids = file_ids[1:]\n", + " \n", + " for _id in file_ids:\n", + " stream = await gridfs_map[db_name].open_download_stream(ObjectId(_id))\n", + " photo_bytes_list.append((stream.name, BytesIO(await stream.read()), _id))\n", + " \n", + " for name, background, _id in photo_bytes_list:\n", + " if sign_avatar:\n", + " background = insert_logo(Image.open(background).convert(\"RGBA\"), logo)\n", + " images[_id] = (\"photo\", (name, background, \"image/jpeg\"))\n", + " \n", + " return images" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6d815249", + "metadata": {}, + "outputs": [], + "source": [ + "@router.post(\"/send\")\n", + "async def send_vk(data:dict, request: Request):\n", + " auth = request.headers.get(\"Authorization\")\n", + " if auth and auth.lower().startswith(\"bearer \"):\n", + " parts = auth.split(\" \", 1)\n", + " if len(parts) == 2:\n", + " if parts[1].strip() != ATOKEN:\n", + " return None\n", + " item = data['item']\n", + " text = data['text']\n", + " file_ids = data['file_ids']\n", + " doc = data['doc']\n", + " \n", + " sign_avatar = data['sign_avatar']\n", + " db_name = item.get('db')\n", + " # if db_name == 'competitions':\n", + " # social = await social_collection.find_one({'service': True, 'type':'vk'})\n", + " # else:\n", + " social = doc\n", + " logging.info(doc)\n", + " logging.info(social)\n", + " images = await get_images(db_name, file_ids, sign_avatar)\n", + " async with httpx.AsyncClient(http2=True, verify=ctx) as client:\n", + " attachments = []\n", + " for _id, img in images.items():\n", + " # doc_photo = await mongo_client['vk']['photos'].find_one({'id':f\"{_id}:{social.get('_id')}\"})\n", + " # if doc_photo:\n", + " # attachments.append(doc_photo['attachment'])\n", + " # continue\n", + " payload = {\"v\": \"5.199\"}\n", + " if 'group' in social:\n", + " payload['group_id'] = social['id']\n", + " resp = await client.post(f\"{VK_API_URI}photos.getWallUploadServer\", headers={\"Authorization\": f\"Bearer {await token_manager.get_access_token(social['user_id'])}\"}, data=payload, timeout=30.0)\n", + " resp.raise_for_status()\n", + " upload_url = resp.json()['response']['upload_url']\n", + " resp = await client.post(upload_url, files=[img], timeout=60.0)\n", + " resp.raise_for_status()\n", + " payload.update(resp.json())\n", + " payload.update({'caption':text})\n", + " resp = await client.post(f\"{VK_API_URI}photos.saveWallPhoto\", headers={\"Authorization\": f\"Bearer {await token_manager.get_access_token(social['user_id'])}\"}, data=payload, timeout=30.0)\n", + " resp.raise_for_status()\n", + " attachment = ','.join([f'photo{i['owner_id']}_{i['id']}' for i in resp.json()['response']])\n", + " if not attachment:\n", + " return {'ok': False}\n", + " # await mongo_client['vk']['photos'].insert_one({'attachment':attachment,'id':f\"{_id}:{social.get('_id')}\"})\n", + " attachments.append(attachment)\n", + " logging.info(attachments)\n", + " \n", + " resp = await client.post(f\"{VK_API_URI}wall.post\", data={\n", + " \"access_token\": await token_manager.get_access_token(doc['user_id']),\n", + " \"owner_id\": f'-{doc['id']}' if 'group' in doc else doc['id'],\n", + " 'message': text,\n", + " 'attachments': ','.join(attachments),\n", + " 'primary_attachments_mode': 'grid',\n", + " \"v\": \"5.199\",\n", + " })\n", + " res = resp.json()\n", + " res.update({'doc': doc})\n", + " if 'error' in res:\n", + " return {'ok': False}\n", + " update_spec = {\"$push\": {\"vk\": res}}\n", + " await mongo_client[db_name][item.get('col')].update_one({'_id': ObjectId(item.get('_id'))}, update_spec)\n", + " return {\"ok\": True}\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "242e59e6", + "metadata": {}, + "outputs": [], + "source": [ + "@router.post(\"/edit\")\n", + "async def edit_vk(data:dict, request: Request):\n", + " auth = request.headers.get(\"Authorization\")\n", + " if auth and auth.lower().startswith(\"bearer \"):\n", + " parts = auth.split(\" \", 1)\n", + " if len(parts) == 2:\n", + " if parts[1].strip() != ATOKEN:\n", + " return None\n", + " item = data['item']\n", + " text = data['text']\n", + " file_ids = data['file_ids']\n", + " doc = data['doc']\n", + " post_id = data['post_id']\n", + " sign_avatar = data['sign_avatar']\n", + "# async def edit_vk(item: dict, text: str, file_ids: list, doc: dict, sign_avatar: bool = True):\n", + " db_name = item.get('db')\n", + " if db_name == 'competitions':\n", + " social = await social_collection.find_one({'service': True, 'type':'vk'})\n", + " else:\n", + " social = doc\n", + " logging.info(doc)\n", + " logging.info(social)\n", + " images = await get_images(db_name, file_ids, sign_avatar)\n", + " async with httpx.AsyncClient(http2=True, verify=ctx) as client:\n", + " attachments = []\n", + " for _id, img in images.items():\n", + " vk_photos_key = f\"{item.get('_id')}:{_id}:{social.get('_id')}\"\n", + " doc_photo = await mongo_client['vk']['photos'].find_one({'id':vk_photos_key})\n", + " if doc_photo:\n", + " attachments.append(doc_photo['attachment'])\n", + " continue\n", + " payload = {\"v\": \"5.199\"}\n", + " if 'group' in social:\n", + " payload['group_id'] = social['id']\n", + " resp = await client.post(f\"{VK_API_URI}photos.getWallUploadServer\", headers={\"Authorization\": f\"Bearer {await token_manager.get_access_token(social['user_id'])}\"}, data=payload, timeout=30.0)\n", + " resp.raise_for_status()\n", + " upload_url = resp.json()['response']['upload_url']\n", + " resp = await client.post(upload_url, files=[img], timeout=60.0)\n", + " resp.raise_for_status()\n", + " payload.update(resp.json())\n", + " payload.update({'caption':text})\n", + " resp = await client.post(f\"{VK_API_URI}photos.saveWallPhoto\", headers={\"Authorization\": f\"Bearer {await token_manager.get_access_token(social['user_id'])}\"}, data=payload, timeout=30.0)\n", + " resp.raise_for_status()\n", + " attachment = ','.join([f'photo{i['owner_id']}_{i['id']}' for i in resp.json()['response']])\n", + " if not attachment:\n", + " return {'ok': False}\n", + " await mongo_client['vk']['photos'].insert_one({'attachment':attachment,'id':vk_photos_key})\n", + " attachments.append(attachment)\n", + " logging.info(attachments)\n", + " \n", + " resp = await client.post(f\"{VK_API_URI}wall.edit\", data={\n", + " \"access_token\": await token_manager.get_access_token(doc['user_id']),\n", + " \"owner_id\": f'-{doc['id']}' if 'group' in doc else doc['id'],\n", + " \"post_id\": post_id,\n", + " 'message': text,\n", + " 'attachments': attachments,\n", + " 'primary_attachments_mode': 'grid',\n", + " \"v\": \"5.199\",\n", + " })\n", + " res = resp.json()\n", + " res.update({'doc': doc})\n", + " if 'error' in res:\n", + " return {'ok': False}\n", + " update_spec = {\"$push\": {\"vk\": res}}\n", + " await mongo_client[db_name][item.get('col')].update_one({'_id': ObjectId(item.get('_id'))}, update_spec)\n", + " return {\"ok\": True}\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a7bb0794", + "metadata": {}, + "outputs": [], + "source": [ + "@router.post(\"/delete\")\n", + "async def wall_delete(data: dict, request: Request):\n", + " # ---- auth ----\n", + " auth = request.headers.get(\"Authorization\")\n", + " if auth and auth.lower().startswith(\"bearer \"):\n", + " token = auth.split(\" \", 1)[1].strip()\n", + " if token != ATOKEN:\n", + " return {\"ok\": False, \"error\": \"unauthorized\"}\n", + "\n", + " item = data[\"item\"]\n", + " post_id = data[\"post_id\"]\n", + " doc = data[\"doc\"]\n", + "\n", + " db_name = item.get(\"db\")\n", + " if db_name == \"competitions\":\n", + " social = await social_collection.find_one({\"service\": True, \"type\": \"vk\"})\n", + " else:\n", + " social = doc\n", + "\n", + " logging.info(doc)\n", + " logging.info(social)\n", + "\n", + " owner_id = f\"-{doc['id']}\" if \"group\" in doc else doc[\"id\"]\n", + "\n", + " # ---- VK request ----\n", + " async with httpx.AsyncClient(http2=True, verify=ctx) as client:\n", + " resp = await client.post(f\"{VK_API_URI}wall.delete\", data={\n", + " \"access_token\": await token_manager.get_access_token(doc[\"user_id\"]),\n", + " \"owner_id\": owner_id,\n", + " \"post_id\": post_id,\n", + " \"v\": \"5.199\",\n", + " })\n", + "\n", + " res = resp.json()\n", + " logging.info(f\"DELETE VK response: {res}\")\n", + " res[\"doc\"] = doc\n", + "\n", + " if \"error\" in res:\n", + " return {\"ok\": False, \"error\": res[\"error\"]}\n", + "\n", + " # ---- Mongo remove vk post record ----\n", + " col = item.get(\"col\")\n", + " mongo_col = mongo_client[db_name][col]\n", + "\n", + " mongo_col.update_one(\n", + " {\"_id\": ObjectId(item.get(\"_id\"))},\n", + " {\"$pull\": {\"vk\": {\"response\": {\"post_id\": post_id}}}}\n", + " )\n", + "\n", + " return {\"ok\": True}\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b5f808c", + "metadata": {}, + "outputs": [], + "source": [ + "@celery_app_vk.task(\n", + " bind=True,\n", + " rate_limit='60/m',\n", + " name='vk.edit_vk',\n", + " retry_backoff=True,\n", + " retry_backoff_max=600,\n", + " retry_jitter=True,\n", + " max_retries=20,\n", + " soft_time_limit=60,\n", + " time_limit=100,\n", + ")\n", + "def edit(self, post_id: int, item: dict, text: str, file_ids: list, social: dict, sign_avatar: bool = True):\n", + " try:\n", + " logging.info(f\"VK начало edit {item} {file_ids}\")\n", + " res = httpx.post(\n", + " 'http://kultmost-api.kultmost/api/v1/social/vk/edit', \n", + " headers={\"Authorization\": f\"bearer {ATOKEN}\"}, \n", + " json={'post_id': post_id, 'item': item, 'text': text, 'file_ids': file_ids, 'doc': social, 'sign_avatar': sign_avatar},\n", + " timeout=100\n", + " ).json()\n", + " if res['ok']:\n", + " return res\n", + " raise RuntimeError(f\"VK send failed: {res}\")\n", + " \n", + " except Exception as exc:\n", + " raise self.retry(exc=exc)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3da1d0f1", + "metadata": {}, + "outputs": [], + "source": [ + "@celery_app_vk.task(\n", + " bind=True,\n", + " rate_limit='60/m',\n", + " name='vk.wall_delete',\n", + " retry_backoff=True,\n", + " retry_backoff_max=600,\n", + " retry_jitter=True,\n", + " max_retries=20,\n", + " soft_time_limit=60,\n", + " time_limit=100,\n", + ")\n", + "def delete_wall(self, item: dict, post_id: int, doc: dict):\n", + " try:\n", + " logging.info(f\"VK удаления публикации {item} {post_id}\")\n", + " res = httpx.post(\n", + " 'http://kultmost-api.kultmost/api/v1/social/vk/delete',\n", + " headers={\"Authorization\": f\"bearer {ATOKEN}\"},\n", + " json={'item': item, 'post_id': post_id, \"doc\": doc},\n", + " timeout=100\n", + " ).json()\n", + " if res.get('ok'):\n", + " return res\n", + " raise RuntimeError(f\"VK send failed: {res}\")\n", + " \n", + " except Exception as exc:\n", + " raise self.retry(exc=exc)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20ac6800", + "metadata": {}, + "outputs": [], + "source": [ + "@celery_app_vk.task(\n", + " bind=True,\n", + " rate_limit='60/m',\n", + " name='vk.send_vk',\n", + " retry_backoff=True,\n", + " retry_backoff_max=600,\n", + " retry_jitter=True,\n", + " max_retries=20,\n", + " soft_time_limit=60,\n", + " time_limit=100,\n", + ")\n", + "def notify(self, item: dict, text: str, file_ids: list, social: dict, sign_avatar: bool = True):\n", + " try:\n", + " logging.info(f\"VK начало публикации {item} {file_ids}\")\n", + " res = httpx.post(\n", + " 'http://kultmost-api.kultmost/api/v1/social/vk/send',\n", + " headers={\"Authorization\": f\"bearer {ATOKEN}\"},\n", + " json={'item': item, 'text': text, 'file_ids': file_ids, 'doc': social, 'sign_avatar': sign_avatar},\n", + " timeout=100\n", + " ).json()\n", + " if res.get('ok'):\n", + " return res\n", + " raise RuntimeError(f\"VK send failed: {res}\")\n", + " \n", + " except Exception as exc:\n", + " raise self.retry(exc=exc)\n" + ] + }, + { + "cell_type": "markdown", + "id": "8810213f", + "metadata": {}, + "source": [ + "## Run" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dacd2f6e", + "metadata": {}, + "outputs": [], + "source": [ + "# ignore \n", + "# async for post in posts_collection.find({}).sort('_id', -1).limit(50):\n", + "# vk_posts = post.get('vk')\n", + "# if len(vk_posts) > 1:\n", + "# print('ok')\n", + "# post_ref = {'_id': post['_id'], 'db': 'posts', 'col': 'posts'}\n", + " \n", + "# res = httpx.post(\n", + "# 'https://kultmost.spiritofweb.ru/api/v1/social/vk/delete',\n", + "# headers={\"Authorization\": f\"bearer {ATOKEN}\"},\n", + "# json={'item': bson_clear(post_ref), 'post_id': int(vk_posts[-1]['response']['post_id']), \"doc\": bson_clear(vk_posts[-1]['doc'])},\n", + "# timeout=100\n", + "# ).json()\n", + "# await asyncio.sleep(1)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0e75e5f2", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import os\n", + "\n", + "IGNORE = ['build.ipynb', 'data.ipynb']\n", + "\n", + "for _filename in [i for i in os.listdir() if 'ipynb' in i and i not in IGNORE]:\n", + " filename_without_extension = os.path.splitext(os.path.basename(_filename))[0]\n", + "\n", + " with open(f'{filename_without_extension}.ipynb', 'r') as f:\n", + " data = f.read()\n", + "\n", + " main = json.loads(data)\n", + " app = []\n", + " for cell in main['cells']:\n", + " if '## Run' in cell['source']:\n", + " break\n", + " if '# ignore\\n' in cell['source']:\n", + " continue\n", + " app.append(''.join(cell['source']))\n", + "\n", + " with open(f'{filename_without_extension}.py', 'w') as f:\n", + " f.write('\\n\\n'.join(app))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6f76c7c1", + "metadata": {}, + "outputs": [], + "source": [ + "!celery -A vk.celery_app_vk worker --loglevel=info" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b0d41fb5", + "metadata": {}, + "outputs": [], + "source": [ + "!docker build -t registry.spiritofweb.ru/avtopost-api:latest ." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "674d49e8", + "metadata": {}, + "outputs": [], + "source": [ + "!sudo docker image push registry.spiritofweb.ru/avtopost-api:latest" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65b3c103", + "metadata": {}, + "outputs": [], + "source": [ + "!kubectl rollout restart deployments/vk-celery -n kultmost" + ] + }, + { + "cell_type": "markdown", + "id": "62a866f8", + "metadata": {}, + "source": [ + "## Test" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0b930db4", + "metadata": {}, + "outputs": [], + "source": [ + "stop" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7fbab37f", + "metadata": {}, + "outputs": [], + "source": [ + "user_id = '681684390000000000000000'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ab179fe7", + "metadata": {}, + "outputs": [], + "source": [ + "await redis_client.delete(f\"access_token:{user_id}\")\n", + "await redis_client.delete(f\"tokens:{user_id}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dce288c3", + "metadata": {}, + "outputs": [], + "source": [ + "uid = ObjectId(user_id)\n", + "\n", + "doc = {}\n", + "doc.update({\n", + " \"expires_at\": 55435\n", + "})\n", + "\n", + "await mongo_client['test']['123'].update_one(\n", + " {\"user_ids\": uid},\n", + " {\n", + " \"$set\": doc,\n", + " \"$setOnInsert\": {\"user_ids\": [uid]},\n", + " },\n", + " upsert=True,\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d1c2c193", + "metadata": {}, + "outputs": [], + "source": [ + "import token_manager as tm" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c8b3a406", + "metadata": {}, + "outputs": [], + "source": [ + "from token_manager import TokenManager\n", + "token_manager = TokenManager()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fbe436c7", + "metadata": {}, + "outputs": [], + "source": [ + "token_manager = TokenManager()\n", + "access_token = await token_manager.get_access_token('644fa8bb0000000000000000')\n", + "access_token" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fe977ad0", + "metadata": {}, + "outputs": [], + "source": [ + "item ={'_id': '6a21559cca3ad8a73b652bcb', 'db': 'competitions', 'col': 'competitions'} \n", + "file_ids = ['681684390000000000000000', '6a21559cca3ad8a73b652bc6']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8abc66f", + "metadata": {}, + "outputs": [], + "source": [ + "docs =[ doc async for doc in mongo_client['social']['social'].find({'user_id':'681684390000000000000000'})]\n", + "doc = docs[1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42b797b5", + "metadata": {}, + "outputs": [], + "source": [ + "competition = await competitions_col.find_one({'_id':ObjectId('6a21559cca3ad8a73b652bcb')})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "110260d2", + "metadata": {}, + "outputs": [], + "source": [ + "images = await get_images(db_name, file_ids, sign_avatar)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e2537fa4", + "metadata": {}, + "outputs": [], + "source": [ + "async with httpx.AsyncClient(http2=True, verify=ctx) as client:\n", + " attachments = []\n", + " for _id, img in images.items():\n", + " doc_photo = await mongo_client['vk']['photos'].find_one({'id':f\"{_id}:{doc.get('_id')}\"})\n", + " if doc_photo:\n", + " attachments.append(doc_photo['attachment'])\n", + " continue\n", + " payload = {\"v\": \"5.199\"}\n", + " if 'group' in doc:\n", + " payload['group_id'] = doc['id']\n", + " resp = await client.post(f\"{VK_API_URI}photos.getWallUploadServer\", headers={\"Authorization\": f\"Bearer {await token_manager.get_access_token(doc['user_id'])}\"}, data=payload, timeout=30.0)\n", + " resp.raise_for_status()\n", + " upload_url = resp.json()['response']['upload_url']\n", + " resp = await client.post(upload_url, files=[img], timeout=60.0)\n", + " resp.raise_for_status()\n", + " payload.update(resp.json())\n", + " payload.update({'caption':text})\n", + " resp = await client.post(f\"{VK_API_URI}photos.saveWallPhoto\", headers={\"Authorization\": f\"Bearer {await token_manager.get_access_token(doc['user_id'])}\"}, data=payload, timeout=30.0)\n", + " resp.raise_for_status()\n", + " attachment = ','.join([f'photo{i['owner_id']}_{i['id']}' for i in resp.json()['response']])\n", + " await mongo_client['vk']['photos'].insert_one({'attachment':attachment,'id':f\"{_id}:{doc.get('_id')}\"})\n", + " attachments.append(attachment)\n", + " print(attachments)\n", + " resp = await client.post(f\"{VK_API_URI}wall.post\", data={\n", + " \"access_token\": await token_manager.get_access_token(doc['user_id']),\n", + " \"owner_id\": f'-{doc['id']}' if 'group' in doc else doc['id'],\n", + " 'message': text,\n", + " 'attachments': attachments,\n", + " 'primary_attachments_mode': 'grid',\n", + " \"v\": \"5.199\",\n", + " })\n", + " res = resp.json()\n", + " res.update({'doc': doc})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7b531356", + "metadata": {}, + "outputs": [], + "source": [ + "res" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e994cda8", + "metadata": {}, + "outputs": [], + "source": [ + "@router.post(\"/send\")\n", + "async def send_vk(data:dict):\n", + " item = data['item']\n", + " text = data['text']\n", + " file_ids = data['file_ids']\n", + " doc = data['doc']\n", + " \n", + " sign_avatar = data['sign_avatar']\n", + "# async def send_vk(item: dict, text: str, file_ids: list, doc: dict, sign_avatar: bool = True):\n", + " \n", + " if db_name == 'competitions':\n", + " social = await social_collection.find_one({'service': True, 'type':'vk'})\n", + " else:\n", + " social = doc\n", + " logging.info(doc)\n", + " logging.info(social)\n", + " \n", + " async with httpx.AsyncClient(http2=True, verify=ctx) as client:\n", + " attachments = []\n", + " for _id, img in images.items():\n", + " doc_photo = await mongo_client['vk']['photos'].find_one({'id':f\"{_id}:{social.get('_id')}\"})\n", + " if doc_photo:\n", + " attachments.append(doc_photo['attachment'])\n", + " continue\n", + " payload = {\"v\": \"5.199\"}\n", + " if 'group' in social:\n", + " payload['group_id'] = social['id']\n", + " resp = await client.post(f\"{VK_API_URI}photos.getWallUploadServer\", headers={\"Authorization\": f\"Bearer {await token_manager.get_access_token(social['user_id'])}\"}, data=payload, timeout=30.0)\n", + " resp.raise_for_status()\n", + " upload_url = resp.json()['response']['upload_url']\n", + " resp = await client.post(upload_url, files=[img], timeout=60.0)\n", + " resp.raise_for_status()\n", + " payload.update(resp.json())\n", + " payload.update({'caption':text})\n", + " resp = await client.post(f\"{VK_API_URI}photos.saveWallPhoto\", headers={\"Authorization\": f\"Bearer {await token_manager.get_access_token(social['user_id'])}\"}, data=payload, timeout=30.0)\n", + " resp.raise_for_status()\n", + " attachment = ','.join([f'photo{i['owner_id']}_{i['id']}' for i in resp.json()['response']])\n", + " if not attachment:\n", + " return {'ok': False}\n", + " await mongo_client['vk']['photos'].insert_one({'attachment':attachment,'id':f\"{_id}:{social.get('_id')}\"})\n", + " attachments.append(attachment)\n", + " logging.info(attachments)\n", + " \n", + " resp = await client.post(f\"{VK_API_URI}wall.post\", data={\n", + " \"access_token\": await token_manager.get_access_token(doc['user_id']),\n", + " \"owner_id\": f'-{doc['id']}' if 'group' in doc else doc['id'],\n", + " 'message': text,\n", + " 'attachments': attachments,\n", + " 'primary_attachments_mode': 'grid',\n", + " \"v\": \"5.199\",\n", + " })\n", + " res = resp.json()\n", + " res.update({'doc': doc})\n", + " if 'error' in res:\n", + " return {'ok': False}\n", + " update_spec = {\"$push\": {\"vk\": res}}\n", + " await mongo_client[db_name][item.get('col')].update_one({'_id': ObjectId(item.get('_id'))}, update_spec)\n", + " return {\"ok\": True}\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d532511", + "metadata": {}, + "outputs": [], + "source": [ + "stream = await gridfs_map['competition'].open_download_stream(ObjectId(competition['poster_file_id']))\n", + "data = await stream.read()\n", + "\n", + "files = {\"photo\": (stream.name, BytesIO(data), \"image/jpeg\")}\n", + "async with httpx.AsyncClient(verify=ctx, http2=True) as client:\n", + " payload = {\"v\": \"5.199\"}\n", + " if 'group' in doc:\n", + " payload['group_id'] = doc['id']\n", + " resp_get_upload_server = await client.post(f\"{VK_API_URI}photos.getWallUploadServer\", headers={\"Authorization\": f\"Bearer {access_token}\"}, data=payload, timeout=30.0)\n", + " resp_get_upload_server.raise_for_status()\n", + " upload_url = resp_get_upload_server.json()['response']['upload_url']\n", + " resp_upload = await client.post(upload_url, files=files, timeout=60.0)\n", + " resp_upload.raise_for_status()\n", + " payload.update(resp_upload.json())\n", + " payload.update({'caption':text})\n", + " resp = await client.post(f\"{VK_API_URI}photos.saveWallPhoto\", headers={\"Authorization\": f\"Bearer {access_token}\"}, data=payload, timeout=30.0)\n", + " resp.raise_for_status()\n", + " print(resp.json())\n", + " attachments = ','.join([f'photo{i['owner_id']}_{i['id']}' for i in resp.json()['response']])\n", + " print(attachments)\n", + " resp_wall = await client.post(f\"{VK_API_URI}wall.post\", data={\n", + " \"access_token\": access_token,\n", + " \"owner_id\": f'-{doc['id']}' if 'group' in doc else doc['id'],\n", + " 'message': text,\n", + " 'attachments': attachments,\n", + " 'primary_attachments_mode': 'grid',\n", + " \"v\": \"5.199\",\n", + " })\n", + " print(resp_wall.json())\n", + " \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8360f581", + "metadata": {}, + "outputs": [], + "source": [ + "await competitions_col.find_one({'_id': competition['_id']})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "04953ea6", + "metadata": {}, + "outputs": [], + "source": [ + "await competitions_col.update_one(\n", + " {'_id': competition['_id']},\n", + " {\n", + " \"$push\": {\"vk\": resp_wall.json()}\n", + " },\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "415c9000", + "metadata": {}, + "outputs": [], + "source": [ + "resp = httpx.post(f\"{VK_API_URI}users.get\", data={\n", + " \"access_token\": access_token,\n", + " \"fields\": \"photo_max_orig,screen_name\",\n", + " \"v\": \"5.199\",\n", + " })\n", + "resp.json()['response'][0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8feb5878", + "metadata": {}, + "outputs": [], + "source": [ + "[doc async for doc in mongo_client['social']['social'].find({'user_id':competition['user']['id']})]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1316ebc0", + "metadata": {}, + "outputs": [], + "source": [ + "z=httpx.post(f\"{VK_API_URI}wall.edit\", data={\n", + " 'access_token':access_token,\n", + " 'post_id':2970,\n", + " 'attachments': 'photo25126640_457244944',\n", + " 'message':text,\n", + " 'v':'5.199',\n", + "})\n", + "\n", + "z.json()['response']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a6f67f24", + "metadata": {}, + "outputs": [], + "source": [ + "#!pip freeze > requirements.txt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eea60230", + "metadata": { + "vscode": { + "languageId": "javascript" + } + }, + "outputs": [], + "source": [ + "await fetch(\"https://api.vk.com/method/groups.editManager?v=5.269&client_id=6287487\", {\n", + " \"headers\": {\n", + " \"Accept\": \"*/*\",\n", + " },\n", + " \"body\": \"group_id=191599084&user_id=392892935&role=moderator&is_contact=0&contact_position=&contact_phone=&contact_email=&permissions=&access_token=vk1.a.-kG07OUZgX5FrxtsQrU5QJh8HaP_suYXkqcg\",\n", + " \"method\": \"POST\",\n", + "});" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c878e08d", + "metadata": { + "vscode": { + "languageId": "javascript" + } + }, + "outputs": [], + "source": [ + "OAUTH_URL = 'https://id.vk.com/oauth2/auth'\n", + "AUTHORIZE_URL = 'https://id.vk.ru/authorize'\n", + "VK_CLIENT_ID = 51852444\n", + "SCOPES = 'offline,wall,photos,groups'\n", + "redirect_uri = 'https://oauth.vk.ru/blank.html'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b990c1c5", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "AUTHORIZE_URL = 'https://id.vk.ru/authorize'\n", + "VK_CLIENT_ID = 51852444\n", + "SCOPES = 'offline,wall,photos,groups'\n", + "redirect_uri = 'https://oauth.vk.ru/blank.html'\n", + "\n", + "import os\n", + "import hashlib\n", + "import base64\n", + "from urllib.parse import urlencode\n", + "\n", + "\n", + "state = os.urandom(32).hex()\n", + "code_verifier = os.urandom(50).hex()\n", + "\n", + "code_challenge = base64.urlsafe_b64encode(hashlib.sha256(code_verifier.encode()).digest()).decode()\n", + "code_challenge = code_challenge.rstrip('=')\n", + "\n", + "\n", + "url = f'{AUTHORIZE_URL}?responsetype_item=code&client_id={VK_CLIENT_ID}&scope={SCOPES}&redirect_uri={redirect_uri}&state={state}&code_challenge={code_challenge}&code_challenge_method=S256'\n", + "\n", + "print(f\"\\n\\n{url}\\n\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23ee767f", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "code='vk2.a.---'\n", + "device_id=''" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6a5f9469", + "metadata": {}, + "outputs": [], + "source": [ + "refresh_token = \"vk2.a.-----\"\n", + "device_id = \"-\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c04637d", + "metadata": {}, + "outputs": [], + "source": [ + "import httpx\n", + "import os\n", + "z=httpx.post('https://id.vk.com/oauth2/auth', data={\n", + " 'grant_type':'refresh_token',\n", + " 'refresh_token':refresh_token,\n", + " 'client_id':51852444,\n", + " 'device_id':device_id,\n", + " 'state': os.urandom(32).hex(),\n", + "})\n", + "z.json()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19d44789", + "metadata": {}, + "outputs": [], + "source": [ + "CLIENT_ID = 51852444\n", + "OAUTH_URL = 'https://id.vk.com/oauth2/auth'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b21a826", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "import httpx\n", + "r=httpx.post('https://id.vk.com/oauth2/auth',data={\n", + " 'state':os.urandom(32).hex(),\n", + " 'grant_type':'authorization_code',\n", + " 'code_verifier':code_verifier,\n", + " 'code':code,\n", + " 'client_id':VK_CLIENT_ID,\n", + " 'device_id':device_id,\n", + " 'redirect_uri': 'https://oauth.vk.com/blank.html'\n", + "})\n", + "r.json()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1dd76a96", + "metadata": {}, + "outputs": [], + "source": [ + "class T():\n", + " def json(self):\n", + " return {'refresh_token': 'vk2.a.----',\n", + " 'access_token': 'vk2.a.-',\n", + " 'token_type': 'Bearer',\n", + " 'expires_in': 3600,\n", + " 'user_id': 888082464,\n", + " 'state': '',\n", + " 'scope': 'photos wall offline'}\n", + "\n", + "z=T()\n", + "z.json()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "935136ea", + "metadata": {}, + "outputs": [], + "source": [ + "refresh_token=r.json()['refresh_token']\n", + "refresh_token" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "330eb2f4", + "metadata": {}, + "outputs": [], + "source": [ + "import httpx\n", + "import os\n", + "z=httpx.post('https://id.vk.com/oauth2/auth', data={\n", + " 'grant_type':'refresh_token',\n", + " 'refresh_token':refresh_token,\n", + " 'client_id':51852444,\n", + " 'device_id':device_id,\n", + " 'state': os.urandom(32).hex(),\n", + "})\n", + "z.json()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8996e0b9", + "metadata": {}, + "outputs": [], + "source": [ + "print(f'\\n\\n{z.json()['refresh_token']}\\n\\n')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "da4184df", + "metadata": {}, + "outputs": [], + "source": [ + "users.search" + ] + }, + { + "cell_type": "markdown", + "id": "282d1110", + "metadata": {}, + "source": [ + "q\n", + "\n", + "string\n", + "\n", + "Строка поискового запроса. Например, Вася Бабич.\n", + "sort\n", + "\n", + "integer\n", + "\n", + "Сортировка результатов. Возможные значения:\n", + "\n", + " •\n", + " 1 — по дате регистрации,\n", + " •\n", + " 0 — по популярности.\n", + "\n", + "offset\n", + "\n", + "positive\n", + "\n", + "Смещение относительно первого найденного пользователя для выборки определенного подмножества.\n", + "count\n", + "\n", + "positive\n", + "\n", + "Количество возвращаемых пользователей.\n", + "\n", + " Обратите внимание, даже при использовании параметра offset для получения информации доступны только первые 1000 результатов.\n", + "\n", + "fields\n", + "\n", + "string\n", + "\n", + "Список дополнительных полей профилей, которые необходимо вернуть. См. подробное описание. Доступные значения:\n", + "\n", + " •\n", + " about,\n", + " •\n", + " activities,\n", + " •\n", + " bdate,\n", + " •\n", + " blacklisted,\n", + " •\n", + " blacklisted_by_me\n", + " •\n", + " books,\n", + " •\n", + " can_post,\n", + " •\n", + " can_see_all_posts,\n", + " •\n", + " can_see_audio,\n", + " •\n", + " can_send_friend_request,\n", + " •\n", + " can_write_private_message,\n", + " •\n", + " career,\n", + " •\n", + " city,\n", + " •\n", + " common_count,\n", + " •\n", + " connections,\n", + " •\n", + " contacts,\n", + " •\n", + " country,\n", + " •\n", + " crop_photo,\n", + " •\n", + " domain,\n", + " •\n", + " education,\n", + " •\n", + " exports,\n", + " •\n", + " followers_count,\n", + " •\n", + " friend_status,\n", + " •\n", + " games,\n", + " •\n", + " has_mobile,\n", + " •\n", + " has_photo,\n", + " •\n", + " home_town,\n", + " •\n", + " interests,\n", + " •\n", + " is_favorite,\n", + " •\n", + " is_friend,\n", + " •\n", + " is_hidden_from_feed,\n", + " •\n", + " last_seen,\n", + " •\n", + " lists,\n", + " •\n", + " maiden_name,\n", + " •\n", + " military,\n", + " •\n", + " movies,\n", + " •\n", + " music,\n", + " •\n", + " nickname,\n", + " •\n", + " occupation,\n", + " •\n", + " online,\n", + " •\n", + " personal,\n", + " •\n", + " photo_100,\n", + " •\n", + " photo_200,\n", + " •\n", + " photo_200_orig,\n", + " •\n", + " photo_400_orig,\n", + " •\n", + " photo_50,\n", + " •\n", + " photo_id,\n", + " •\n", + " photo_max,\n", + " •\n", + " photo_max_orig,\n", + " •\n", + " quotes,\n", + " •\n", + " relation,\n", + " •\n", + " relatives,\n", + " •\n", + " schools,\n", + " •\n", + " screen_name,\n", + " •\n", + " sex,\n", + " •\n", + " site,\n", + " •\n", + " status,\n", + " •\n", + " timezone,\n", + " •\n", + " tv,\n", + " •\n", + " universities,\n", + " •\n", + " verified,\n", + " •\n", + " wall_comments.\n", + "\n", + "city\n", + "\n", + "positive\n", + "\n", + "Идентификатор города.\n", + "city_id\n", + "\n", + "positive\n", + "\n", + "Идентификатор города для обратной совместимости. Используйте city\n", + "country\n", + "\n", + "positive\n", + "\n", + "Идентификатор страны.\n", + "country_id\n", + "\n", + "positive\n", + "\n", + "Идентификатор страны для обратной совместимости. Используйте country\n", + "hometown\n", + "\n", + "string\n", + "\n", + "Название города строкой.\n", + "university_country\n", + "\n", + "positive\n", + "\n", + "Идентификатор страны, в которой пользователи закончили ВУЗ.\n", + "university\n", + "\n", + "positive\n", + "\n", + "Идентификатор ВУЗа.\n", + "university_year\n", + "\n", + "positive\n", + "\n", + "Год окончания ВУЗа.\n", + "university_faculty\n", + "\n", + "positive\n", + "\n", + "Идентификатор факультета.\n", + "university_chair\n", + "\n", + "positive\n", + "\n", + "Идентификатор кафедры.\n", + "sex\n", + "\n", + "positive\n", + "\n", + "Пол. Возможные значения:\n", + "\n", + " •\n", + " 1 — женщина,\n", + " •\n", + " 2 — мужчина,\n", + " •\n", + " 0 — любой (по умолчанию).\n", + "\n", + "status\n", + "\n", + "positive\n", + "\n", + "Семейное положение. Возможные значения:\n", + "\n", + " •\n", + " 1 — не женат (не замужем),\n", + " •\n", + " 2 — встречается,\n", + " •\n", + " 3 — помолвлен(-а),\n", + " •\n", + " 4 — женат (замужем),\n", + " •\n", + " 5 — всё сложно,\n", + " •\n", + " 6 — в активном поиске,\n", + " •\n", + " 7 — влюблен(-а),\n", + " •\n", + " 8 — в гражданском браке.\n", + "\n", + "age_from\n", + "\n", + "positive\n", + "\n", + "Возраст, от.\n", + "age_to\n", + "\n", + "positive\n", + "\n", + "Возраст, до.\n", + "birth_day\n", + "\n", + "positive\n", + "\n", + "День рождения.\n", + "birth_month\n", + "\n", + "positive\n", + "\n", + "Месяц рождения.\n", + "birth_year\n", + "\n", + "positive\n", + "\n", + "Год рождения.\n", + "online\n", + "\n", + "checkbox\n", + "\n", + "Учитывать ли статус «онлайн». Возможные значения:\n", + "\n", + " •\n", + " 1 — искать только пользователей онлайн,\n", + " •\n", + " 0 — искать по всем пользователям.\n", + "\n", + "has_photo\n", + "\n", + "checkbox\n", + "\n", + "Учитывать ли наличие фото. Возможные значения:\n", + "\n", + " •\n", + " 1 — искать только пользователей с фотографией,\n", + " •\n", + " 0 — искать по всем пользователям.\n", + "\n", + "school_country\n", + "\n", + "positive\n", + "\n", + "Идентификатор страны, в которой пользователи закончили школу.\n", + "school_city\n", + "\n", + "positive\n", + "\n", + "Идентификатор города, в котором пользователи закончили школу.\n", + "school_class\n", + "\n", + "positive\n", + "\n", + "Буква класса.\n", + "school\n", + "\n", + "positive\n", + "\n", + "Идентификатор школы, которую закончили пользователи.\n", + "school_year\n", + "\n", + "positive\n", + "\n", + "Год окончания школы.\n", + "religion\n", + "\n", + "string\n", + "\n", + "Религиозные взгляды.\n", + "company\n", + "\n", + "string\n", + "\n", + "Название компании, в которой работают пользователи.\n", + "position\n", + "\n", + "string\n", + "\n", + "Название должности.\n", + "group_id\n", + "\n", + "integer\n", + "\n", + "Идентификатор группы, среди пользователей которой необходимо проводить поиск.\n", + "from_list\n", + "\n", + "string\n", + "\n", + "Разделы среди которых нужно осуществить поиск, перечисленные через запятую. Возможные значения:\n", + "\n", + " •\n", + " friends — искать среди друзей,\n", + " •\n", + " subscriptions — искать среди друзей и подписок пользователя.\n", + "\n", + "screen_ref\n", + "\n", + "string\n", + "\n", + "Реферер, откуда был вызван метод\n", + "from_group_id\n", + "\n", + "integer\n", + "Результат\n", + "\n", + "После успешного выполнения возвращает объект, содержащий число результатов в поле count и массив объектов, описывающих пользователей в поле items." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}