avtopost.kultmost.ru/vk.ipynb
2026-07-19 18:20:59 -04:00

1556 lines
49 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"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
}