Posting on Twitter/X
Posted: Tue Jul 23, 2024 12:58 pm
This is a small library for using the "free tier" abilities of the Twitter/X API. This includes posting tweets, posting tweets with images, gifs or videos, posting polls, and replying to and quoting tweets. In order to use it, you will need to create an app on Twitter, then get its keys and use them as shown in the demo code below.
Thanks to Infratec for his binary HMAC code.
Note: this requires my MultipartFormDataRequests.pbi.
TwitterX.pbi:
Usage demo:
Hopefully this is useful for someone. It certainly will be for me. If you run into any bugs, please let me know.
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)