66 "encoding/json"
77 "errors"
88 "fmt"
9+ "io"
910 "log"
1011 "net/http"
1112 "os"
@@ -22,6 +23,9 @@ import (
2223 koios "github.com/cardano-community/koios-go-client/v3"
2324 "github.com/cenkalti/backoff/v4"
2425 "github.com/gorilla/websocket"
26+ "github.com/michimani/gotwi"
27+ "github.com/michimani/gotwi/tweet/managetweet"
28+ "github.com/michimani/gotwi/tweet/managetweet/types"
2529 "github.com/spf13/viper"
2630 telebot "gopkg.in/tucnak/telebot.v2"
2731)
@@ -69,6 +73,9 @@ type Indexer struct {
6973 epoch int
7074 networkMagic int
7175 wg sync.WaitGroup
76+ // Twitter fields
77+ twitterClient * gotwi.Client
78+ twitterEnabled bool
7279}
7380
7481type BlockEvent struct {
@@ -138,6 +145,32 @@ func (i *Indexer) Start() error {
138145 log .Fatalf ("failed to start bot: %s" , err )
139146 }
140147
148+ // Initialize Twitter client if credentials are provided
149+ twitterAPIKey := os .Getenv ("TWITTER_API_KEY" )
150+ twitterAPISecret := os .Getenv ("TWITTER_API_KEY_SECRET" )
151+ twitterAccessToken := os .Getenv ("TWITTER_ACCESS_TOKEN" )
152+ twitterAccessTokenSecret := os .Getenv ("TWITTER_ACCESS_TOKEN_SECRET" )
153+
154+ if twitterAPIKey != "" && twitterAPISecret != "" &&
155+ twitterAccessToken != "" && twitterAccessTokenSecret != "" {
156+ in := & gotwi.NewClientInput {
157+ AuthenticationMethod : gotwi .AuthenMethodOAuth1UserContext ,
158+ OAuthToken : twitterAccessToken ,
159+ OAuthTokenSecret : twitterAccessTokenSecret ,
160+ APIKey : twitterAPIKey ,
161+ APIKeySecret : twitterAPISecret ,
162+ }
163+ i .twitterClient , err = gotwi .NewClient (in )
164+ if err != nil {
165+ log .Printf ("failed to initialize Twitter client: %s" , err )
166+ } else {
167+ i .twitterEnabled = true
168+ log .Println ("Twitter client initialized successfully" )
169+ }
170+ } else {
171+ log .Println ("Twitter credentials not provided, Twitter notifications disabled" )
172+ }
173+
141174 /// Initialize the kois client based on networkMagic number
142175 if i .networkMagic == 1 {
143176 i .koios , e = koios .New (
@@ -357,16 +390,37 @@ func (i *Indexer) handleEvent(event event.Event) error {
357390 timeDiffString , i .epochBlocks , i .totalBlocks ,
358391 blockEvent .Context .BlockNumber , blockEvent .Payload .BlockHash )
359392
393+ // Get duck image URL for both Telegram and Twitter
394+ duckImageURL := getDuckImage ()
395+
360396 // Send the message to the appropriate channel
361397 channelID , err := strconv .ParseInt (i .telegramChannel , 10 , 64 )
362398 if err != nil {
363399 log .Fatalf ("failed to parse telegram channel ID: %s" , err )
364400 }
365- photo := & telebot.Photo {File : telebot .FromURL (getDuckImage () ), Caption : msg }
401+ photo := & telebot.Photo {File : telebot .FromURL (duckImageURL ), Caption : msg }
366402 _ , err = i .bot .Send (& telebot.Chat {ID : channelID }, photo )
367403 if err != nil {
368404 log .Printf ("failed to send Telegram message: %s" , err )
369405 }
406+
407+ // Send tweet if Twitter is enabled (shorter format for 280 char limit)
408+ if i .twitterEnabled {
409+ tweetMsg := fmt .Sprintf (
410+ "🦆 New Block!\n \n " +
411+ "Pool: %s\n " +
412+ "Tx: %d | Size: %.2fKB\n " +
413+ "Epoch: %d | Lifetime: %d\n \n " +
414+ "pooltool.io/realtime/%d" ,
415+ i .poolName , blockEvent .Payload .TransactionCount , blockSizeKB ,
416+ i .epochBlocks , i .totalBlocks ,
417+ blockEvent .Context .BlockNumber )
418+
419+ err = i .sendTweet (tweetMsg , duckImageURL )
420+ if err != nil {
421+ log .Printf ("failed to send tweet: %s" , err )
422+ }
423+ }
370424 }
371425
372426 // Print the received event information
@@ -432,6 +486,47 @@ func getDuckImage() string {
432486 return imageURL
433487}
434488
489+ // Download image from URL to bytes
490+ func downloadImage (imageURL string ) ([]byte , error ) {
491+ resp , err := http .Get (imageURL )
492+ if err != nil {
493+ return nil , fmt .Errorf ("failed to download image: %w" , err )
494+ }
495+ defer resp .Body .Close ()
496+
497+ if resp .StatusCode != http .StatusOK {
498+ return nil , fmt .Errorf ("failed to download image: status %d" , resp .StatusCode )
499+ }
500+
501+ imageData , err := io .ReadAll (resp .Body )
502+ if err != nil {
503+ return nil , fmt .Errorf ("failed to read image data: %w" , err )
504+ }
505+
506+ return imageData , nil
507+ }
508+
509+ // Send tweet with optional image
510+ func (i * Indexer ) sendTweet (text string , imageURL string ) error {
511+ if ! i .twitterEnabled || i .twitterClient == nil {
512+ return nil
513+ }
514+
515+ // Create tweet request
516+ input := & types.CreateInput {
517+ Text : gotwi .String (text ),
518+ }
519+
520+ // Post the tweet
521+ _ , err := managetweet .Create (context .Background (), i .twitterClient , input )
522+ if err != nil {
523+ return fmt .Errorf ("failed to post tweet: %w" , err )
524+ }
525+
526+ log .Println ("Tweet posted successfully" )
527+ return nil
528+ }
529+
435530// Convert to bech32 poolID
436531func convertToBech32 (hash string ) (string , error ) {
437532 bytes , err := hex .DecodeString (hash )
0 commit comments