MrShandy Опубликовано 6 января Поделиться Опубликовано 6 января Не совсем понимаю как мне загружать файлы по REST API. Написал скрипт на python для загрузки, но как бы я не пробовал, ничего не получается. Если кто то делал, можете подсказать как именно отправляли файлы? Вот скрипт, может я в чем то ошибаюсь? Хотя мы с напарником многое перепробовали уже и пока никаких результатов. Спойлер import asyncio import aiohttp url = 'https://scrapmechanic.ru/api/downloads/files' token = 'API KEY' params = { 'category': 5, 'author': 1, 'title': 'archive.zip', 'description': '<p>This is an archive file.</p>', 'hidden': 1, } async def upload_file(data_files: dict): auth = aiohttp.BasicAuth(token, "") async with aiohttp.ClientSession(auth=auth) as session: print("Params:") print(params) print("Data:") print(data_files) async with session.post(url, params=params, data=data_files) as response: print("Headers:") print(response.request_info.headers) print("Response JSON:") print(await response.json()) print("Response text:") print(await response.text()) print("Response status:") print(response.status) print("Response content-type:") print(response.content_type) print("=" * 10) if response.ok: print('File uploaded successfully.') else: print('Failed to upload file. Error:', await response.text()) async def main(): file_content = open('archive.zip', 'rb') data_files = { "archive.zip": [file_content.read()], } params['files'] = [key for key in data_files.keys()] await upload_file(data_files) file_content.close() loop = asyncio.new_event_loop() loop.run_until_complete(main()) Вот часть кода API, ориентировался на него когда писал скрипт. Спойлер /** * POST /downloads/files * Upload a file * * @note For requests using an OAuth Access Token for a particular member, any parameters the user doesn't have permission to use are ignored (for example, locked will only be honoured if the authenticated user has permission to lock files). * @reqapiparam int category The ID number of the category the file should be created in * @reqapiparam int author The ID number of the member creating the file (0 for guest). Required for requests made using an API Key or the Client Credentials Grant Type. For requests using an OAuth Access Token for a particular member, that member will always be the author * @reqapiparam string title The file name * @reqapiparam string description The description as HTML (e.g. "<p>This is an file.</p>"). Will be sanatized for requests using an OAuth Access Token for a particular member; will be saved unaltered for requests made using an API Key or the Client Credentials Grant Type. * @apiparam string version The version number * @reqapiparam object files Files. Keys should be filename (e.g. 'file.txt') and values should be file content * @apiparam object screenshots Screenshots. Keys should be filename (e.g. 'screenshot1.png') and values should be file content. * @apiparam string prefix Prefix tag * @apiparam string tags Comma-separated list of tags (do not include prefix) * @apiparam datetime date The date/time that should be used for the file post date. If not provided, will use the current date/time. Ignored for requests using an OAuth Access Token for a particular member. * @apiparam string ip_address The IP address that should be stored for the file. If not provided, will use the IP address from the API request. Ignored for requests using an OAuth Access Token for a particular member. * @apiparam int locked 1/0 indicating if the file should be locked * @apiparam int hidden 0 = unhidden; 1 = hidden, pending moderator approval; -1 = hidden (as if hidden by a moderator) * @apiparam int pinned 1/0 indicating if the file should be featured * @apiparam int featured 1/0 indicating if the file should be featured * @apiparam bool anonymous If 1, the item will be posted anonymously. * @throws 1S303/7 NO_CATEGEORY The category ID does not exist * @throws 1S303/8 NO_AUTHOR The author ID does not exist * @throws 1S303/9 NO_TITLE No title was supplied * @throws 1S303/A NO_DESC No description was supplied * @throws 1S303/B NO_FILES No files were supplied * @throws 2S303/H NO_PERMISSION The authorized user does not have permission to create a file in that category * @throws 1S303/I BAD_FILE_EXT One of the files has a file type that is not allowed * @throws 1S303/J BAD_FILE_SIZE One of the files is too big * @throws 1S303/K BAD_SS One of the screenshots is not a valid image * @throws 1S303/L BAD_SS_SIZE One of the screenshots is too big by filesize * @throws 1S303/M BAD_SS_DIMS One of the screenshots is too big by dimensions * @throws 1S303/N NO_SS No screenshots are provided, but screenshots are required for the category * @return \IPS\downloads\File */ public function POSTindex() { /* Get category */ try { $category = \IPS\downloads\Category::load( \IPS\Request::i()->category ); } catch ( \OutOfRangeException $e ) { throw new \IPS\Api\Exception( 'NO_CATEGEORY', '1S303/7', 400 ); } /* Get author */ if ( $this->member ) { if ( !$category->can( 'add', $this->member ) ) { throw new \IPS\Api\Exception( 'NO_PERMISSION', '2S303/H', 403 ); } $author = $this->member; } else { if ( \IPS\Request::i()->author ) { $author = \IPS\Member::load( \IPS\Request::i()->author ); if ( !$author->member_id ) { throw new \IPS\Api\Exception( 'NO_AUTHOR', '1S303/8', 400 ); } } else { if ( \IPS\Request::i()->author === 0 ) { $author = new \IPS\Member; $author->name = \IPS\Request::i()->author_name; } else { throw new \IPS\Api\Exception( 'NO_AUTHOR', '1S303/8', 400 ); } } } /* Check we have a title and a description */ if ( !\IPS\Request::i()->title ) { throw new \IPS\Api\Exception( 'NO_TITLE', '1S303/9', 400 ); } if ( !\IPS\Request::i()->description ) { throw new \IPS\Api\Exception( 'NO_DESC', '1S303/A', 400 ); } /* Validate files */ if ( !isset( \IPS\Request::i()->files ) or !\is_array( \IPS\Request::i()->files ) or empty( \IPS\Request::i()->files ) ) { throw new \IPS\Api\Exception( 'NO_FILES', '1L296/B', 400 ); } if ( $this->member ) { $this->_validateFilesForMember( $category ); } /* Create file record */ $file = $this->_create( $category, $author ); /* Save records */ foreach ( array_keys( \IPS\Request::i()->files ) as $name ) { $fileObject = \IPS\File::create( 'downloads_Files', $name, $_POST['files'][ $name ] ); \IPS\Db::i()->insert( 'downloads_files_records', array( 'record_file_id' => $file->id, 'record_type' => 'upload', 'record_location' => (string) $fileObject, 'record_realname' => $fileObject->originalFilename, 'record_size' => $fileObject->filesize(), 'record_time' => time(), ) ); } if ( $category->bitoptions['allowss'] and isset( \IPS\Request::i()->screenshots ) ) { $primary = 1; foreach ( array_keys( \IPS\Request::i()->screenshots ) as $name ) { $fileObject = \IPS\File::create( 'downloads_Screenshots', $name, $_POST['screenshots'][ $name ] ); \IPS\Db::i()->insert( 'downloads_files_records', array( 'record_file_id' => $file->id, 'record_type' => 'ssupload', 'record_location' => (string) $fileObject, 'record_thumb' => (string) $fileObject->thumbnail( 'downloads_Screenshots' ), 'record_realname' => $fileObject->originalFilename, 'record_size' => \strlen( $fileObject->contents() ), 'record_time' => time(), 'record_no_watermark' => NULL, 'record_default' => $primary ) ); $primary = 0; } } /* Recaluclate properties */ $file = $this->_recalculate( $file ); /* Return */ $file->save(); return new \IPS\Api\Response( 201, $file->apiOutput( $this->member ) ); } Ошибку постоянно получаю эту: Failed to upload file. Error: { "errorCode": "1L296\/B", "errorMessage": "NO_FILES" } Если вывести \IPS\Request::i()->files вместо 'NO_FILES' выводится последний элемент массива files. Пока не понял логику этого ryancoolround 1 Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
ryancoolround Опубликовано 6 января Поделиться Опубликовано 6 января А если использовать форму multipart/form-data? Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
MrShandy Опубликовано 6 января Автор Поделиться Опубликовано 6 января Только что, ryancoolround сказал: А если использовать форму multipart/form-data? Ну заголовками управляет aiohttp, моё дело рулить данными Вот какие он выставляет Headers: <CIMultiDictProxy('Host': 'scrapmechanic.ru', 'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'User-Agent': 'Python/3.10 aiohttp/3.8.4', 'Authorization': 'Basic токен', 'Content-Length': '1485', 'Content-Type': 'application/x-www-form-urlencoded')> Насильно выставить multipart/form-data не дало результата Headers: <CIMultiDictProxy('Host': 'scrapmechanic.ru', 'Accept': 'application/json', 'Content-Type': 'multipart/form-data', 'Accept-Encoding': 'gzip, deflate', 'User-Agent': 'Python/3.10 aiohttp/3.8.4', 'Authorization': 'Basic токен', 'Content-Length': '1485')> Response JSON: {'errorCode': '1L296/B', 'errorMessage': 'NO_FILES'} Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
Desti Опубликовано 6 января Поделиться Опубликовано 6 января (изменено) print(params) чего показывает в upload_file? там же просто всё - isset( \IPS\Request::i()->files ) or !\is_array( \IPS\Request::i()->files ) or empty( \IPS\Request::i()->files ) ) Изменено 6 января пользователем Desti Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
MrShandy Опубликовано 6 января Автор Поделиться Опубликовано 6 января Только что, Desti сказал: print(params) чего показывает в upload_file? Params: {'category': 5, 'author': 1, 'title': 'archive.zip', 'description': '<p>This is an archive file.</p>', 'hidden': 1, 'files': ['archive.zip']} Data: {'archive.zip': [b'PK\x03\x04\x14\x00\x00\x00\x08\x00\x1d`\xf3V2\xc6\np\xbd\x01\x00\x00*\x03\x00\x00\x07\x00\x00\x00main.pymRM\x8f\xd30\x10\xbdG\xca\x7f\x18\x89\x83\x1b)\x9b\xb44\x9be#\xf5\xb0\x12 q\xe0\x06\x12\x12\xe2\xe0\xd8\x93\xc6\xda\xc46\xb6\x03t\x11\xff\x1d\xdbqKw\x85\xe5\x83g<\xef\xbd\xf9\x12\xb3V\xc6AO-\xb6M\x9e\xe5Yr\x18\xfc\xbe\xa0u6\xb8\x163\xc1\x01\xc8\xe8\x9c\xb6]][f\xa8\x9e\x91\x8dT\nV\x99\xa5\xa6Z\xd4\\\xfd\x94\x93\xa2\xdc\xd6\x83\x98\xd0\x92<s\xea\x11e\xc0\xe1\xb6i\x18\xc5v\x8b\x03\xbd\xc3\xfb\xbb}\xdb\xf48 \xbf\x7f\xd3\xb3]?\xec}\xac\xa6\x86\xce\xd6\x07\xff\xce3\xf0\x870\xea\xf0\xa8\xcc\x89t\xb0{\xbd/\x01^\xc1\x87\xb7 \x97\xb9G\x03j\x007"\x9cc\xa2\x11T\xc1\x8ej\x998\xf4\xfe\xcf\xa0\xff\xe5 d"\xa4\x8b\x1b\x95\xf1t\xcdm\xfb\x7f\xba\x19\xa3\x15\x91B\x1e/\xac\x89\xc0\t7\xa1\xc7\x13j\xd8(~`\xf5$4\x89LQZ\xd2\xf9\x1c\xc9\xd1wHh\'\x94\x0c\xf1\x9fFa\xc1_*!A#\xa2\xba\x02_!\x12\xc7\xda\xc4\xee\xdc\x8fp\x9e\tw\xa04\xca\xcd\x8bd\x88\xe9IqaeJ:\x94ne\xf8\x93g\xfe\x8eH9\x9a\xebF\x7f\xb9y\x88\x9d\x11O4%\xbc\xaeB\xd5\xb7\rJ\xa68n\xe2 \xabd\x14E\xc5q}\x95\x89\xe2\xb3Es\xf3p\xf4R\xa1\xdc\x8f\xa7`G\xb3\xdeU[\x12u\xf3\xcc\xa0\xd5JZ\x84\xc3e\xb5*\xad\xac\xdb\xf8\xe5*c\xbe\xf6\xb0n\xc1\xd7T\xfc\xb7\x128u4y\x0b\xbf\x99\x03\x9cY*\xf5\xd8\xad\xea\xda\x08\xe96\xe4}(x\xd1a\x03\xfd\xd4\xed\xc2\x18Z;,\xd3t\xaa\x88\x87\xe2d\xf1\x05\x80z\x04\x07\xa7\x12j\x9d\t\xbc3F\x99\x8e\x94\xff\x94\x1c\xferE\xa8\xe0/PK\x01\x02\x1f\x00\x14\x00\x00\x00\x08\x00\x1d`\xf3V2\xc6\np\xbd\x01\x00\x00*\x03\x00\x00\x07\x00$\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00main.py\n\x00 \x00\x00\x00\x00\x00\x01\x00\x18\x00\x9d\x82\xe6\x88\x1f\xba\xd9\x01\x9d\x82\xe6\x88\x1f\xba\xd9\x01\x07Rx\xa3\x1e\xba\xd9\x01PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00Y\x00\x00\x00\xe2\x01\x00\x00\x00\x00']} Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
MrShandy Опубликовано 6 января Автор Поделиться Опубликовано 6 января 11 минут назад, Desti сказал: там же просто всё - isset( \IPS\Request::i()->files ) or !\is_array( \IPS\Request::i()->files ) or empty( \IPS\Request::i()->files ) ) Ну я видел это, передаю список, но результат ошибка. Как мне запрос то делать надо? Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
Desti Опубликовано 6 января Поделиться Опубликовано 6 января У тебя в запрос улетает ...api/downloads/files?category=1&author=1&title=archive.zip&description=%3Cp%3EThis+is+an+archive+file.%3C/p%3E&hidden=1&files=archive.zip Т.е. files должен быть массивом, а уходит строка. Как это починить - не знаю, питон не мое. Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
MrShandy Опубликовано 6 января Автор Поделиться Опубликовано 6 января 1 минуту назад, Desti сказал: У тебя в запрос улетает ...api/downloads/files?category=1&author=1&title=archive.zip&description=%3Cp%3EThis+is+an+archive+file.%3C/p%3E&hidden=1&files=archive.zip Т.е. files должен быть массивом, а уходит строка. Как это починить - не знаю, питон не мое. Ну допустим так, но надо как то туда передать список? Если это вообще возможно передать в параметрах. Мне бы просто понять как отправлять, хотя бы через postman, а там уже разберусь как на питоне написать API у них убогий какой то, можно было в разы лучше сделать Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
Desti Опубликовано 6 января Поделиться Опубликовано 6 января files - Keys should be filename (e.g. 'file.txt') and values should be file content Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
MrShandy Опубликовано 6 января Автор Поделиться Опубликовано 6 января Только что, Desti сказал: files - Keys should be filename (e.g. 'file.txt') and values should be file content Только при этом ниже имеем: foreach ( array_keys( Request::i()->files ) as $name ) { $fileObject = \IPS\File::create( 'downloads_Files', $name, $_POST['files'][ $name ] ); Db::i()->insert( 'downloads_files_records', array( 'record_file_id' => $file->id, 'record_type' => 'upload', 'record_location' => (string) $fileObject, 'record_realname' => $fileObject->originalFilename, 'record_size' => $fileObject->filesize(), 'record_time' => time(), ) ); } То есть файлы должны быть и в $_POST? Я не знаю php, но $_POST это же data, а не параметры, правильно? Ну и опять же, в параметры невозможно передать dict. Это как минимум не логично Traceback (most recent call last): File "C:\Users\MrSha\Documents\Projects\ICFileUploader\main.py", line 57, in <module> loop.run_until_complete(main()) File "C:\Users\MrSha\AppData\Local\Programs\Python\Python310\lib\asyncio\base_events.py", line 649, in run_until_complete return future.result() File "C:\Users\MrSha\Documents\Projects\ICFileUploader\main.py", line 52, in main await upload_file(data_files) File "C:\Users\MrSha\Documents\Projects\ICFileUploader\main.py", line 27, in upload_file async with session.post(url, params=params, data=data_files) as response: File "C:\Users\MrSha\.virtualenvs\ICFileUploader-DZz49JbJ\lib\site-packages\aiohttp\client.py", line 1141, in __aenter__ self._resp = await self._coro File "C:\Users\MrSha\.virtualenvs\ICFileUploader-DZz49JbJ\lib\site-packages\aiohttp\client.py", line 508, in _request req = self._request_class( File "C:\Users\MrSha\.virtualenvs\ICFileUploader-DZz49JbJ\lib\site-packages\aiohttp\client_reqrep.py", line 283, in __init__ url2 = url.with_query(params) File "C:\Users\MrSha\.virtualenvs\ICFileUploader-DZz49JbJ\lib\site-packages\yarl\_url.py", line 1007, in with_query new_query = self._get_str_query(*args, **kwargs) or "" File "C:\Users\MrSha\.virtualenvs\ICFileUploader-DZz49JbJ\lib\site-packages\yarl\_url.py", line 968, in _get_str_query query = "&".join(self._query_seq_pairs(quoter, query.items())) File "C:\Users\MrSha\.virtualenvs\ICFileUploader-DZz49JbJ\lib\site-packages\yarl\_url.py", line 931, in _query_seq_pairs yield quoter(key) + "=" + quoter(cls._query_var(val)) File "C:\Users\MrSha\.virtualenvs\ICFileUploader-DZz49JbJ\lib\site-packages\yarl\_url.py", line 946, in _query_var raise TypeError( TypeError: Invalid variable type: value should be str, int or float, got {'archive.zip': [b'PK\x03\x04\x14\x00\x00\x00\x08\x00\x1d`\xf3V2\xc6\np\xbd\x01\x00\x00*\x03\x00\x00\x07\x00\x00\x00main.pymRM\x8f\xd30\x10\xbdG\xca\x7f\x18\x89\x83\x1b)\x9b\xb44\x9be#\xf5\xb0\x12 q\xe0\x06\x12\x12\xe2\xe0\xd8\x93\xc6\xda\xc46\xb6\x03t\x11\xff\x1d\xdbqKw\x85\xe5\x83g<\xef\xbd\xf9\x12\xb3V\xc6AO-\xb6M\x9e\xe5Yr\x18\xfc\xbe\xa0u6\xb8\x163\xc1\x01\xc8\xe8\x9c\xb6]][f\xa8\x9e\x91\x8dT\nV\x99\xa5\xa6Z\xd4\\\xfd\x94\x93\xa2\xdc\xd6\x83\x98\xd0\x92<s\xea\x11e\xc0\xe1\xb6i\x18\xc5v\x8b\x03\xbd\xc3\xfb\xbb}\xdb\xf48 \xbf\x7f\xd3\xb3]?\xec}\xac\xa6\x86\xce\xd6\x07\xff\xce3\xf0\x870\xea\xf0\xa8\xcc\x89t\xb0{\xbd/\x01^\xc1\x87\xb7 \x97\xb9G\x03j\x007"\x9cc\xa2\x11T\xc1\x8ej\x998\xf4\xfe\xcf\xa0\xff\xe5 d"\xa4\x8b\x1b\x95\xf1t\xcdm\xfb\x7f\xba\x19\xa3\x15\x91B\x1e/\xac\x89\xc0\t7\xa1\xc7\x13j\xd8(~`\xf5$4\x89LQZ\xd2\xf9\x1c\xc9\xd1wHh\'\x94\x0c\xf1\x9fFa\xc1_*!A#\xa2\xba\x02_!\x12\xc7\xda\xc4\xee\xdc\x8fp\x9e\tw\xa04\xca\xcd\x8bd\x88\xe9IqaeJ:\x94ne\xf8\x93g\xfe\x8eH9\x9a\xebF\x7f\xb9y\x88\x9d\x11O4%\xbc\xaeB\xd5\xb7\rJ\xa68n\xe2 \xabd\x14E\xc5q}\x95\x89\xe2\xb3Es\xf3p\xf4R\xa1\xdc\x8f\xa7`G\xb3\xdeU[\x12u\xf3\xcc\xa0\xd5JZ\x84\xc3e\xb5*\xad\xac\xdb\xf8\xe5*c\xbe\xf6\xb0n\xc1\xd7T\xfc\xb7\x128u4y\x0b\xbf\x99\x03\x9cY*\xf5\xd8\xad\xea\xda\x08\xe96\xe4}(x\xd1a\x03\xfd\xd4\xed\xc2\x18Z;,\xd3t\xaa\x88\x87\xe2d\xf1\x05\x80z\x04\x07\xa7\x12j\x9d\t\xbc3F\x99\x8e\x94\xff\x94\x1c\xferE\xa8\xe0/PK\x01\x02\x1f\x00\x14\x00\x00\x00\x08\x00\x1d`\xf3V2\xc6\np\xbd\x01\x00\x00*\x03\x00\x00\x07\x00$\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00main.py\n\x00 \x00\x00\x00\x00\x00\x01\x00\x18\x00\x9d\x82\xe6\x88\x1f\xba\xd9\x01\x9d\x82\xe6\x88\x1f\xba\xd9\x01\x07Rx\xa3\x1e\xba\xd9\x01PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00Y\x00\x00\x00\xe2\x01\x00\x00\x00\x00']} of type <class 'dict'> Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
Desti Опубликовано 6 января Поделиться Опубликовано 6 января Осталось только превратить содержимое файла в строку. Ну и rtfm, как обычно For PUT and POST requests, all parameters should be sent Form URL Encoded in the body. MrShandy 1 Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
MrShandy Опубликовано 6 января Автор Поделиться Опубликовано 6 января 5 минут назад, Desti сказал: Осталось только превратить содержимое файла в строку. Окей, это работает Сейчас проверю с отправкой файла в байтах, иначе как условный архив туда закинуть 3 минуты назад, MrShandy сказал: Сейчас проверю с отправкой файла в байтах, иначе как условный архив туда закинуть @Desti, проверил, текст отправляется без проблем. Байты нет, ошибка нет файлов Текст: Спойлер Params: {'category': 5, 'author': 1, 'title': 'archive.zip', 'description': '<p>This is an archive file.</p>', 'hidden': 1} Data: {'files[test.txt]': 'text for test'} Headers: <CIMultiDictProxy('Host': 'scrapmechanic.ru', 'Accept': 'application/json', 'Accept-Encoding': 'gzip, deflate', 'User-Agent': 'Python/3.10 aiohttp/3.8.4', 'Authorization': 'Basic токен', 'Content-Length': '33', 'Content-Type': 'application/x-www-form-urlencoded')> Response JSON: {'id': 106, 'title': 'archive.zip', 'category': {'id': 5, 'name': 'Постройки', 'url': 'https://scrapmechanic.ru/files/category/5-%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B8/', 'class': 'IPS\\downloads\\Category', 'parentId': 0, 'permissions': {'perm_id': 110, 'perm_view': '*', 'perm_2': '*', 'perm_3': '8,4,7,6,3', 'perm_4': '*', 'perm_5': '8,4,7,6,3', 'perm_6': '8,4,7,6,3', 'perm_7': None}, 'club': 0}, 'author': {'id': 1, 'name': 'MrShandy', 'title': None, 'timeZone': 'Europe/Moscow', 'formattedName': '<strong class="site-administration">MrShandy</strong>', 'primaryGroup': {'id': 4, 'name': 'Администраторы', 'formattedName': '<strong class="site-administration">Администраторы</strong>'}, 'secondaryGroups': [], 'email': '[email protected]', 'joined': '2020-01-01T09:55:00Z', 'registrationIpAddress': '5.187.74.254', 'warningPoints': 0, 'reputationPoints': 20, 'photoUrl': 'https://b.cdn.scrapmechanic.ru/uploads/photos/monthly_2022_08/393072407_redditperson.png.cd76d868c8efde81a749aafb7fe4adbf.png', 'photoUrlIsDefault': False, 'coverPhotoUrl': 'https://b.cdn.scrapmechanic.ru/uploads/photos/monthly_2023_03/p1_3065335_a11db4bd.jpg.ee7c3210a58bc29b1ffe7c88ac7531ee.jpg', 'profileUrl': 'https://scrapmechanic.ru/profile/1-mrshandy/', 'validating': False, 'posts': 203, 'lastActivity': '2024-01-06T16:03:40Z', 'lastVisit': '2024-01-06T13:10:53Z', 'lastPost': '2024-01-06T16:09:18Z', 'birthday': '10/10', 'profileViews': 2188, 'customFields': {'1': {'name': 'Личная информация', 'fields': {'1': {'name': 'Обо мне', 'value': '<p>\n\tСоздатель этого сайта и сервера\xa0<a href="https://discord.gg/TTczVRpKdB" rel="external nofollow">Try Gaming</a>\xa0в discord\n</p>\n\n<p>\n\tПишу ботов на python и иногда стримлю на ютуб <span><span class="ipsEmoji">🙂</span></span>\n</p>\n'}, '2': {'name': 'Аккаунт Discord', 'value': '@mrshandy'}}}}, 'rank': {'id': 7, 'name': 'Техник', 'icon': 'https://a.cdn.scrapmechanic.ru/uploads/monthly_2023_12/8_Regular.svg.8fa0b9f78ee4a5928f9e6007432d4e17.svg', 'points': 1300}, 'achievements_points': 1451, 'allowAdminEmails': False, 'completed': True}, 'date': '2024-01-06T16:09:18Z', 'updated': '2024-01-06T16:09:18Z', 'description': '<p>This is an archive file.</p>', 'version': None, 'changelog': None, 'screenshots': [], 'screenshotsThumbnails': [], 'primaryScreenshot': None, 'primaryScreenshotThumb': None, 'downloads': None, 'comments': 0, 'reviews': 0, 'views': 0, 'prefix': None, 'tags': [], 'locked': False, 'hidden': True, 'pinned': False, 'featured': False, 'url': 'https://scrapmechanic.ru/files/file/106-archivezip/', 'topic': None, 'isPaid': False, 'isPurchasable': False, 'prices': None, 'canDownload': None, 'canBuy': None, 'canReview': None, 'rating': 0, 'purchases': None, 'renewalTerm': None, 'hasPendingVersion': False} Response text: { "id": 106, "title": "archive.zip", "category": { "id": 5, "name": "\u041f\u043e\u0441\u0442\u0440\u043e\u0439\u043a\u0438", "url": "https:\/\/scrapmechanic.ru\/files\/category\/5-%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B8\/", "class": "IPS\\downloads\\Category", "parentId": 0, "permissions": { "perm_id": 110, "perm_view": "*", "perm_2": "*", "perm_3": "8,4,7,6,3", "perm_4": "*", "perm_5": "8,4,7,6,3", "perm_6": "8,4,7,6,3", "perm_7": null }, "club": 0 }, "author": { "id": 1, "name": "MrShandy", "title": null, "timeZone": "Europe\/Moscow", "formattedName": "<strong class=\"site-administration\">MrShandy<\/strong>", "primaryGroup": { "id": 4, "name": "\u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u044b", "formattedName": "<strong class=\"site-administration\">\u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u044b<\/strong>" }, "secondaryGroups": [], "email": "[email protected]", "joined": "2020-01-01T09:55:00Z", "registrationIpAddress": "1.1.1.1", "warningPoints": 0, "reputationPoints": 20, "photoUrl": "https:\/\/b.cdn.scrapmechanic.ru\/uploads\/photos\/monthly_2022_08\/393072407_redditperson.png.cd76d868c8efde81a749aafb7fe4adbf.png", "photoUrlIsDefault": false, "coverPhotoUrl": "https:\/\/b.cdn.scrapmechanic.ru\/uploads\/photos\/monthly_2023_03\/p1_3065335_a11db4bd.jpg.ee7c3210a58bc29b1ffe7c88ac7531ee.jpg", "profileUrl": "https:\/\/scrapmechanic.ru\/profile\/1-mrshandy\/", "validating": false, "posts": 203, "lastActivity": "2024-01-06T16:03:40Z", "lastVisit": "2024-01-06T13:10:53Z", "lastPost": "2024-01-06T16:09:18Z", "birthday": "10\/10", "profileViews": 2188, "customFields": { "1": { "name": "\u041b\u0438\u0447\u043d\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f", "fields": { "1": { "name": "\u041e\u0431\u043e \u043c\u043d\u0435", "value": "<p>\n\t\u0421\u043e\u0437\u0434\u0430\u0442\u0435\u043b\u044c \u044d\u0442\u043e\u0433\u043e \u0441\u0430\u0439\u0442\u0430 \u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430\u00a0<a href=\"https:\/\/discord.gg\/TTczVRpKdB\" rel=\"external nofollow\">Try Gaming<\/a>\u00a0\u0432 discord\n<\/p>\n\n<p>\n\t\u041f\u0438\u0448\u0443 \u0431\u043e\u0442\u043e\u0432 \u043d\u0430 python \u0438 \u0438\u043d\u043e\u0433\u0434\u0430 \u0441\u0442\u0440\u0438\u043c\u043b\u044e \u043d\u0430 \u044e\u0442\u0443\u0431 <span><span class=\"ipsEmoji\">\ud83d\ude42<\/span><\/span>\n<\/p>\n" }, "2": { "name": "\u0410\u043a\u043a\u0430\u0443\u043d\u0442 Discord", "value": "@mrshandy" } } } }, "rank": { "id": 7, "name": "\u0422\u0435\u0445\u043d\u0438\u043a", "icon": "https:\/\/a.cdn.scrapmechanic.ru\/uploads\/monthly_2023_12\/8_Regular.svg.8fa0b9f78ee4a5928f9e6007432d4e17.svg", "points": 1300 }, "achievements_points": 1451, "allowAdminEmails": false, "completed": true }, "date": "2024-01-06T16:09:18Z", "updated": "2024-01-06T16:09:18Z", "description": "<p>This is an archive file.<\/p>", "version": null, "changelog": null, "screenshots": [], "screenshotsThumbnails": [], "primaryScreenshot": null, "primaryScreenshotThumb": null, "downloads": null, "comments": 0, "reviews": 0, "views": 0, "prefix": null, "tags": [], "locked": false, "hidden": true, "pinned": false, "featured": false, "url": "https:\/\/scrapmechanic.ru\/files\/file\/106-archivezip\/", "topic": null, "isPaid": false, "isPurchasable": false, "prices": null, "canDownload": null, "canBuy": null, "canReview": null, "rating": 0, "purchases": null, "renewalTerm": null, "hasPendingVersion": false } Response status: 201 Response content-type: application/json ========== File uploaded successfully. Для байтов: Спойлер Params: {'category': 5, 'author': 1, 'title': 'archive.zip', 'description': '<p>This is an archive file.</p>', 'hidden': 1} Data: {'files[archive.zip]': b'PK\x03\x04\x14\x00\x00\x00\x08\x00\x1d`\xf3V2\xc6\np\xbd\x01\x00\x00*\x03\x00\x00\x07\x00\x00\x00main.pymRM\x8f\xd30\x10\xbdG\xca\x7f\x18\x89\x83\x1b)\x9b\xb44\x9be#\xf5\xb0\x12 q\xe0\x06\x12\x12\xe2\xe0\xd8\x93\xc6\xda\xc46\xb6\x03t\x11\xff\x1d\xdbqKw\x85\xe5\x83g<\xef\xbd\xf9\x12\xb3V\xc6AO-\xb6M\x9e\xe5Yr\x18\xfc\xbe\xa0u6\xb8\x163\xc1\x01\xc8\xe8\x9c\xb6]][f\xa8\x9e\x91\x8dT\nV\x99\xa5\xa6Z\xd4\\\xfd\x94\x93\xa2\xdc\xd6\x83\x98\xd0\x92<s\xea\x11e\xc0\xe1\xb6i\x18\xc5v\x8b\x03\xbd\xc3\xfb\xbb}\xdb\xf48 \xbf\x7f\xd3\xb3]?\xec}\xac\xa6\x86\xce\xd6\x07\xff\xce3\xf0\x870\xea\xf0\xa8\xcc\x89t\xb0{\xbd/\x01^\xc1\x87\xb7 \x97\xb9G\x03j\x007"\x9cc\xa2\x11T\xc1\x8ej\x998\xf4\xfe\xcf\xa0\xff\xe5 d"\xa4\x8b\x1b\x95\xf1t\xcdm\xfb\x7f\xba\x19\xa3\x15\x91B\x1e/\xac\x89\xc0\t7\xa1\xc7\x13j\xd8(~`\xf5$4\x89LQZ\xd2\xf9\x1c\xc9\xd1wHh\'\x94\x0c\xf1\x9fFa\xc1_*!A#\xa2\xba\x02_!\x12\xc7\xda\xc4\xee\xdc\x8fp\x9e\tw\xa04\xca\xcd\x8bd\x88\xe9IqaeJ:\x94ne\xf8\x93g\xfe\x8eH9\x9a\xebF\x7f\xb9y\x88\x9d\x11O4%\xbc\xaeB\xd5\xb7\rJ\xa68n\xe2 \xabd\x14E\xc5q}\x95\x89\xe2\xb3Es\xf3p\xf4R\xa1\xdc\x8f\xa7`G\xb3\xdeU[\x12u\xf3\xcc\xa0\xd5JZ\x84\xc3e\xb5*\xad\xac\xdb\xf8\xe5*c\xbe\xf6\xb0n\xc1\xd7T\xfc\xb7\x128u4y\x0b\xbf\x99\x03\x9cY*\xf5\xd8\xad\xea\xda\x08\xe96\xe4}(x\xd1a\x03\xfd\xd4\xed\xc2\x18Z;,\xd3t\xaa\x88\x87\xe2d\xf1\x05\x80z\x04\x07\xa7\x12j\x9d\t\xbc3F\x99\x8e\x94\xff\x94\x1c\xferE\xa8\xe0/PK\x01\x02\x1f\x00\x14\x00\x00\x00\x08\x00\x1d`\xf3V2\xc6\np\xbd\x01\x00\x00*\x03\x00\x00\x07\x00$\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00main.py\n\x00 \x00\x00\x00\x00\x00\x01\x00\x18\x00\x9d\x82\xe6\x88\x1f\xba\xd9\x01\x9d\x82\xe6\x88\x1f\xba\xd9\x01\x07Rx\xa3\x1e\xba\xd9\x01PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00Y\x00\x00\x00\xe2\x01\x00\x00\x00\x00'} Headers: <CIMultiDictProxy('Host': 'scrapmechanic.ru', 'Accept': 'application/json', 'Accept-Encoding': 'gzip, deflate', 'User-Agent': 'Python/3.10 aiohttp/3.8.4', 'Authorization': 'Basic токен', 'Content-Length': '826', 'Content-Type': 'multipart/form-data; boundary=33af861834ce49e583ecfc15980f1840')> Response JSON: {'errorCode': '1L296/B', 'errorMessage': 'NO_FILES'} Response text: { "errorCode": "1L296\/B", "errorMessage": "NO_FILES" } Response status: 400 Response content-type: application/json ========== Failed to upload file. Error: { "errorCode": "1L296\/B", "errorMessage": "NO_FILES" } Забавно на самом деле Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
Desti Опубликовано 6 января Поделиться Опубликовано 6 января {'files[archive.zip]': b'PK\x03.... это не строка Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
MrShandy Опубликовано 6 января Автор Поделиться Опубликовано 6 января Только что, Desti сказал: {'files[archive.zip]': b'PK\x03.... это не строка Ну а как я должен отправить не текстовую информацию?) Не, можно впринципе сделать ход конем и отправлять base64, а на сервере это обратно. Вопрос в адекватности таких действий Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
MrShandy Опубликовано 6 января Автор Поделиться Опубликовано 6 января Можно закрывать тему, решил 🙂 Просто заменил строку $fileObject = \IPS\File::create( 'downloads_Files', $name, $_POST['files'][ $name ] ); на $fileObject = \IPS\File::create( 'downloads_Files', $name, base64_decode($_POST['files'][ $name ]) ); На стороне клиента (в моём случае приложения на Python) так: "files[archive.zip]": base64.b64encode(file_content.read()).decode('utf-8') Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
Desti Опубликовано 6 января Поделиться Опубликовано 6 января Ну как затычка временная - пойдет, но проблема никуда не делась. Кстати, на их форуме есть обсуждение подобной проблемы, у человека тоже не получилось загрузить бинарный поток. Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
MrShandy Опубликовано 6 января Автор Поделиться Опубликовано 6 января 1 минуту назад, Desti сказал: Ну как затычка временная - пойдет, но проблема никуда не делась. Кстати, на их форуме есть обсуждение подобной проблемы, у человека тоже не получилось загрузить бинарный поток. Какое API такое и решение) Ну если они не смогли даже нормально задокументировать всё, как я должен по другому действовать? Вариант рабочий, проблем не должен создать. Если найду решение лучше - обязательно напишу здесь, но пока будет так Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
Desti Опубликовано 6 января Поделиться Опубликовано 6 января Целиком код покажи, может кому пригодится. Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
MrShandy Опубликовано 6 января Автор Поделиться Опубликовано 6 января Да, без проблем import asyncio import base64 import aiohttp url = "https://example.com/api/downloads/files" token = "токен" params = { "category": 5, "author": 1, "title": "archive.zip", "description": "<p>This is an archive file.</p>", "hidden": 1, } headers = {"Accept": "application/json"} async def upload_file(data_files: dict): auth = aiohttp.BasicAuth(token, "") async with aiohttp.ClientSession(auth=auth, headers=headers) as session: print("Params:", params) print("Data:", data_files) async with session.post(url, params=params, data=data_files) as response: print("Headers:", response.request_info.headers) print("Response JSON:", await response.json()) print("Response text:", await response.text()) print("Response status:", response.status) print("Response content-type:", response.content_type) print("=" * 10) if response.ok: print("File uploaded successfully.") else: print("Failed to upload file. Error:", await response.text()) async def main(): with open("archive.zip", "rb") as file_content: data_files = { "files[archive.zip]": base64.b64encode(file_content.read()).decode("utf-8") } await upload_file(data_files) loop = asyncio.new_event_loop() loop.run_until_complete(main()) Думаю кому будет надо разберется где что менять Desti 1 Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
001 Опубликовано 6 января Поделиться Опубликовано 6 января Если API поддерживает стандартный метод multipart/form-data, рекомендуется использовать его для более эффективной передачи файлов. Ниже представлен пример использования aiohttp.FormData для достижения этой цели: import asyncio import aiohttp url = "https://example.com/api/downloads/files" token = "токен" params = { "category": 5, "author": 1, "title": "archive.zip", "description": "<p>This is an archive file.</p>", "hidden": 1, } headers = {"Accept": "application/json"} async def upload_file(data_files: dict): auth = aiohttp.BasicAuth(token, "") async with aiohttp.ClientSession(auth=auth, headers=headers) as session: form_data = aiohttp.FormData() form_data.add_fields(**params) for file_name, file_content in data_files.items(): form_data.add_field('files', file_content, filename=file_name) async with session.post(url, data=form_data) as response: print("Headers:", response.request_info.headers) print("Response JSON:", await response.json()) print("Response text:", await response.text()) print("Response status:", response.status) print("Response content-type:", response.content_type) print("=" * 10) if response.ok: print("File uploaded successfully.") else: print("Failed to upload file. Error:", await response.text()) async def main(): with open("archive.zip", "rb") as file_content: data_files = { "archive.zip": file_content } await upload_file(data_files) loop = asyncio.new_event_loop() loop.run_until_complete(main()) В этом примере файл добавляется в форму с использованием add_field, и aiohttp самостоятельно управляет кодированием multipart/form-data. Ваши параметры и заголовки добавляются также, как и в предыдущем варианте. Этот метод более эффективен для передачи больших файлов MrShandy 1 Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
Desti Опубликовано 6 января Поделиться Опубликовано 6 января что-то ругается - TypeError: FormData.add_fields() got an unexpected keyword argument 'category' Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
001 Опубликовано 6 января Поделиться Опубликовано 6 января Если так import asyncio import aiohttp url = "https://example.com/api/downloads/files" token = "токен" params = { "category": 5, "author": 1, "title": "archive.zip", "description": "<p>This is an archive file.</p>", "hidden": 1, } headers = {"Accept": "application/json"} async def upload_file(data_files: dict): auth = aiohttp.BasicAuth(token, "") async with aiohttp.ClientSession(auth=auth, headers=headers) as session: form_data = aiohttp.FormData() for key, value in params.items(): form_data.add_field(key, str(value)) for file_name, file_content in data_files.items(): form_data.add_field('files', file_content, filename=file_name) async with session.post(url, data=form_data) as response: print("Headers:", response.request_info.headers) print("Response JSON:", await response.json()) print("Response text:", await response.text()) print("Response status:", response.status) print("Response content-type:", response.content_type) print("=" * 10) if response.ok: print("File uploaded successfully.") else: print("Failed to upload file. Error:", await response.text()) async def main(): with open("archive.zip", "rb") as file_content: data_files = { "archive.zip": file_content } await upload_file(data_files) loop = asyncio.new_event_loop() loop.run_until_complete(main()) Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
Desti Опубликовано 6 января Поделиться Опубликовано 6 января (изменено) тоже не айс File "C:\Python312\Lib\site-packages\aiohttp\payload.py", line 377, in size return os.fstat(self._value.fileno()).st_size - self._value.tell() ^^^^^^^^^^^^^^^^^^^^ ValueError: I/O operation on closed file Изменено 6 января пользователем Desti Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
001 Опубликовано 6 января Поделиться Опубликовано 6 января import asyncio import aiohttp url = "https://example.com/api/downloads/files" token = "токен" params = { "category": 5, "author": 1, "title": "archive.zip", "description": "<p>This is an archive file.</p>", "hidden": 1, } headers = {"Accept": "application/json"} async def upload_file(file_path: str): auth = aiohttp.BasicAuth(token, "") async with aiohttp.ClientSession(auth=auth, headers=headers) as session: with open(file_path, "rb") as file_content: form_data = aiohttp.FormData() for key, value in params.items(): form_data.add_field(key, str(value)) form_data.add_field('files', file_content, filename=file_path) async with session.post(url, data=form_data) as response: print("Headers:", response.request_info.headers) print("Response JSON:", await response.json()) print("Response text:", await response.text()) print("Response status:", response.status) print("Response content-type:", response.content_type) print("=" * 10) if response.ok: print("File uploaded successfully.") else: print("Failed to upload file. Error:", await response.text()) async def main(): file_path = "archive.zip" await upload_file(file_path) loop = asyncio.new_event_loop() loop.run_until_complete(main()) Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
MrShandy Опубликовано 6 января Автор Поделиться Опубликовано 6 января Совсем какой то треш пошел, не проще ли открыть файл, прочитать данные и спокойно закрыть? Зачем его открытым то держать пока запрос выполняется? Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
Рекомендуемые сообщения
Присоединяйтесь к обсуждению
Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.