Thanks to Infratec for his binary HMAC code.
Note: this requires my MultipartFormDataRequests.pbi.
TwitterX.pbi:
Code: Select all
XIncludeFile "MultipartFormDataRequests.pbi"
Procedure.b R(t.s)
MessageRequester("Report",t,0)
EndProcedure
Procedure.d Defeat(a.d,b.d)
If a<b
ProcedureReturn a
Else
ProcedureReturn b
EndIf
EndProcedure
Macro EnsureThisNotEnd(etnt,endd)
If Right(etnt,Len(endd)) = endd
etnt = Left(etnt,Len(etnt)-Len(endd))
EndIf
EndMacro
Procedure.s EnsureEnd(t.s,endd.s)
If endd
If Right(t,Len(endd)) <> endd
t = t+endd
EndIf
EndIf
ProcedureReturn t
EndProcedure
#d1 = "|"
Procedure.s Twitter_FormatURLParameter(t.s)
t = ReplaceString(t,":","%3A")
t = ReplaceString(t,"?","%3F")
t = ReplaceString(t,"=","%3D")
t = ReplaceString(t,"/","%2F")
ProcedureReturn t
EndProcedure
Procedure.s Twitter_PrepareText(t.s)
t = ReplaceString(t,"<br>",Chr(13))
t = EscapeString(t)
ProcedureReturn t
EndProcedure
Macro Twitter_TweetURL(tu_at,tu_tid)
"https://x.com/"+tu_at+"/status/"+tu_tid
EndMacro
Procedure.s DelimitedArrayToJSONStringArray(arr.s,del.s="|")
#splitter = Chr(34)+","+Chr(34)
arr = ReplaceString(arr,del,#splitter)
EnsureThisNotEnd(arr,#splitter)
arr = Trim(arr,c34)
arr = "["+c34+arr+c34+"]"
ProcedureReturn arr
EndProcedure
Procedure StringHMAC_HexStringToBin(*HexString.Ascii, *Destination.Ascii)
Protected HighNibble, LowNibble
If *HexString And *Destination
While *HexString\a
HighNibble = *HexString\a - '0'
If HighNibble > $f
HighNibble - $27
EndIf
*HexString + 2
LowNibble = *HexString\a - '0'
If LowNibble > $f
LowNibble - $27
EndIf
*HexString + 2
*Destination\a = HighNibble << 4 | LowNibble
*Destination + 1
;Debug Hex(HighNibble << 4 | LowNibble, #PB_Ascii)
Wend
EndIf
EndProcedure
Procedure.s StringHMAC(msg$, key$, cipher.i=#PB_Cipher_SHA1, bits.i=256, encode$="Hex")
#IPAD = $36
#OPAD = $5C
Protected.i i, BlockSize, cipherResultSize
Protected result$, innerHash$
Protected *tmp, *ptr.Ascii, *innerHash, *outerHash
; adjust the needed values for different ciphers
BlockSize = 64
Select cipher
Case #PB_Cipher_MD5
cipherResultSize = 16
Case #PB_Cipher_SHA1
cipherResultSize = 20
Case #PB_Cipher_SHA2
If bits > 256
BlockSize = 128
EndIf
cipherResultSize = bits / 8
Case #PB_Cipher_SHA3
Select bits
Case 224
BlockSize = 1152 / 8
Case 256
BlockSize = 1088 / 8
Case 384
BlockSize = 832 / 8
Case 512
BlockSize = 576 / 8
EndSelect
cipherResultSize = bits / 8
Default
ProcedureReturn "cipher not implemented"
EndSelect
; special rule if length of the key is larger then the blocksize:
; use H(K) instead of K
If StringByteLength(key$, #PB_Ascii) > BlockSize
key$ = StringFingerprint(key$, cipher, bits, #PB_Ascii)
*tmp = AllocateMemory(cipherResultSize)
If *tmp
StringHMAC_HexStringToBin(@key$, *tmp)
key$ = PeekS(*tmp, cipherResultSize, #PB_Ascii)
FreeMemory(*tmp)
EndIf
EndIf
*outerHash = AllocateMemory(BlockSize + cipherResultSize)
If *outerHash
; K XOR opad
*ptr = *outerHash
PokeS(*outerHash, key$, -1, #PB_Ascii|#PB_String_NoZero)
For i = 0 To BlockSize - 1
*ptr\a = *ptr\a ! #OPAD
*ptr + 1
Next i
*innerHash = AllocateMemory(BlockSize + StringByteLength(msg$, #PB_UTF8))
If *innerHash
; K XOR ipad
*ptr = *innerHash
PokeS(*innerHash, key$, -1, #PB_Ascii|#PB_String_NoZero)
For i = 0 To BlockSize - 1
*ptr\a = *ptr\a ! #IPAD
*ptr + 1
Next i
; (K XOR ipad) + M)
PokeS(*ptr, msg$, -1, #PB_UTF8|#PB_String_NoZero)
; H((K XOR ipad) + M))
innerHash$ = Fingerprint(*innerHash, MemorySize(*innerHash), cipher, bits)
; (K XOr opad) + H((K XOr ipad) + M)
StringHMAC_HexStringToBin(@innerHash$, *outerHash + BlockSize)
; H((K XOR opad) + H((K XOR ipad) + M))
result$ = Fingerprint(*outerHash, BlockSize + cipherResultSize, cipher, bits)
; optional result is coded in Base64
If LCase(encode$) = "base64"
*tmp = AllocateMemory(cipherResultSize)
If *tmp
StringHMAC_HexStringToBin(@result$, *tmp)
result$ = Base64Encoder(*tmp, MemorySize(*tmp))
FreeMemory(*tmp)
EndIf
EndIf
FreeMemory(*innerHash)
EndIf
FreeMemory(*outerHash)
EndIf
ProcedureReturn result$
EndProcedure
Structure TwitterCredentials
ConsumerKey.s
ConsumerSecret.s
AccessToken.s
AccessTokenSecret.s
EndStructure
Procedure.s OAuth(*o.TwitterCredentials,ur.s)
UseMD5Fingerprint() : UseSHA1Fingerprint()
oauthTimestamp.i = Date()
oauthNonce.s = StrD(Date() * Random(999,333),0)
oauthNonce = StringFingerprint(oauthNonce, #PB_Cipher_MD5)
NewList oauthParameters.s()
AddElement(oauthParameters()) : oauthParameters() = "oauth_consumer_key=" + *o\ConsumerKey
AddElement(oauthParameters()) : oauthParameters() = "oauth_nonce=" + oauthNonce
AddElement(oauthParameters()) : oauthParameters() = "oauth_signature_method=HMAC-SHA1"
AddElement(oauthParameters()) : oauthParameters() = "oauth_timestamp=" + oauthTimestamp
AddElement(oauthParameters()) : oauthParameters() = "oauth_token=" + *o\AccessToken
AddElement(oauthParameters()) : oauthParameters() = "oauth_version=1.0"
SortList(oauthParameters(),#PB_Sort_Ascending)
oauthParameterString.s = ""
ForEach oauthParameters()
oauthParameterString + oauthParameters()+"&"
Next
EnsureThisNotEnd(oauthParameterString,"&")
oauthParameterString = ReplaceString(oauthParameterString,"&","%26")
oauthBaseString.s = "POST&"
oauthBaseString + Twitter_FormatURLParameter(ur) + "&"
oauthBaseString + Twitter_FormatURLParameter(oauthParameterString)
oauthSigningKey.s = URLEncoder(*o\ConsumerSecret) + "&" + URLEncoder(*o\AccessTokenSecret)
oauthSignature.s = StringFingerprint(oauthBaseString,#PB_Cipher_SHA1|#PB_Cipher_HMAC,0,#PB_UTF8,oauthSigningKey,#PB_UTF8)
oauthSignature = StringHMAC(oauthBaseString,oauthSigningKey,#PB_Cipher_SHA1,256,"base64")
oauthSignature = Twitter_FormatURLParameter(URLEncoder(oauthSignature))
authorizationHeader.s = "OAuth "
authorizationHeader + "oauth_consumer_key="+c34 + *o\ConsumerKey +c34+ ", "
authorizationHeader + "oauth_nonce="+c34 + oauthNonce + c34+", "
authorizationHeader + "oauth_signature="+c34 + oauthSignature + c34+", "
authorizationHeader + "oauth_signature_method="+c34+"HMAC-SHA1" + c34+", "
authorizationHeader + "oauth_timestamp="+c34+oauthTimestamp +c34+", "
authorizationHeader + "oauth_token="+c34+*o\AccessToken +c34+", "
authorizationHeader + "oauth_version="+c34+"1.0"+c34
ProcedureReturn authorizationHeader
EndProcedure
Procedure.s DeriveMediaIDFromResponse(ret.s)
j.i = ParseJSON(#PB_Any,ret)
If j
ObjectValue = JSONValue(j)
If ExamineJSONMembers(ObjectValue)
While NextJSONMember(ObjectValue)
;R("JSONMemberKey(ObjectValue): "+JSONMemberKey(ObjectValue))
If JSONMemberKey(ObjectValue) = "id"
media_id.s = GetJSONString(JSONMemberValue(ObjectValue))
Break 1
EndIf
Wend
Else
R("CAN'T EXAMINE MAIN")
EndIf
FreeJSON(j)
EndIf
If media_id=""
R("Couldn't find media_id in Twitter's response!")
EndIf
ProcedureReturn media_id
EndProcedure
Procedure.s DeriveMediaIDFromResponse_NEW(ret.s)
j.i = ParseJSON(#PB_Any,ret)
If j
ObjectValue = JSONValue(j)
If ExamineJSONMembers(ObjectValue)
While NextJSONMember(ObjectValue)
;R("JSONMemberKey(ObjectValue): "+JSONMemberKey(ObjectValue))
If JSONMemberKey(ObjectValue) = "data"
dov = JSONMemberValue(ObjectValue)
If ExamineJSONMembers(dov)
While NextJSONMember(dov)
;R("JSONMemberKey(dov): "+JSONMemberKey(dov))
If JSONMemberKey(dov) = "id"
media_id.s = GetJSONString(JSONMemberValue(dov))
Break 1
EndIf
Wend
EndIf
EndIf
Wend
Else
R("CAN'T EXAMINE MAIN")
EndIf
FreeJSON(j)
EndIf
If media_id=""
R("Couldn't find media_id in Twitter's response!")
EndIf
ProcedureReturn media_id
EndProcedure
#DefaultMediaUploadURL = "https://api.x.com/2/media/upload"
Procedure.s Twitter_UploadImageFile(*o.TwitterCredentials,imgfn.s) ; will not work with GIFs
fsz = FileSize(imgfn)
If fsz = -1
Debug "Image file does not exist!"
Debug imgfn
ProcedureReturn ""
EndIf
m.i = MPFR_Create()
MPFR_AddFile(m,"media",imgfn)
MPFR_AddTextField(m,"media_category","tweet_image")
NewMap header.s()
;ur.s = "https://upload.twitter.com/1.1/media/upload.json"
ur.s = #DefaultMediaUploadURL;+"?media_category=tweet_image"
header("Authorization") = OAuth(*o,ur)
MPFR_SendWithCustomHeaders(m,ur,header())
If MPFRequest(m)\status_code=200
;R(MPFRequest(m)\response_message)
;Debug MPFRequest(m)\response_message
media_id.s = DeriveMediaIDFromResponse_NEW(MPFRequest(m)\response_message)
Else
Debug "Twitter_UploadImageFile STATUS CODE: "+MPFRequest(m)\status_code
If MPFRequest(m)\error_message : Debug "ERROR MESSAGE: "+MPFRequest(m)\error_message : EndIf
Debug "Twitter_UploadImageFile RESPONSE: "+MPFRequest(m)\response_message
EndIf
MPFR_Free(m)
ProcedureReturn media_id
EndProcedure
Procedure.s Twitter_UploadPBImage(*o.TwitterCredentials,img.i)
UseJPEGImageEncoder()
*mem = EncodeImage(img,#PB_ImagePlugin_JPEG)
m.i = MPFR_Create()
MPFR_AddData(m,"media",*mem,MemorySize(*mem),MimeType("jpg"))
FreeMemory(*mem)
MPFR_AddTextField(m,"media_category","tweet_image")
NewMap header.s()
;ur.s = "https://upload.twitter.com/1.1/media/upload.json"
ur.s = #DefaultMediaUploadURL
header("Authorization") = OAuth(*o,ur)
MPFR_SendWithCustomHeaders(m,ur,header())
If MPFRequest(m)\status_code=200
media_id.s = DeriveMediaIDFromResponse_NEW(MPFRequest(m)\response_message)
Else
Debug "Twitter_UploadPBImage STATUS CODE: "+MPFRequest(m)\status_code
If MPFRequest(m)\error_message : Debug "ERROR MESSAGE: "+MPFRequest(m)\error_message : EndIf
Debug "Twitter_UploadPBImage RESPONSE: "+MPFRequest(m)\response_message
EndIf
MPFR_Free(m)
ProcedureReturn media_id
EndProcedure
Procedure.s Twitter_UploadMediaFileChunked(*o.TwitterCredentials,media_fn.s) ; WORKS, uses MPFR library
Debug "PROC: UploadChunkedMedia"
fsz.d = FileSize(media_fn)
If fsz = -1
Debug "Media file does not exist!"
ProcedureReturn ""
EndIf
mime_type.s = MimeType(GetExtensionPart(media_fn))
NewMap custom_header.s()
u.s = #DefaultMediaUploadURL+"/initialize"
custom_header("Authorization") = OAuth(*o,u)
;R(custom_header("Authorization"))
custom_header("Content-Type") = "application/json"
jt.s = "{"
jt + c34+"total_bytes"+c34+":"+Str(fsz)+","
If LCase(GetExtensionPart(media_fn))="srt"
jt + c34+"media_category"+c34+":"+c34+"subtitles"+c34+","
EndIf
jt + c34+"media_type"+c34+":"+c34+mime_type+c34
jt+"}"
Debug "INIT"
req.i = HTTPRequest(#PB_HTTP_Post,u,jt,0,custom_header())
Debug "Twitter_UploadMediaFileChunked init STATUS CODE: "+HTTPInfo(req,#PB_HTTP_StatusCode)
If HTTPInfo(req,#PB_HTTP_ErrorMessage) : Debug "Twitter_UploadMediaFileChunked init ERROR: "+HTTPInfo(req,#PB_HTTP_ErrorMessage) : EndIf
Debug "Twitter_UploadMediaFileChunked init RESPONSE: "+HTTPInfo(req,#PB_HTTP_Response)
media_id.s = DeriveMediaIDFromResponse_NEW(HTTPInfo(req,#PB_HTTP_Response))
;R("MEDIA ID: *"+media_id+"*")
FinishHTTP(req)
If media_id="" : ProcedureReturn "" : EndIf
Debug "------------------------" : Debug "" : Debug ""
;media_id.s=""
u.s = #DefaultMediaUploadURL+"/"+media_id+"/append"
ClearMap(custom_header())
custom_header("Authorization") = OAuth(*o,u)
#ChunkSize = 1024 * 1024 * 3 ; 3mb
chunks.i = Round(fsz / #ChunkSize,#PB_Round_Up)
;R("CHUNKS: "+Str(chunks))
file_start.i = 0
f = ReadFile(#PB_Any,media_fn)
For a = 1 To chunks
Debug "APPEND #"+Str(a)+"/"+Str(chunks)
seg = a-1
data_len.i = Defeat(#ChunkSize,fsz-file_start)
*mem = AllocateMemory(data_len)
ReadData(f,*mem,data_len)
m.i = MPFR_Create()
;MPFR_AddTextField(m,"id",media_id)
MPFR_AddTextField(m,"segment_index",Str(seg))
MPFR_AddData(m,"media",*mem,data_len,mime_type)
FreeMemory(*mem)
MPFR_SendWithCustomHeaders(m,u,custom_header())
Debug "Twitter_UploadMediaFileChunked append STATUS CODE: "+MPFRequest(m)\status_code
If MPFRequest(m)\error_message : Debug "Twitter_UploadMediaFileChunked append ERROR: "+MPFRequest(m)\error_message : EndIf
Debug "Twitter_UploadMediaFileChunked append RESPONSE: "+MPFRequest(m)\response_message
If MPFRequest(m)\status_code<>200 : Debug "Append failure. Aborting." : CloseFile(f) : MPFR_Free(m) : ProcedureReturn "" : EndIf
MPFR_Free(m)
file_start + #ChunkSize
Debug "------------------------" : Debug "" : Debug ""
Next a
CloseFile(f)
;End
Debug "FINALISE"
ClearMap(custom_header())
custom_header("Content-Type") = "application/json"
u.s = #DefaultMediaUploadURL+"/"+media_id+"/finalize"
custom_header("Authorization") = OAuth(*o,u)
req.i = HTTPRequest(#PB_HTTP_Post,u,"",0,custom_header())
Debug "Twitter_UploadMediaFileChunked finalize STATUS CODE: "+HTTPInfo(req,#PB_HTTP_StatusCode)
If HTTPInfo(req,#PB_HTTP_ErrorMessage) : Debug "Twitter_UploadMediaFileChunked finalize ERROR: "+HTTPInfo(req,#PB_HTTP_ErrorMessage) : EndIf
Debug "Twitter_UploadMediaFileChunked finalize RESPONSE: "+HTTPInfo(req,#PB_HTTP_Response)
If HTTPInfo(req,#PB_HTTP_StatusCode)<>"200" : Debug "Finalise failure. Aborting." : FinishHTTP(req) : ProcedureReturn "" : EndIf
Debug "------------------------"
FinishHTTP(req)
ProcedureReturn media_id
EndProcedure
Procedure.b Twitter_SetMediaMetadata(*o.TwitterCredentials,media_id.s,alt_text.s,allow_download.b)
If media_id="" ;Or alt_text=""
ProcedureReturn #False
EndIf
postFields.s = "{ "
postFields + c34+"media_id"+c34+": "+c34+media_id+c34
postFields + ", "+c34+"alt_text"+c34+": {"+c34+"text"+c34+": "+c34+alt_text+c34+" }"
;postFields + ", "+c34+"allow_download_status"+c34+": {"+c34+"allow_download"+c34+": "+c34+ByteTruth(allow_download)+c34+" }"
;postFields + ", "+c34+"found_media_origin"+c34+": {"+c34+"id"+c34+": "+c34+fmo_id+c34+", "+c34+"provider"+c34+": "+c34+fmo_provider+c34+" }"
;postFields + ", "+c34+"upload_source"+c34+": {"+c34+"text"+c34+": "+c34+upload_source+c34+" }"
postFields + " }"
;R(postFields)
NewMap header.s()
;ur.s = "https://upload.twitter.com/1.1/media/metadata/create.json"
ur.s = "https://upload.twitter.com/2/media/metadata/create"
header("Authorization") = OAuth(*o,ur)
header("Content-Type") = "application/json; charset=UTF-8"
req.i = HTTPRequest(#PB_HTTP_Post,ur,postFields,0,header())
If req
StatusCode.i = Val(HTTPInfo(req,#PB_HTTP_StatusCode))
Debug "Twitter_SetMediaMetadata StatusCode: " + Str(StatusCode)
;Debug "Response: " + HTTPInfo(req,#PB_HTTP_Response)
em.s=HTTPInfo(req,#PB_HTTP_ErrorMessage) : If em : Debug "Error: "+em : EndIf
FinishHTTP(req)
If StatusCode=>200 And StatusCode<=299
ProcedureReturn #True
EndIf
EndIf
EndProcedure
Procedure.i Twitter_SetVideoSubtitles(*o.TwitterCredentials,media_id.s,subtitle_media_id.s,language_code.s,display_name.s)
;Debug "PROC: SetVideoSubtitles"
If media_id="" Or subtitle_media_id=""
ProcedureReturn #False
EndIf
postFields.s = "{ "
postFields + c34+"media_id"+c34+": "+c34+media_id+c34
postFields + ", "+c34+"media_category"+c34+": "+c34+"TweetVideo"+c34
postFields + ", "+c34+"subtitle_info"+c34+": { "+c34+"subtitles"+c34+": [{ "+c34+"media_id"+c34+":"+c34+subtitle_media_id+c34+", "+c34+"language_code"+c34+":"+c34+language_code+c34+", "+c34+"display_name"+c34+":"+c34+display_name+c34+" } ] }"
postFields + " }"
;SetClipboardText(postFields)
;R(postFields)
NewMap header.s()
;ur.s = "https://upload.twitter.com/1.1/media/subtitles/create.json"
ur.s = "https://upload.twitter.com/2/media/subtitles/create.json"
header("Authorization") = OAuth(*o,ur)
header("Content-Type") = "application/json; charset=UTF-8"
req.i = HTTPRequest(#PB_HTTP_Post,ur,postFields,0,header())
If req
StatusCode.i = Val(HTTPInfo(req,#PB_HTTP_StatusCode))
Debug "Twitter_SetVideoSubtitles StatusCode: " + Str(StatusCode)
;Debug "Response: " + HTTPInfo(req,#PB_HTTP_Response)
em.s=HTTPInfo(req,#PB_HTTP_ErrorMessage) : If em : Debug "Error: "+em : EndIf
FinishHTTP(req)
If StatusCode=200
ProcedureReturn #True
Else
ProcedureReturn StatusCode
EndIf
EndIf
EndProcedure
Procedure.i Twitter_RemoveVideoSubtitles(*o.TwitterCredentials,media_id.s,language_code.s)
;Debug "PROC: RemoveVideoSubtitles"
If media_id="" Or language_code=""
ProcedureReturn #False
EndIf
postFields.s = "{ "
postFields + c34+"media_id"+c34+": "+c34+media_id+c34
postFields + ", "+c34+"media_category"+c34+": "+c34+"TweetVideo"+c34
postFields + ", "+c34+"subtitle_info"+c34+": { "+c34+"subtitles"+c34+": [{ "+c34+"language_code"+c34+":"+c34+language_code+c34+" }] }"
postFields + " }"
;R(postFields) : End
NewMap header.s()
;ur.s = "https://upload.twitter.com/1.1/media/subtitles/delete.json"
ur.s = "https://upload.twitter.com/2/media/subtitles/delete.json"
header("Authorization") = OAuth(*o,ur)
header("Content-Type") = "application/json; charset=UTF-8"
req.i = HTTPRequest(#PB_HTTP_Post,ur,postFields,0,header())
If req
StatusCode.i = Val(HTTPInfo(req,#PB_HTTP_StatusCode))
Debug "Twitter_RemoveVideoSubtitles StatusCode: " + Str(StatusCode)
;Debug "Response: " + HTTPInfo(req,#PB_HTTP_Response)
em.s=HTTPInfo(req,#PB_HTTP_ErrorMessage) : If em : Debug "Error: "+em : EndIf
FinishHTTP(req)
If StatusCode=200
ProcedureReturn #True
Else
ProcedureReturn StatusCode
EndIf
EndIf
EndProcedure
Procedure.s Twitter_PostTweet(*o.TwitterCredentials,text.s,media_id_arr.s="",in_reply_to.s="",quoting_tweet_id.s="")
If text="" And media_id_arr=""
ProcedureReturn ""
EndIf
postFields.s = "{ "
postFields + c34+"text"+c34+": "+c34+Twitter_PrepareText(text)+c34
If media_id_arr<>""
media_id_arr = DelimitedArrayToJSONStringArray(media_id_arr,#d1)
postFields + ", "+c34+"media"+c34+": {"+c34+"media_ids"+c34+": "+media_id_arr+" }"
EndIf
If in_reply_to<>""
postFields + ", "+c34+"reply"+c34+": {"+c34+"in_reply_to_tweet_id"+c34+": "+c34+in_reply_to+c34+" }"
EndIf
If quoting_tweet_id<>""
postFields + ", "+c34+"quote_tweet_id"+c34+": "+c34+quoting_tweet_id+c34
EndIf
postFields + " }"
;R(postFields)
NewMap header.s()
ur.s = "https://api.twitter.com/2/tweets"
header("Authorization") = OAuth(*o,ur)
header("Content-Type") = "application/json"
req.i = HTTPRequest(#PB_HTTP_Post,ur,postFields,0,header())
If req
If HTTPInfo(req,#PB_HTTP_StatusCode)<>"201"
Debug "Twitter_PostTweet StatusCode: " + HTTPInfo(req,#PB_HTTP_StatusCode)
Debug "Twitter_PostTweet Response: " + HTTPInfo(req,#PB_HTTP_Response)
EndIf
em.s=HTTPInfo(req,#PB_HTTP_ErrorMessage) : If em : Debug "Twitter_PostTweet Error: "+em : EndIf
*ret = HTTPMemory(req)
;Debug "Response size: " + MemorySize(*ret)
j.i = CatchJSON(#PB_Any,*ret,MemorySize(*ret))
FinishHTTP(req)
FreeMemory(*ret)
;dat.s = ComposeJSON(j,#PB_JSON_PrettyPrint) : R(dat)
ObjectValue = JSONValue(j)
If ExamineJSONMembers(ObjectValue)
While NextJSONMember(ObjectValue)
If JSONMemberKey(ObjectValue) = "data"
value = JSONMemberValue(ObjectValue)
If ExamineJSONMembers(value)
While NextJSONMember(value)
If JSONMemberKey(value) = "id"
id.s = GetJSONString(JSONMemberValue(value))
Break 2
EndIf
Wend
Else
R("CAN'T EXAMINE SUB")
EndIf
EndIf
Wend
Else
R("CAN'T EXAMINE MAIN")
EndIf
FreeJSON(j)
ProcedureReturn id
EndIf
EndProcedure
Procedure.s Twitter_PostPoll(*o.TwitterCredentials,text.s,duration_minutes.i,option_arr.s,in_reply_to.s="",quoting_tweet_id.s="")
postFields.s = "{ "
postFields + c34+"text"+c34+": "+c34+Twitter_PrepareText(text)+c34
option_arr = DelimitedArrayToJSONStringArray(option_arr)
postFields + ", "+c34+"poll"+c34+": {"+c34+"options"+c34+": "+option_arr+", "+c34+"duration_minutes"+c34+":"+Str(duration_minutes)+" }"
If in_reply_to<>""
postFields + ", "+c34+"reply"+c34+": {"+c34+"in_reply_to_tweet_id"+c34+": "+c34+in_reply_to+c34+" }"
EndIf
If quoting_tweet_id<>""
postFields + ", "+c34+"quote_tweet_id"+c34+": "+c34+quoting_tweet_id+c34
EndIf
postFields + " }"
NewMap header.s()
ur.s = "https://api.twitter.com/2/tweets"
header("Authorization") = OAuth(*o,ur)
header("Content-Type") = "application/json"
req.i = HTTPRequest(#PB_HTTP_Post,ur,postFields,0,header())
If req
Debug "Twitter_PostPoll StatusCode: " + HTTPInfo(req,#PB_HTTP_StatusCode)
Debug "Twitter_PostPoll Response: " + HTTPInfo(req,#PB_HTTP_Response)
em.s=HTTPInfo(req,#PB_HTTP_ErrorMessage) : If em : Debug "Error: "+em : EndIf
*ret = HTTPMemory(req)
;Debug "Response size: " + MemorySize(*ret)
j.i = CatchJSON(#PB_Any,*ret,MemorySize(*ret))
FinishHTTP(req)
FreeMemory(*ret)
dat.s = ComposeJSON(j,#PB_JSON_PrettyPrint) : R(dat)
ObjectValue = JSONValue(j)
If ExamineJSONMembers(ObjectValue)
While NextJSONMember(ObjectValue)
If JSONMemberKey(ObjectValue) = "data"
value = JSONMemberValue(ObjectValue)
If ExamineJSONMembers(value)
While NextJSONMember(value)
If JSONMemberKey(value) = "id"
id.s = GetJSONString(JSONMemberValue(value))
Break 2
EndIf
Wend
Else
R("CAN'T EXAMINE SUB")
EndIf
EndIf
Wend
Else
R("CAN'T EXAMINE MAIN")
EndIf
FreeJSON(j)
ProcedureReturn id
EndIf
EndProcedure
Procedure.s Twitter_MediaIDFromString(*o.TwitterCredentials,info.s)
If info=""
ProcedureReturn ""
EndIf
img.i = Val(info)
If IsImage(img)
; uploading a PB image
ProcedureReturn Twitter_UploadPBImage(*o,img)
Else
If FileSize(info)>0
ProcedureReturn Twitter_UploadImageFile(*o,info)
Else
; an already cached online file
ProcedureReturn info
EndIf
EndIf
ProcedureReturn ""
EndProcedure
Procedure.s Twitter_PostThread(*o.TwitterCredentials,List item.s(),include_numbers.b=#True)
reply_to.s = ""
num.i = 0
items.i = ListSize(item())
ForEach item()
num+1
this_one.s = EnsureEnd(item(),#d1)
txt.s = StringField(this_one,1,#d1)
media_id_arr.s = ""
fields = CountString(this_one,#d1)
For f = 2 To fields
media_info.s = StringField(this_one,f,#d1)
media_id.s = Twitter_MediaIDFromString(*o,media_info)
If media_id<>"" : media_id_arr+media_id+#d1 : EndIf
Next f
;R(Str(num)+" MEDIA ID ARR:"+c13+media_id_arr)
If txt="" And media_id_arr="" : R("SKIPPING "+Str(num)) : Continue : EndIf
If include_numbers
If num=1
;txt + " 🧵"
;txt + " \u1F9F5 "
txt + " 🧵"
Else
txt + " "+Str(num)+"/"+Str(items)
EndIf
EndIf
id.s = Twitter_PostTweet(*o,txt,media_id_arr,reply_to)
;R(Str(num)+c13+txt+c13+c13+c13+id)
If num=1 : orig_id.s=id : EndIf
reply_to = id
Next
ProcedureReturn orig_id
EndProcedure
Procedure.b Twitter_Retweet(*o.TwitterCredentials,id.s,tweet_id.s) ; untested; does not work with the free tier
postFields.s = "{ "+c34+"tweet_id"+c34+": "+c34+tweet_id+c34+" }"
NewMap header.s()
ur.s = "https://api.twitter.com/2/users/:"+id+"/retweets" ; not sure whether the : should be before id
header("Authorization") = OAuth(*o,ur)
header("Content-Type") = "application/json"
req.i = HTTPRequest(#PB_HTTP_Post,ur,postFields,0,header())
If req
Debug "Twitter_Retweet StatusCode: " + HTTPInfo(req,#PB_HTTP_StatusCode)
Debug "Twitter_Retweet Response: " + HTTPInfo(req,#PB_HTTP_Response)
em.s=HTTPInfo(req,#PB_HTTP_ErrorMessage) : If em : Debug "Error: "+em : EndIf
*ret = HTTPMemory(req)
;Debug "Response size: " + MemorySize(*ret)
j.i = CatchJSON(#PB_Any,*ret,MemorySize(*ret))
FinishHTTP(req)
FreeMemory(*ret)
dat.s = ComposeJSON(j,#PB_JSON_PrettyPrint) : R(dat)
ObjectValue = JSONValue(j)
If ExamineJSONMembers(ObjectValue)
While NextJSONMember(ObjectValue)
If JSONMemberKey(ObjectValue) = "data"
value = JSONMemberValue(ObjectValue)
If ExamineJSONMembers(value)
While NextJSONMember(value)
If JSONMemberKey(value) = "retweeted"
status.b = GetJSONBoolean(JSONMemberValue(value))
Break 2
EndIf
Wend
Else
R("RETWEET. CAN'T EXAMINE SUB")
EndIf
EndIf
Wend
Else
R("RETWEET. CAN'T EXAMINE MAIN")
EndIf
FreeJSON(j)
ProcedureReturn status
EndIf
EndProcedure
Procedure.b Twitter_PinTweet(*o.TwitterCredentials,tweet_id.s) ; untested; does not work with the free tier
postFields.s = "{ "+c34+"tweet_id"+c34+": "+c34+tweet_id+c34+" }"
NewMap header.s()
ur.s = "https://api.twitter.com/1.1/account/pin_tweet"
header("Authorization") = OAuth(*o,ur)
header("Content-Type") = "application/json"
req.i = HTTPRequest(#PB_HTTP_Post,ur,postFields,0,header())
If req
Debug "Twitter_PinTweet StatusCode: " + HTTPInfo(req,#PB_HTTP_StatusCode)
Debug "Twitter_PinTweet Response: " + HTTPInfo(req,#PB_HTTP_Response)
em.s=HTTPInfo(req,#PB_HTTP_ErrorMessage) : If em : Debug "Error: "+em : EndIf
*ret = HTTPMemory(req)
Debug "Twitter_PinTweet Response size: " + MemorySize(*ret)
Debug PeekS(*ret)
j.i = CatchJSON(#PB_Any,*ret,MemorySize(*ret))
FinishHTTP(req)
FreeMemory(*ret)
dat.s = ComposeJSON(j,#PB_JSON_PrettyPrint) : R(dat)
ObjectValue = JSONValue(j)
If ExamineJSONMembers(ObjectValue)
While NextJSONMember(ObjectValue)
If JSONMemberKey(ObjectValue) = "data"
value = JSONMemberValue(ObjectValue)
If ExamineJSONMembers(value)
While NextJSONMember(value)
If JSONMemberKey(value) = "retweeted"
status.b = GetJSONBoolean(JSONMemberValue(value))
Break 2
EndIf
Wend
Else
R("RETWEET. CAN'T EXAMINE SUB")
EndIf
EndIf
Wend
Else
R("RETWEET. CAN'T EXAMINE MAIN")
EndIf
FreeJSON(j)
ProcedureReturn status
EndIf
EndProcedure
Code: Select all
XIncludeFile "TwitterX.pbi"
cred.TwitterCredentials
cred\ConsumerKey = ""
cred\ConsumerSecret = ""
cred\AccessToken = ""
cred\AccessTokenSecret = ""
^ fill in these details!
; post a tweet:
Twitter_PostTweet(@cred,"my test tweet")
; post a tweet then reply to it:
tid.s = Twitter_PostTweet(@cred,"initial tweet")
Twitter_PostTweet(@cred,"reply tweet","",tid)
; post a tweet then quote it in another:
tid.s = Twitter_PostTweet(@cred,"the quoted tweet")
Twitter_PostTweet(@cred,"the quoting tweet","","",tid)
; post a tweet with an image (not a gif):
img_fn.s = "G:\my test image.jpg"
img_id.s = Twitter_UploadImageFile(@cred,img_fn)
Twitter_PostTweet(@cred,"my tweet with image",img_id)
; post a tweet with a PB image:
iw=600 : ih=400
img.i = CreateImage(#PB_Any,iw,ih)
StartDrawing(ImageOutput(img))
For a = 1 To 20
LineXY(Random(iw),Random(ih),Random(iw),Random(ih),RGB(Random(255),Random(255),Random(255)))
Next a
StopDrawing()
img_id.s = Twitter_UploadPBImage(@cred,img)
Twitter_PostTweet(@cred,"my PB image",img_id)
; post a tweet with an image, video or gif:
gif_fn.s = "G:\my test animation.gif"
gif_id.s = Twitter_UploadMediaFile(@cred,gif_fn)
Twitter_PostTweet(@cred,"my tweet with gif",gif_id)
; post a tweet with a video with subtitles:
video_fn.s = "G:\my video.mp4"
video_id.s = Twitter_UploadMedia(@cred,video_fn)
srt_fn.s = "G:\my subtitles.srt"
subtitle_id.s = Twitter_UploadMedia(@cred,srt_fn)
Twitter_SetVideoSubtitles(@cred,video_id,subtitle_id,"EN","English")
Twitter_PostTweet(@cred,"my subtitled video",video_id)
; post a poll:
Twitter_PostPoll(@cred,"a test poll",60 * 60 * 24,"apple|pear|grape|melon|")
; post a thread:
UsePNGImageDecoder()
UseJPEGImageDecoder()
imgfn.s = OpenFileRequester("Please choose an image to upload", "", "Images (*.jpg;*.png)|*.jpg;*.png", 0)
red_img.i = CreateImage(#PB_Any,600,400,24,#Red)
red_media_id.s = Twitter_UploadPBImage(@cred,red_img)
blue_img.i = CreateImage(#PB_Any,600,400,24,#Blue)
NewList item.s()
AddElement(item()) : item()="my thread listing some fruits"
AddElement(item()) : item()="apple"
AddElement(item()) : item()="pear|"+imgfn
AddElement(item()) : item()="melon|"+red_media_id
AddElement(item()) : item()="grape|"+Str(blue_img)
id.s = Twitter_PostThread(@cred,item(),#True)
ur.s = Twitter_TweetURL(#TwitterAt,id)
RunProgram(ur)