From 2d0d72b026bd1a0bac0f4408039f63e0d34a2e87 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 23 Mar 2023 15:03:30 +0100 Subject: [PATCH] WIP: API change --- classes/poster_client.py | 29 +++++++++------ modules/api_client.py | 72 +++++++++++++++++++++++++++++++++++--- modules/cli.py | 6 ++-- modules/sender.py | 38 +++++++++++++------- plugins/commands/photos.py | 51 +++++++++++++++++++++------ poster.py | 31 +++++++--------- 6 files changed, 168 insertions(+), 59 deletions(-) diff --git a/classes/poster_client.py b/classes/poster_client.py index 94be2c6..8deecb7 100644 --- a/classes/poster_client.py +++ b/classes/poster_client.py @@ -1,9 +1,8 @@ +from http import HTTPStatus +from ujson import loads from os import path, remove, sep from shutil import rmtree from typing import Tuple, Union -from photosapi_client.api.default.photo_upload_albums_album_photos_post import ( - asyncio as upload_pic, -) from photosapi_client.models.body_photo_upload_albums_album_photos_post import ( BodyPhotoUploadAlbumsAlbumPhotosPost, ) @@ -13,7 +12,7 @@ import aiofiles from pyrogram.client import Client from pyrogram.types import Message from classes.exceptions import SubmissionDuplicatesError, SubmissionUnavailableError -from modules.api_client import client +from modules.api_client import client, photo_upload from modules.database import col_submitted from bson import ObjectId from modules.logger import logWrite @@ -75,7 +74,7 @@ class PosterClient(Client): async with aiofiles.open(filepath, "rb") as f: file_bytes = await f.read() - response = await upload_pic( + response = await photo_upload( album=configGet("album", "posting", "api"), client=client, multipart_data=BodyPhotoUploadAlbumsAlbumPhotosPost( @@ -86,10 +85,20 @@ class PosterClient(Client): compress=False, ) - if isinstance(response, HTTPValidationError) > 0: - raise SubmissionDuplicatesError( - str(filepath), response.to_dict()["duplicates"] - ) + print(response, flush=True) + + if response.status_code == HTTPStatus.CONFLICT: + duplicates = [] + for index, duplicate in enumerate(loads(response.content)["duplicates"]): # type: ignore + if loads(response.content)["access_token"] is None: + duplicates.append( + f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/photos/{duplicate["id"]}' + ) + else: + duplicates.append( + f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/token/photo/{loads(response.content)["access_token"]}?id={index}' + ) + raise SubmissionDuplicatesError(str(filepath), duplicates) col_submitted.find_one_and_update( {"_id": ObjectId(id)}, {"$set": {"done": True}} @@ -112,7 +121,7 @@ class PosterClient(Client): f"Could not delete '{filepath}' on submission accepted", debug=True ) - return submission, response[2] + return submission, response.parsed.id async def ban_user(self, id: int) -> None: pass diff --git a/modules/api_client.py b/modules/api_client.py index 49baa60..5a0039a 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -1,6 +1,45 @@ """This is only a temporary solution. Complete Photos API client is yet to be developed.""" -from photosapi_client import AuthenticatedClient +import asyncio +from photosapi_client import AuthenticatedClient, Client +from photosapi_client.api.default.user_me_users_me_get import sync as user_me +from photosapi_client.api.default.user_create_users_post import asyncio as user_create +from photosapi_client.api.default.login_for_access_token_token_post import ( + sync as login, +) +from photosapi_client.api.default.video_find_albums_album_videos_get import ( + asyncio as video_find, +) +from photosapi_client.api.default.album_find_albums_get import asyncio as album_find +from photosapi_client.api.default.album_create_albums_post import ( + asyncio as album_create, +) +from photosapi_client.api.default.album_delete_album_id_delete import ( + asyncio as album_delete, +) +from photosapi_client.api.default.photo_find_albums_album_photos_get import ( + asyncio as photo_find, +) +from photosapi_client.api.default.photo_patch_photos_id_patch import ( + asyncio as photo_patch, +) +from photosapi_client.api.default.photo_delete_photos_id_delete import ( + asyncio as photo_delete, +) +from photosapi_client.api.default.photo_upload_albums_album_photos_post import ( + asyncio_detailed as photo_upload, +) +from photosapi_client.api.default.photo_get_photos_id_get import asyncio as photo_get + +from photosapi_client.models.body_photo_upload_albums_album_photos_post import ( + BodyPhotoUploadAlbumsAlbumPhotosPost, +) +from photosapi_client.types import File +from photosapi_client.models.token import Token +from photosapi_client.models.http_validation_error import HTTPValidationError +from photosapi_client.models.body_login_for_access_token_token_post import ( + BodyLoginForAccessTokenTokenPost, +) # import asyncio from base64 import b64decode, b64encode @@ -72,8 +111,33 @@ async def authorize() -> str: return (await response.json())["access_token"] +unauthorized_client = Client( + base_url=configGet("address", "posting", "api"), + timeout=5.0, + verify_ssl=True, + raise_on_unexpected_status=True, +) + +login_token = login( + client=unauthorized_client, + form_data=BodyLoginForAccessTokenTokenPost( + grant_type="password", + scope="me albums.list albums.read albums.write photos.list photos.read photos.write videos.list videos.read videos.write", + username=configGet("username", "posting", "api"), + password=configGet("password", "posting", "api"), + ), +) + +if not isinstance(login_token, Token): + logWrite(f"Could not initialize connection due to invalid token: {login_token}") + exit() + client = AuthenticatedClient( - base_url=configGet("address", "posting", "api"), token=await authorize() + base_url=configGet("address", "posting", "api"), + timeout=5.0, + verify_ssl=True, + raise_on_unexpected_status=True, + token=login_token.access_token, ) # async def random_pic(token: Union[str, None] = None) -> Tuple[str, str]: @@ -267,5 +331,5 @@ client = AuthenticatedClient( # return None -# if __name__ == "__main__": -# print(asyncio.run(authorize())) +if __name__ == "__main__": + print(asyncio.run(authorize())) diff --git a/modules/cli.py b/modules/cli.py index 0e4768e..2a8d84f 100644 --- a/modules/cli.py +++ b/modules/cli.py @@ -1,7 +1,7 @@ import asyncio from sys import exit from traceback import print_exc -from modules.api_client import create_album, create_user, http_session +from modules.api_client import album_create, user_create, http_session from argparse import ArgumentParser from modules.utils import configSet @@ -26,7 +26,7 @@ async def cli_create_user() -> None: email = input(f"Choose email for user '{username}': ").strip() password = input(f"Choose password for user '{username}': ").strip() try: - result_1 = await create_user(username, email, password) + result_1 = await user_create(username, email, password) # asyncio.run(create_user(username, email, password)) configSet("username", username, "posting", "api") configSet("password", password, "posting", "api") @@ -52,7 +52,7 @@ async def cli_create_album() -> None: name = input("Choose a name for your album: ").strip() title = input(f"Choose a title for album '{name}': ").strip() try: - result_2 = await create_album(name, title) + result_2 = await album_create(name, title) # asyncio.run(create_album(name, title)) configSet("album", name, "posting", "api") except Exception as exp: diff --git a/modules/sender.py b/modules/sender.py index ed4de5a..67ccb12 100644 --- a/modules/sender.py +++ b/modules/sender.py @@ -9,7 +9,14 @@ import aiofiles from classes.poster_client import PosterClient -from modules.api_client import authorize, move_pic, random_pic, http_session +from modules.api_client import ( + client, + authorize, + photo_find, + photo_get, + photo_patch, + http_session, +) from modules.database import col_sent, col_submitted from modules.logger import logWrite from modules.utils import configGet, locale @@ -27,8 +34,15 @@ async def send_content(app: PosterClient) -> None: return try: - pic = await random_pic() - except KeyError: + search_result = await photo_find( + client=client, + album=configGet("album", "posting", "api"), + caption="queue", + page_size=configGet("page_size", "posting"), + ) + print(search_result, flush=True) + pic = choice(search_result.results) + except (IndexError, KeyError): logWrite(locale("post_empty", "console", locale=configGet("locale"))) if configGet("error", "reports"): await app.send_message( @@ -45,7 +59,7 @@ async def send_content(app: PosterClient) -> None: return response = await http_session.get( - f'{configGet("address", "posting", "api")}/photos/{pic[0]}', + f'{configGet("address", "posting", "api")}/photos/{pic.id}', headers={"Authorization": f"Bearer {token}"}, ) @@ -67,7 +81,7 @@ async def send_content(app: PosterClient) -> None: makedirs(path.join(configGet("tmp", "locations"), tmp_dir), exist_ok=True) - tmp_path = path.join(tmp_dir, pic[1]) + tmp_path = path.join(tmp_dir, pic.filename) async with aiofiles.open( path.join(configGet("tmp", "locations"), tmp_path), "wb" @@ -75,7 +89,7 @@ async def send_content(app: PosterClient) -> None: await out_file.write(await response.read()) logWrite( - f'Candidate {pic[1]} ({pic[0]}) is {path.getsize(path.join(configGet("tmp", "locations"), tmp_path))} bytes big', + f'Candidate {pic.filename} ({pic.id}) is {path.getsize(path.join(configGet("tmp", "locations"), tmp_path))} bytes big', debug=True, ) @@ -107,7 +121,7 @@ async def send_content(app: PosterClient) -> None: del response - submitted = col_submitted.find_one({"temp.file": pic[1]}) + submitted = col_submitted.find_one({"temp.file": pic.filename}) if submitted is not None and submitted["caption"] is not None: caption = submitted["caption"].strip() @@ -144,7 +158,7 @@ async def send_content(app: PosterClient) -> None: disable_notification=configGet("silent", "posting"), ) except Exception as exp: - logWrite(f"Could not send image {pic[1]} ({pic[0]}) due to {exp}") + logWrite(f"Could not send image {pic.filename} ({pic.id}) due to {exp}") if configGet("error", "reports"): await app.send_message( app.owner, @@ -158,8 +172,8 @@ async def send_content(app: PosterClient) -> None: col_sent.insert_one( { "date": datetime.now(), - "image": pic[0], - "filename": pic[1], + "image": pic.id, + "filename": pic.filename, "channel": configGet("channel", "posting"), "caption": None if (submitted is None or submitted["caption"] is None) @@ -167,13 +181,13 @@ async def send_content(app: PosterClient) -> None: } ) - await move_pic(pic[0]) + await photo_patch(client=client, id=pic.id, caption="sent") rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True) logWrite( locale("post_sent", "console", locale=configGet("locale")).format( - pic[0], + pic.id, str(configGet("channel", "posting")), caption.replace("\n", "%n"), str(configGet("silent", "posting")), diff --git a/plugins/commands/photos.py b/plugins/commands/photos.py index e56a798..3e0e0a4 100644 --- a/plugins/commands/photos.py +++ b/plugins/commands/photos.py @@ -5,16 +5,24 @@ from shutil import disk_usage, rmtree from traceback import format_exc from uuid import uuid4 from zipfile import ZipFile +import aiofiles from convopyro import listen_message from pyrogram import filters from pyrogram.types import Message from classes.poster_client import PosterClient -from modules.api_client import remove_pic, upload_pic from modules.app import app, users_with_context from modules.logger import logWrite from modules.utils import configGet, extract_and_save, locale +from modules.api_client import ( + client, + photo_upload, + photo_delete, + HTTPValidationError, + BodyPhotoUploadAlbumsAlbumPhotosPost, + File, +) @app.on_message(~filters.scheduled & filters.command(["import"], prefixes=["", "/"])) @@ -114,13 +122,34 @@ async def cmd_import(app: PosterClient, msg: Message): if not path.isfile(filename): continue # upload filename - uploaded = await upload_pic(filename) - if uploaded[0] is False: - logWrite( - f"Could not upload '{filename}' from '{path.join(configGet('tmp', 'locations'), tmp_dir)}'. Duplicates: {str(uploaded[1])}", - debug=True, + async with aiofiles.open(filename, "rb") as f: + file_bytes = await f.read() + uploaded = await photo_upload( + album=configGet("album", "posting", "api"), + client=client, + multipart_data=BodyPhotoUploadAlbumsAlbumPhotosPost( + File(file_bytes, path.basename(filename), "image/jpeg") + ), + ignore_duplicates=configGet("allow_duplicates", "submission"), + caption="queue", + compress=False, + ) + if uploaded is None or isinstance(uploaded, HTTPValidationError): + await msg.reply_text( + locale( + "import_upload_error_other", + "message", + locale=msg.from_user.language_code, + ).format(path.basename(filename)), + disable_notification=True, ) - if len(uploaded[1]) > 0: + return + if not hasattr(uploaded, "id"): + if hasattr(uploaded, "duplicates"): + logWrite( + f"Could not upload '{filename}' from '{path.join(configGet('tmp', 'locations'), tmp_dir)}'. Duplicates: {str(uploaded.duplicates)}", + debug=True, + ) await msg.reply_text( locale( "import_upload_error_duplicate", @@ -140,7 +169,7 @@ async def cmd_import(app: PosterClient, msg: Message): ) else: logWrite( - f"Uploaded '{filename}' from '{path.join(configGet('tmp', 'locations'), tmp_dir)}' and got ID {uploaded[2]}", + f"Uploaded '{filename}' from '{path.join(configGet('tmp', 'locations'), tmp_dir)}' and got ID {uploaded.id}", debug=True, ) @@ -187,8 +216,8 @@ async def cmd_remove(app: PosterClient, msg: Message): locale("remove_abort", "message", locale=msg.from_user.language_code) ) return - response = await remove_pic(answer.text) - if response: + try: + response = await photo_delete(id=answer.text, client=client) logWrite( f"Removed '{answer.text}' by request of user {answer.from_user.id}" ) @@ -197,7 +226,7 @@ async def cmd_remove(app: PosterClient, msg: Message): "remove_success", "message", locale=msg.from_user.language_code ).format(answer.text) ) - else: + except: logWrite( f"Could not remove '{answer.text}' by request of user {answer.from_user.id}" ) diff --git a/poster.py b/poster.py index 0ab5c20..f528e9c 100644 --- a/poster.py +++ b/poster.py @@ -3,7 +3,7 @@ from os import getpid, path from sys import exit from time import time from traceback import format_exc -from modules.api_client import authorize +from modules.api_client import authorize, album_find, album_create from modules.cli import * from modules.http_client import http_session @@ -106,8 +106,8 @@ if configGet("post", "mode"): # if configGet("api_based", "mode"): # token = authorize() -# if len(get(f'{configGet("address", "posting", "api")}/albums?q={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"}).json()["results"]) == 0: -# post(f'{configGet("address", "posting", "api")}/albums?name={configGet("queue", "posting", "api", "albums")}&title={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"}) +# if len(get(f'{configGet("address", "posting", "api")}/albums?q={configGet("album", "posting", "api")}', headers={"Authorization": f"Bearer {token}"}).json()["results"]) == 0: +# post(f'{configGet("address", "posting", "api")}/albums?name={configGet("album", "posting", "api")}&title={configGet("album", "posting", "api")}', headers={"Authorization": f"Bearer {token}"}) # await idle() @@ -209,31 +209,24 @@ async def main(): scheduler.start() try: - - token = await authorize() - if ( len( ( - await ( - await http_session.get( - f'{configGet("address", "posting", "api")}/albums?q={configGet("queue", "posting", "api", "albums")}', - headers={"Authorization": f"Bearer {token}"}, - ) - ).json() - )["results"] + await album_find( + client=client, q=configGet("album", "posting", "api") + ) + ).results ) == 0 ): logWrite("Media album does not exist on API server. Trying to create it...") try: - await http_session.post( - f'{configGet("address", "posting", "api")}/albums?name={configGet("queue", "posting", "api", "albums")}&title={configGet("queue", "posting", "api", "albums")}', - headers={"Authorization": f"Bearer {token}"}, - ) - logWrite( - "Created media album on API server."}" + await album_create( + client=client, + name=configGet("album", "posting", "api"), + title=configGet("album", "posting", "api"), ) + logWrite("Created media album on API server.") except Exception as exp: logWrite( f"Could not create media album on API server due to {exp}: {format_exc()}"