@@ -410,6 +410,32 @@ def _parse_response(resp: Response) -> dict:
410410
411411 return data
412412
413+ @staticmethod
414+ def _format_response (resp_json , cls , multi = False ) -> md .Response :
415+ data , includes , meta , errors = (
416+ resp_json .get ("data" , []),
417+ resp_json .get ("includes" ),
418+ resp_json .get ("meta" ),
419+ resp_json .get ("errors" ),
420+ )
421+ if multi :
422+ data = [cls .new_from_json_dict (item ) for item in data ]
423+ else :
424+ data = cls .new_from_json_dict (data )
425+
426+ res = md .Response (
427+ data = data ,
428+ includes = md .Includes .new_from_json_dict (includes ),
429+ meta = md .Meta .new_from_json_dict (meta ),
430+ errors = (
431+ [md .Error .new_from_json_dict (err ) for err in errors ]
432+ if errors is not None
433+ else None
434+ ),
435+ _json = resp_json ,
436+ )
437+ return res
438+
413439 def _get (
414440 self ,
415441 url : str ,
@@ -434,29 +460,7 @@ def _get(
434460 if return_json :
435461 return resp_json
436462 else :
437- data , includes , meta , errors = (
438- resp_json .get ("data" , []),
439- resp_json .get ("includes" ),
440- resp_json .get ("meta" ),
441- resp_json .get ("errors" ),
442- )
443- if multi :
444- data = [cls .new_from_json_dict (item ) for item in data ]
445- else :
446- data = cls .new_from_json_dict (data )
447-
448- res = md .Response (
449- data = data ,
450- includes = md .Includes .new_from_json_dict (includes ),
451- meta = md .Meta .new_from_json_dict (meta ),
452- errors = (
453- [md .Error .new_from_json_dict (err ) for err in errors ]
454- if errors is not None
455- else None
456- ),
457- _json = resp_json ,
458- )
459- return res
463+ return self ._format_response (resp_json , cls , multi )
460464
461465 def get_tweets (
462466 self ,
@@ -762,6 +766,198 @@ def upload_media_chunked_status(
762766 else :
763767 return md .MediaUploadResponse .new_from_json_dict (data = data )
764768
769+ def upload_media_simple_v2 (
770+ self ,
771+ media : Optional [IO ] = None ,
772+ media_category : Optional [str ] = None ,
773+ additional_owners : Optional [List [str ]] = None ,
774+ return_json : bool = False ,
775+ ) -> Union [dict , md .MediaUpload ]:
776+ """
777+ Simple Upload, Use this endpoint to upload images to Twitter.
778+
779+ Note: The simple upload endpoint can only be used to upload images.
780+
781+ :param media: The raw binary file content being uploaded.
782+ :param media_category: The category that represents how the media will be used.
783+ This field is required when using the media with the Ads API.
784+ Possible values:
785+ - tweet_image
786+ - tweet_gif
787+ - tweet_video
788+ - amplify_video
789+ :param additional_owners: A comma-separated list of user IDs to set as additional owners
790+ allowed to use the returned media_id in Tweets or Cards.
791+ Up to 100 additional owners may be specified.
792+ :param return_json: Type for returned data. If you set True JSON data will be returned.
793+ :return: Media upload response.
794+ """
795+
796+ files , args = {}, {}
797+ if media :
798+ files ["media" ] = media
799+ else :
800+ raise PyTwitterError ("Need media or media_data" )
801+ if media_category :
802+ args ["media_category" ] = media_category
803+ if additional_owners :
804+ args ["additional_owners" ] = enf_comma_separated (
805+ name = "additional_owners" , value = additional_owners
806+ )
807+
808+ resp = self ._request (
809+ url = f"{ self .BASE_URL_V2 } /media/upload" ,
810+ verb = "POST" ,
811+ data = args ,
812+ files = files ,
813+ )
814+ data = self ._parse_response (resp = resp )
815+ if return_json :
816+ return data
817+ else :
818+ return md .MediaUpload .new_from_json_dict (data = data )
819+
820+ def upload_media_chunked_init_v2 (
821+ self ,
822+ total_bytes : int ,
823+ media_type : str ,
824+ media_category : Optional [str ] = None ,
825+ additional_owners : Optional [List [str ]] = None ,
826+ return_json : bool = False ,
827+ ) -> Union [dict , md .Response ]:
828+ """
829+ Chunked Upload, Use this endpoint to upload videos and images to Twitter.
830+
831+ Note: The chunked upload endpoint can be used to upload both images and videos.
832+ Videos must be sent as chunked media containers, which means that you must send the
833+ raw chunked media data and the media category separately.
834+
835+ :param total_bytes: The total size of the media being uploaded in bytes.
836+ :param media_type: The MIME type of the media being uploaded. example: image/jpeg, image/gif, and video/mp4.
837+ :param media_category: The category that represents how the media will be used.
838+ This field is required when using the media with the Ads API.
839+ Possible values:
840+ - tweet_image
841+ - tweet_gif
842+ - tweet_video
843+ - amplify_video
844+ - dm_video
845+ - subtitles
846+ :param additional_owners: A comma-separated list of user IDs to set as additional owners
847+ allowed to use the returned media_id in Tweets or Cards.
848+ Up to 100 additional owners may be specified.
849+ :param return_json: Type for returned data. If you set True JSON data will be returned.
850+ :return: Media upload response.
851+ """
852+
853+ args = {
854+ "command" : "INIT" ,
855+ "total_bytes" : total_bytes ,
856+ "media_type" : media_type ,
857+ }
858+ if media_category :
859+ args ["media_category" ] = media_category
860+ if additional_owners :
861+ args ["additional_owners" ] = enf_comma_separated (
862+ name = "additional_owners" , value = additional_owners
863+ )
864+
865+ resp = self ._request (
866+ url = f"{ self .BASE_URL_V2 } /media/upload" ,
867+ verb = "POST" ,
868+ data = args ,
869+ )
870+ data = self ._parse_response (resp = resp )
871+ if return_json :
872+ return data
873+ else :
874+ return self ._format_response (resp_json = data , cls = md .MediaUpload )
875+
876+ def upload_media_chunked_append_v2 (
877+ self ,
878+ media_id : str ,
879+ segment_index : int ,
880+ media : Optional [IO ],
881+ ) -> bool :
882+ """
883+ Used to upload a chunk (consecutive byte range) of the media file.
884+
885+ :param media_id: The `media_id` returned from the INIT step.
886+ :param segment_index: An ordered index of file chunk. It must be between 0-999 inclusive.
887+ The first segment has index 0, second segment has index 1, and so on.
888+ :param media: The raw binary file content being uploaded. Cannot be used with `media_data`.
889+ :return: True if upload success.
890+ """
891+ resp = self ._request (
892+ url = f"{ self .BASE_URL_V2 } /media/upload" ,
893+ verb = "POST" ,
894+ params = {
895+ "command" : "APPEND" ,
896+ "media_id" : media_id ,
897+ },
898+ data = {"segment_index" : segment_index },
899+ files = {"media" : media },
900+ )
901+ if resp .ok :
902+ return True
903+ raise PyTwitterError (resp .json ())
904+
905+ def upload_media_chunked_finalize_v2 (
906+ self ,
907+ media_id : str ,
908+ return_json : bool = False ,
909+ ) -> Union [dict , md .Response ]:
910+ """
911+ Check the status of the chunk upload.
912+
913+ Note: Can only call after the FINALIZE step. If chunked upload not sync mode will return error.
914+
915+ :param media_id: The `media_id` returned from the INIT step.
916+ :param return_json: Type for returned data. If you set True JSON data will be returned.
917+ :return: Media upload response.
918+ """
919+ resp = self ._request (
920+ url = f"{ self .BASE_URL_V2 } /media/upload" ,
921+ verb = "POST" ,
922+ params = {
923+ "command" : "FINALIZE" ,
924+ "media_id" : media_id ,
925+ },
926+ )
927+ data = self ._parse_response (resp = resp )
928+ if return_json :
929+ return data
930+ else :
931+ return self ._format_response (resp_json = data , cls = md .MediaUpload )
932+
933+ def upload_media_chunked_status_v2 (
934+ self ,
935+ media_id : str ,
936+ return_json : bool = False ,
937+ ) -> Union [dict , md .Response ]:
938+ """
939+ Check the status of the chunk upload.
940+
941+ Note: Can only call after the FINALIZE step. If chunked upload not sync mode will return error.
942+
943+ :param media_id: The `media_id` returned from the INIT step.
944+ :param return_json: Type for returned data. If you set True JSON data will be returned.
945+ :return: Media upload response.
946+ """
947+ resp = self ._request (
948+ url = f"{ self .BASE_URL_V2 } /media/upload" ,
949+ verb = "GET" ,
950+ params = {
951+ "command" : "STATUS" ,
952+ "media_id" : media_id ,
953+ },
954+ )
955+ data = self ._parse_response (resp = resp )
956+ if return_json :
957+ return data
958+ else :
959+ return self ._format_response (resp_json = data , cls = md .MediaUpload )
960+
765961 def create_tweet (
766962 self ,
767963 * ,
0 commit comments