Tooting to Mastodon

Share your advanced PureBasic knowledge/code with the community.
jamirokwai
Enthusiast
Enthusiast
Posts: 771
Joined: Tue May 20, 2008 2:12 am
Location: Cologne, Germany
Contact:

Tooting to Mastodon

Post by jamirokwai »

Hi there,

I fiddled around with Mastodon and tooting from PureBasic.

How to use: Generate a new application on your mastodon-server. Save the access-token and copy everything into the fields #masto_XXX at the end of the example. Then, you can init the module and verify access. If the Access Token returns #true, you can toot a simple text.

1.0.0 initial with tooting
1.1.0 add image and toot it
1.2.0 will return URL of your last toot

Code: Select all

;{ ===========================================================================================================[Header ]
;{ ===========================================================================================================[Header ]
;: 
;: Name ......... : MastoToot
;: Version ...... : 1.2.0
;: Type ......... : Module
;: Author ....... : jamirokwai
;: Compiler ..... : PureBasic V6.01
;: Subsystem .... : none
;: TargetOS ..... : Windows ? / MacOS / Linux ?
;: Description .. : Toot to Mastodon server
;: License ...... : MIT License 
;: 
;: Thanks to: https://chrisjones.io/articles/using-php-And-curl-To-post-media-To-the-mastodon-api/
;: Github: https://github.com/foodsnacker/pb-toot
;:
;: Permission is hereby granted, free of charge, to any person obtaining a copy
;: of this software and associated documentation files (the "Software"), to deal
;: in the Software without restriction, including without limitation the rights
;: to use, copy, modify, merge, publish, distribute, sublicense, And/Or sell
;: copies of the Software, and to permit persons to whom the Software is
;: furnished to do so, subject to the following conditions:
;:  
;: The above copyright notice and this permission notice shall be included in all
;: copies or substantial portions of the Software.
;: 
;: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
;: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
;: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
;: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
;: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
;: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
;: SOFTWARE. 
;:
;}

DeclareModule MastoToot
  
  Global masto_reply.s     ; will be the return value, a JSON
  Global masto_status.s    ; status of last operation, see documentation, e.g. "200"
  
  Declare   InitMasto(appserver.s, appname.s, appweb.s)
  Declare   SetAccessToken(token.s)
  Declare   VerifyAccessToken()
  Declare.s AddMedia(filename.s, *source, buffersize, imagetype.s, description.s = "")
  Declare.s Toot(message.s, imageid.s = "")
  
EndDeclareModule

Module MastoToot
  EnableExplicit
  
  Global masto_appname.s   ; the name of your app, e.g. "PureBasic"
  Global masto_appweb.s    ; the URL of your app, e.g. "https://www.purebasic.com"
  Global masto_server.s    ; the URL of the server, e.g. "https://mastodon.gamedev.place"
  Global access_token.s    ; your access token. You should let your users generate it and place it in the app!

  Define masto_request_create_application.s
  Define masto_request_create_authenticate.s
  Define masto_verify_credentials.s
  
  Global masto_create_application_api.s
  Global masto_request_authenticate_api.s
  Global masto_verify_credentials_api.s
  Global masto_toot_api.s
  Global masto_media_api.s
  
  Define client_id.s
  Define client_secret.s
  Define vapid_key.s     
  
  Procedure   InitMasto(appserver.s, appname.s, appweb.s)
    masto_appname = appname 
    masto_appweb  = appweb
    masto_server  = appserver
    
    masto_create_application_api   = masto_server + "/api/v1/apps"
    masto_request_authenticate_api = masto_server + "/oauth/token"
    masto_verify_credentials_api   = masto_server + "/api/v1/apps/verify_credentials"
    masto_toot_api                 = masto_server + "/api/v1/statuses"
    masto_media_api.s              = masto_server + "/api/v2/media"
  EndProcedure
  
  Procedure   SetAccessToken(token.s)
    access_token = token
  EndProcedure
  
  Procedure.s GenerateCreateApplicationRequest(clientname.s, appweb.s)
    ProcedureReturn "client_name=" + clientname + "&redirect_uris=urn:ietf:wg:oauth:2.0:oob&scopes=Read write push&website=" + appweb
  EndProcedure
  
  Procedure.s GenerateCreateAuthenticate(clientid.s, clientsecret.s)
    ProcedureReturn "client_id=" + clientid + "&client_secret=" + clientsecret + "&redirect_uri=urn:ietf:wg:oauth:2.0:oob&grant_type=client_credentials"
  EndProcedure
  
  Procedure.s GetPartFromString(instring.s, whatstring.s)
    Define first = FindString(instring, whatstring) + Len(whatstring) + 3
    If first > 0
      Define second = FindString(instring, Chr(34), first)
      ProcedureReturn Mid(instring, first, second - first)
    EndIf
    ProcedureReturn "1"
  EndProcedure
  
  Procedure.s CallServerPost(serverurl.s, servercall.s)
    Define result.s = ""
    
    Define *Buffer = AllocateMemory(Len(servercall), #PB_Memory_NoClear)
    PokeS(*Buffer, servercall, -1, #PB_UTF8|#PB_String_NoZero)
    
    Define HttpRequest = HTTPRequestMemory(#PB_HTTP_Post, serverurl, *Buffer, MemorySize(*Buffer))
    
    If HttpRequest
      masto_reply = HTTPInfo(HTTPRequest, #PB_HTTP_Response)
      result      = HTTPInfo(HTTPRequest, #PB_HTTP_StatusCode)
      FinishHTTP(HTTPRequest)
    EndIf
    
    FreeMemory(*buffer)
    
    ProcedureReturn result
  EndProcedure
  
  Procedure.s CallServerPost_Verify(serverurl.s, servercall.s, accesstoken.s)
    Define result.s = ""
    
    Define *Buffer = AllocateMemory(Len(servercall), #PB_Memory_NoClear)
    PokeS(*Buffer, servercall, -1, #PB_UTF8|#PB_String_NoZero)
    
    Define NewMap header.s()
    header("Authorization") = "Bearer " + accesstoken
    Define HttpRequest = HTTPRequestMemory(#PB_HTTP_Get, serverurl, *Buffer, MemorySize(*Buffer), 0, Header())
    
    If HttpRequest
      masto_reply = HTTPInfo(HTTPRequest, #PB_HTTP_Response)
      result      = HTTPInfo(HTTPRequest, #PB_HTTP_StatusCode)
      
      FinishHTTP(HTTPRequest)
    EndIf
    
    FreeMemory(*buffer)
    
    ProcedureReturn result
  EndProcedure
  
  Procedure.s AddMedia(filename.s, *source, buffersize, imagetype.s, description.s = "")
    Define mediaid.s
    Define boundary.s = "------0123456789"
    
    Define NewMap header.s()
    
    Define post_data.s = "--" + boundary + #CRLF$
    post_data + "Content-Disposition: form-data; name=" + Chr(34) + "file" + Chr(34) + "; " +
                "filename=" + Chr(34) + GetFilePart(filename) + Chr(34)
    If description <> ""
      post_data + ";description=" + Chr(34) + description + Chr(34)
    EndIf
    post_data + #CRLF$
    
    post_data + "Content-Type: image/" + imagetype + #CRLF$
    post_data + #CRLF$
    
    Global PostLen = StringByteLength(post_data, #PB_UTF8)
    Global BoundaryLen = StringByteLength(boundary, #PB_UTF8)
    
    Global *Buffer = AllocateMemory(PostLen + buffersize + 2 + 2 + BoundaryLen + 2 + 2, #PB_Memory_NoClear)
    PokeS(*Buffer, post_data, -1, #PB_UTF8|#PB_String_NoZero)
    CopyMemory(*source, *buffer + PostLen, buffersize)
    PokeS(*Buffer + PostLen + buffersize, #CRLF$ + "--" + boundary + "--" + #CRLF$, -1, #PB_UTF8|#PB_String_NoZero)
    
    header("Content-Type") = "multipart/form-data; boundary=" + boundary
    header("Content-Length") = Str(MemorySize(*Buffer))
    header("Authorization") = "Bearer " + access_token
    
    Global HttpRequest = HTTPRequestMemory(#PB_HTTP_Post, masto_media_api, *Buffer, MemorySize(*Buffer), 0, Header())
    If HttpRequest
      masto_status = HTTPInfo(HTTPRequest, #PB_HTTP_StatusCode)
      
      If masto_status <> "200"
        FinishHTTP(HTTPRequest)
        ProcedureReturn ""
      EndIf
      
      masto_reply            = HTTPInfo(HTTPRequest, #PB_HTTP_Response)
      Define masto_mediaid.s = GetPartFromString(masto_reply, "id")
      
      FinishHTTP(HttpRequest)
    EndIf
    
    FreeMemory(*Buffer)
    
    ProcedureReturn masto_mediaid
  EndProcedure
  
  Procedure.s Toot(message.s, imageid.s = "")
    Define result.s = ""
    
    message = "status=" + message
    
    If imageid <> ""
      message + "&media_ids[]=" + imageid
    EndIf
    
    Define NewMap header.s()
    header("Authorization") = "Bearer " + access_token
    HttpRequest = HTTPRequest(#PB_HTTP_Post, masto_toot_api, message, 0, Header())
    
    If HttpRequest
      masto_status = HTTPInfo(HTTPRequest, #PB_HTTP_StatusCode)
      If masto_status <> "200"
        FinishHTTP(HTTPRequest)
        ProcedureReturn "0"
      EndIf
      
      masto_reply        = HTTPInfo(HTTPRequest, #PB_HTTP_Response)
      Define masto_url.s = GetPartFromString(masto_reply, "url")
      
      FinishHTTP(HTTPRequest)
    EndIf
    
    ProcedureReturn masto_url
  EndProcedure
  
  ; you won't need these procedures, but if you like to create an application on your server,
  ; or get keys, and your personal access token, go for it. I would recommend to ask people to open their
  ; account on their server, and generate the token by themselves.
  Procedure   RegisterApplication() ; if you do, call this in step 1 and save the keys
                                    ; register application: don't do it in the app. Let users supply only the access_token
                                    ; masto_request_create_application.s = GenerateCreateApplicationRequest(masto_appname, masto_appweb)
                                    ; CallServerPost(masto_api, masto_request_create_application)
    
    ; get all keys
    ; client_id     = GetPartFromString(masto_reply, "client_id")
    ; client_secret = GetPartFromString(masto_reply, "client_secret")
    ; vapid_key     = GetPartFromString(masto_reply, "vapid_key")    
  EndProcedure
  
  Procedure   RequestAuthorizationForToken() ; if you do, call this in step 2
                                             ; request authentication
                                             ; masto_request_create_authenticate.s = GenerateCreateAuthenticate(client_id, client_secret)
                                             ; CallServerPost(masto_request_authenticate_api, masto_request_create_authenticate)
    
    ;- get access-token
    ; Define access_token.s  = GetPartFromString(masto_reply, "access_token")
    
  EndProcedure
  
  ; this on may be helpful to check, if users have correctly entered access-token and server
  Procedure   VerifyAccessToken() ; if you do, call this in step 3 to verify access and the token from step 2
    CallServerPost_Verify(masto_verify_credentials_api, " ", access_token)    
    If GetPartFromString(masto_reply, "name") = masto_appname ; the app-name will be returned
      ProcedureReturn #True
    Else
      ProcedureReturn #False
    EndIf
  EndProcedure
  
EndModule

CompilerIf #PB_Compiler_IsMainFile
  UsePNGImageEncoder()
  
  #masto_server      = "my-mastodon-server"
  #masto_appname     = "my-app"
  #masto_app_url     = "page-of-my-app"
  #masto_acces_token = "my-token" ; from my/server/settings/applications
  MastoToot::InitMasto(#masto_server, #masto_appname, #masto_app_url)
  MastoToot::SetAccessToken(#masto_acces_token)
  If MastoToot::VerifyAccessToken() = #True
    
    ; creates a black image
    CreateImage(0,400,400,32,0)
    Define *buff = EncodeImage(0, #PB_ImagePlugin_PNG)
    
    ; add media to your mastodon account
    Define media_id.s = MastoToot::AddMedia("filename.png", *buff, MemorySize(*buff), "png", "Black image.")
    
    ; toot it with one image
    Define tooturl.s = MastoToot::Toot("Tooting from PureBasic!", media_id)
  EndIf
CompilerEndIf
Last edited by jamirokwai on Sat Mar 25, 2023 10:16 pm, edited 3 times in total.
Regards,
JamiroKwai
User avatar
Caronte3D
Addict
Addict
Posts: 1026
Joined: Fri Jan 22, 2016 5:33 pm
Location: Some Universe

Re: Tooting to Mastodon

Post by Caronte3D »

Interesthing...
In case someone (like me) don't know about Mastodon, here is a video:
https://www.youtube.com/watch?v=IPSbNdBmWKE
jamirokwai
Enthusiast
Enthusiast
Posts: 771
Joined: Tue May 20, 2008 2:12 am
Location: Cologne, Germany
Contact:

Re: Tooting to Mastodon

Post by jamirokwai »

Caronte3D wrote: Mon Mar 20, 2023 11:39 am Interesthing...
In case someone (like me) don't know about Mastodon, here is a video:
https://www.youtube.com/watch?v=IPSbNdBmWKE
Thanks for the link!

If you are more into twitter, I would assume, access may be created in a similar manner.
Regards,
JamiroKwai
User avatar
idle
Always Here
Always Here
Posts: 5039
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Tooting to Mastodon

Post by idle »

Thanks for sharing.
I still snigger at title tooting to mastadon sounds like what I'd do of an evening listening to prog rock.
jamirokwai
Enthusiast
Enthusiast
Posts: 771
Joined: Tue May 20, 2008 2:12 am
Location: Cologne, Germany
Contact:

Re: Tooting to Mastodon

Post by jamirokwai »

Yeah. "Don't tweet, sweetie" by "Tooting to Mastodon". brand-new ... :-)
Regards,
JamiroKwai
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Tooting to Mastodon

Post by Kwai chang caine »

Caronte3D wrote:in case someone (like me) don't know about Mastodon
+1 :oops:
I not use this site, maybe a day, we never know :wink:
Thanks for sharing 8)
ImageThe happiness is a road...
Not a destination
User avatar
Kuron
Addict
Addict
Posts: 1626
Joined: Sat Oct 17, 2009 10:51 pm
Location: Pacific Northwest

Re: Tooting to Mastodon

Post by Kuron »

I have a Mastodon account, just haven't used it yet.
Best wishes to the PB community. Thank you for the memories. ♥️
Post Reply