From f572ee3958c39c73230ca5f192dd8c33aa07a6e9 Mon Sep 17 00:00:00 2001 From: astravexton Date: Wed, 2 Jul 2025 16:40:20 +0100 Subject: [PATCH 01/13] add delete flag, fix deleteRecord --- main.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 29c3282..f59e825 100644 --- a/main.go +++ b/main.go @@ -44,7 +44,8 @@ type handler struct { } var ( - post = flag.String("post", "", "URL to a BlueSky post") + post = flag.String("post", "", "URL to a BlueSky post") + delete = flag.Bool("delete", false, "true/false to delete post") ) func main() { @@ -85,6 +86,19 @@ func main() { handle, _ = bskyClient.ResolveHandle(s[1]) } + if *delete { + r, e := h.bsky.Bluesky.GetTelegramData(s[2]) + if e == "" { + log.Printf("Found post %s in channel %d, deleting", s[2], r.ChannelID) + m := tgbotapi.NewDeleteMessage(r.ChannelID, r.MessageID) + h.tg.Send(m) + h.bsky.Bluesky.DeleteRecord([]string{s[2], s[1], "blue.zio.bsky2tg.post"}) + } else { + log.Printf("Unable to find post %s on PDS", s[2]) + } + return + } + postJSON := bskyClient.Bluesky.FetchPost(handle, s[2]) p, _ := json.Marshal(postJSON.Record) h.ProcessPost(&models.Event{ @@ -196,7 +210,7 @@ func (h *handler) HandleEvent(ctx context.Context, event *models.Event) error { if e == "" { m := tgbotapi.NewDeleteMessage(r.ChannelID, r.MessageID) h.tg.Send(m) - h.bsky.Bluesky.DeleteRecord([]string{event.Commit.RKey, event.Did, event.Commit.Collection}) + h.bsky.Bluesky.DeleteRecord([]string{event.Commit.RKey, event.Did, "blue.zio.bsky2tg.post"}) } } From 21722264d1940f282e7dc5bd29792cb9ba7ebcd1 Mon Sep 17 00:00:00 2001 From: astravexton Date: Thu, 3 Jul 2025 10:24:07 +0100 Subject: [PATCH 02/13] update readme for TG_API_ENDPOINT --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index b61cf15..8ee216b 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,12 @@ BSKY_HANDLE= BSKY_PASSWORD= ``` +If you use a different Telegram bot endpoint, you can set it with + +```properties +TG_API_ENDPOINT=https://api.domain.com/bot%s/%s +``` + To run: ```bash From 4f94ea647cad85894ef52b9d76d2905df8374d29 Mon Sep 17 00:00:00 2001 From: astra Date: Thu, 3 Jul 2025 15:43:05 +0200 Subject: [PATCH 03/13] Update .forgejo/workflows/build.yml --- .forgejo/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index 35e0797..7e3ba3d 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -17,6 +17,8 @@ jobs: - name: Set up Docker Build Push Action uses: https://github.com/docker/build-push-action@v2 with: - tags: git.zio.sh/astra/bsky2tg:latest + tags: + - git.zio.sh/astra/bsky2tg:latest + - git.zio.sh/astra/bsky2tg:${{ github.sha }} push: true load: false \ No newline at end of file From ce0709f72d4f131e70cf2e01c28365d7286b9cbd Mon Sep 17 00:00:00 2001 From: astra Date: Thu, 3 Jul 2025 15:45:10 +0200 Subject: [PATCH 04/13] Update .forgejo/workflows/build.yml --- .forgejo/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index 7e3ba3d..8b5eaef 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -17,8 +17,8 @@ jobs: - name: Set up Docker Build Push Action uses: https://github.com/docker/build-push-action@v2 with: - tags: - - git.zio.sh/astra/bsky2tg:latest - - git.zio.sh/astra/bsky2tg:${{ github.sha }} + tags: | + git.zio.sh/astra/bsky2tg:latest + git.zio.sh/astra/bsky2tg:${{ github.sha }} push: true load: false \ No newline at end of file From be8b787c52389f841c140e4cf82ad54b6f1beadf Mon Sep 17 00:00:00 2001 From: astravexton Date: Thu, 3 Jul 2025 16:10:36 +0100 Subject: [PATCH 05/13] add error message --- main.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main.go b/main.go index f59e825..e6c6b5b 100644 --- a/main.go +++ b/main.go @@ -86,6 +86,10 @@ func main() { handle, _ = bskyClient.ResolveHandle(s[1]) } + if handle != bskyClient.Bluesky.Cfg.DID { + log.Fatal("Unable to send posts from other accounts") + } + if *delete { r, e := h.bsky.Bluesky.GetTelegramData(s[2]) if e == "" { From dbc89e5b95d39ed46c3f703329de5dcbcee2a5dc Mon Sep 17 00:00:00 2001 From: astravexton Date: Thu, 3 Jul 2025 18:10:35 +0100 Subject: [PATCH 06/13] remove test stuff --- main.go | 41 ----------------------------------------- 1 file changed, 41 deletions(-) diff --git a/main.go b/main.go index e6c6b5b..c96a5bd 100644 --- a/main.go +++ b/main.go @@ -139,47 +139,6 @@ func main() { log.Fatalf("failed to create client: %v", err) } - // ------------------------------------------------------------------------------ - // file, err := os.Open("posts.json") - // if err != nil { - // fmt.Printf("Error opening file: %v\n", err) - // return - // } - // defer file.Close() - // byteValue, err := io.ReadAll(file) - // if err != nil { - // fmt.Printf("Error reading file: %v\n", err) - // return - // } - - // var posts = struct { - // Records []struct { - // URI string `json:"uri"` - // CID string `json:"cid"` - // Value *bsky.Post `json:"value"` - // } `json:"records"` - // }{} - - // // 4. Unmarshal (decode) the JSON data into the struct - // err = json.Unmarshal(byteValue, &posts) - // if err != nil { - // fmt.Printf("Error unmarshaling JSON: %v\n", err) - // return - // } - // for _, post := range posts.Records { - // log.Printf("post: %s\n", post.Value.ProcessFacets(h.bsky.Bluesky.FetchAliases())) - // s, _ := json.Marshal(post.Value) - // h.ProcessPost(&models.Event{Did: bskyClient.Bluesky.Cfg.DID, Commit: &models.Commit{ - // Record: s, - // RKey: strings.Split(post.URI, "/")[4], - // CID: post.CID, - // Collection: "app.bsky.feed.post", - // }}) - // time.Sleep(time.Second * 2) - // } - // return - // ------------------------------------------------------------------------------ - cursor := time.Now().UnixMicro() restartCount := 0 loop: From bd8a437f43742e4b14cbc00432ff7de92b80494b Mon Sep 17 00:00:00 2001 From: astravexton Date: Thu, 10 Jul 2025 19:19:49 +0100 Subject: [PATCH 07/13] Change link media to be large --- main.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index c96a5bd..2e9a476 100644 --- a/main.go +++ b/main.go @@ -21,8 +21,6 @@ import ( "git.zio.sh/astra/bsky2tg/bsky" tgbotapi "github.com/OvyFlash/telegram-bot-api" - // apibsky "github.com/bluesky-social/indigo/api/bsky" - "github.com/bluesky-social/jetstream/pkg/client" "github.com/bluesky-social/jetstream/pkg/client/schedulers/sequential" "github.com/bluesky-social/jetstream/pkg/models" @@ -303,7 +301,8 @@ func (h *handler) ProcessPost(event *models.Event) error { URL: fmt.Sprintf("https://bsky.app/profile/%s/post/%s", strings.Split(ps.Embed.Record.URI, "/")[2], strings.Split(ps.Embed.Record.URI, "/")[4]), - PreferSmallMedia: true, + PreferSmallMedia: false, + PreferLargeMedia: true, ShowAboveText: true, } } else { From 1690279d5c39f140abc58e61cd1a266b4fbc4091 Mon Sep 17 00:00:00 2001 From: astravexton Date: Fri, 11 Jul 2025 14:15:09 +0100 Subject: [PATCH 08/13] add support for multiple message IDs --- bsky/bluesky.go | 2 +- main.go | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/bsky/bluesky.go b/bsky/bluesky.go index 38422f7..1ca7bd5 100644 --- a/bsky/bluesky.go +++ b/bsky/bluesky.go @@ -127,7 +127,7 @@ func (bluesky *Bluesky) CheckSessionValid() { type TelegramRecord struct { ChannelID int64 `json:"channel_id"` - MessageID int `json:"message_id"` + MessageID []int `json:"message_id"` Link *Link `json:"link"` Error string `json:"error"` Message string `json:"message"` diff --git a/main.go b/main.go index 2e9a476..acbbc01 100644 --- a/main.go +++ b/main.go @@ -92,8 +92,10 @@ func main() { r, e := h.bsky.Bluesky.GetTelegramData(s[2]) if e == "" { log.Printf("Found post %s in channel %d, deleting", s[2], r.ChannelID) - m := tgbotapi.NewDeleteMessage(r.ChannelID, r.MessageID) - h.tg.Send(m) + for _, msgID := range r.MessageID { + m := tgbotapi.NewDeleteMessage(r.ChannelID, msgID) + h.tg.Send(m) + } h.bsky.Bluesky.DeleteRecord([]string{s[2], s[1], "blue.zio.bsky2tg.post"}) } else { log.Printf("Unable to find post %s on PDS", s[2]) @@ -169,8 +171,10 @@ func (h *handler) HandleEvent(ctx context.Context, event *models.Event) error { bsky.PersistAuthSession(h.bsky.Bluesky.Cfg) r, e := h.bsky.Bluesky.GetTelegramData(event.Commit.RKey) if e == "" { - m := tgbotapi.NewDeleteMessage(r.ChannelID, r.MessageID) - h.tg.Send(m) + for _, msgID := range r.MessageID { + m := tgbotapi.NewDeleteMessage(r.ChannelID, msgID) + h.tg.Send(m) + } h.bsky.Bluesky.DeleteRecord([]string{event.Commit.RKey, event.Did, "blue.zio.bsky2tg.post"}) } } @@ -278,9 +282,13 @@ func (h *handler) ProcessPost(event *models.Event) error { } else { resp, _ := h.tg.SendMediaGroup(tgbotapi.NewMediaGroup(cid, mediaGroup)) uri, cid := getLink(event) + var messageIDs []int + for _, msgID := range resp { + messageIDs = append(messageIDs, msgID.MessageID) + } h.bsky.Bluesky.CommitTelegramResponse(&bsky.TelegramRecord{ ChannelID: resp[0].Chat.ID, - MessageID: resp[0].MessageID, + MessageID: messageIDs, Link: &bsky.Link{ Cid: cid, URI: uri, @@ -312,7 +320,7 @@ func (h *handler) ProcessPost(event *models.Event) error { uri, cid := getLink(event) h.bsky.Bluesky.CommitTelegramResponse(&bsky.TelegramRecord{ ChannelID: resp.Chat.ID, - MessageID: resp.MessageID, + MessageID: []int{resp.MessageID}, Link: &bsky.Link{ Cid: cid, URI: uri, From aff13c04dd869a9d28c3396226ec0c940544572e Mon Sep 17 00:00:00 2001 From: astravexton Date: Fri, 11 Jul 2025 14:32:00 +0100 Subject: [PATCH 09/13] use deleteMessages instead --- main.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/main.go b/main.go index acbbc01..433fcd0 100644 --- a/main.go +++ b/main.go @@ -92,10 +92,8 @@ func main() { r, e := h.bsky.Bluesky.GetTelegramData(s[2]) if e == "" { log.Printf("Found post %s in channel %d, deleting", s[2], r.ChannelID) - for _, msgID := range r.MessageID { - m := tgbotapi.NewDeleteMessage(r.ChannelID, msgID) - h.tg.Send(m) - } + m := tgbotapi.NewDeleteMessages(r.ChannelID, r.MessageID) + h.tg.Send(m) h.bsky.Bluesky.DeleteRecord([]string{s[2], s[1], "blue.zio.bsky2tg.post"}) } else { log.Printf("Unable to find post %s on PDS", s[2]) @@ -171,10 +169,8 @@ func (h *handler) HandleEvent(ctx context.Context, event *models.Event) error { bsky.PersistAuthSession(h.bsky.Bluesky.Cfg) r, e := h.bsky.Bluesky.GetTelegramData(event.Commit.RKey) if e == "" { - for _, msgID := range r.MessageID { - m := tgbotapi.NewDeleteMessage(r.ChannelID, msgID) - h.tg.Send(m) - } + m := tgbotapi.NewDeleteMessages(r.ChannelID, r.MessageID) + h.tg.Send(m) h.bsky.Bluesky.DeleteRecord([]string{event.Commit.RKey, event.Did, "blue.zio.bsky2tg.post"}) } } From dc7382f16288e99bdffe2577638f129db6cc9f56 Mon Sep 17 00:00:00 2001 From: astravexton Date: Sat, 13 Sep 2025 17:45:28 +0100 Subject: [PATCH 10/13] Update parse.go --- bsky/parse.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/bsky/parse.go b/bsky/parse.go index c600af9..a06f131 100644 --- a/bsky/parse.go +++ b/bsky/parse.go @@ -18,42 +18,50 @@ type Post struct { Facets *[]Facets `json:"facets,omitempty"` CreatedAt time.Time `json:"createdAt"` } + type Ref struct { Link string `json:"$link,omitempty"` } + type Thumb struct { Type string `json:"$type,omitempty"` Ref *Ref `json:"ref,omitempty"` MimeType string `json:"mimeType,omitempty"` Size int `json:"size,omitempty"` } + type External struct { URI string `json:"uri,omitempty"` Thumb *Thumb `json:"thumb,omitempty"` Title string `json:"title,omitempty"` Description string `json:"description,omitempty"` } + type Video struct { Type string `json:"$type,omitempty"` Ref *Ref `json:"ref,omitempty"` MimeType string `json:"mimeType,omitempty"` Size int `json:"size,omitempty"` } + type Image struct { Type string `json:"$type,omitempty"` Ref *Ref `json:"ref,omitempty"` MimeType string `json:"mimeType,omitempty"` Size int `json:"size,omitempty"` } + type AspectRatio struct { Width int `json:"width,omitempty"` Height int `json:"height,omitempty"` } + type Images struct { Alt string `json:"alt,omitempty"` Image *Image `json:"image,omitempty"` AspectRatio *AspectRatio `json:"aspectRatio,omitempty"` } + type Media struct { Type string `json:"$type,omitempty"` External *External `json:"external,omitempty"` @@ -61,16 +69,19 @@ type Media struct { Images *[]Images `json:"images,omitempty"` AspectRatio *AspectRatio `json:"aspectRatio,omitempty"` } + type Record struct { Cid string `json:"cid,omitempty"` URI string `json:"uri,omitempty"` } + type PostRecord struct { Type string `json:"$type,omitempty"` Cid string `json:"cid,omitempty"` URI string `json:"uri,omitempty"` Record *Record `json:"record,omitempty"` } + type Embed struct { Type string `json:"$type,omitempty"` Media *Media `json:"media,omitempty"` @@ -79,35 +90,59 @@ type Embed struct { Record *PostRecord `json:"record,omitempty"` External *External `json:"external,omitempty"` } + type Values struct { Val string `json:"val,omitempty"` } + type Labels struct { Type string `json:"$type,omitempty"` Values *[]Values `json:"values,omitempty"` } + type Root struct { Cid string `json:"cid,omitempty"` URI string `json:"uri,omitempty"` } + +func (r *Root) GetDID() string { + return strings.Split(r.URI, "/")[2] +} + +func (r *Root) GetRKey() string { + return strings.Split(r.URI, "/")[4] +} + type Parent struct { Cid string `json:"cid,omitempty"` URI string `json:"uri,omitempty"` } + +func (p *Parent) GetDID() string { + return strings.Split(p.URI, "/")[2] +} + +func (p *Parent) GetRKey() string { + return strings.Split(p.URI, "/")[4] +} + type Reply struct { Root *Root `json:"root,omitempty"` Parent *Parent `json:"parent,omitempty"` } + type Index struct { ByteEnd int `json:"byteEnd,omitempty"` ByteStart int `json:"byteStart,omitempty"` } + type Features struct { Did string `json:"did,omitempty"` URI string `json:"uri,omitempty"` Tag string `json:"tag,omitempty"` Type string `json:"$type,omitempty"` } + type Facets struct { Type string `json:"$type"` Index *Index `json:"index,omitempty"` @@ -118,6 +153,7 @@ type ParsedEmbeds struct { Type string MimeType string Ref string + Cid string URI string Width int64 Height int64 From 5ff08f5acc7f5669e872551fe7407630f2b05677 Mon Sep 17 00:00:00 2001 From: astravexton Date: Sat, 13 Sep 2025 17:46:14 +0100 Subject: [PATCH 11/13] Change quote post to use deer.social for embeds --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 433fcd0..f7ada17 100644 --- a/main.go +++ b/main.go @@ -32,7 +32,7 @@ const ( serverAddr = "wss://jetstream2.us-west.bsky.network/subscribe" // serverAddr = "wss://stream.zio.blue/subscribe" postFormat = "%s\n—\nšŸ¦‹ @%s" - quotePostFormat = "
%s
\nāž”ļø @%s\n—\nšŸ¦‹ @%s" + quotePostFormat = "
%s
\nāž”ļø @%s\n—\nšŸ¦‹ @%s" ) type handler struct { @@ -327,7 +327,7 @@ func (h *handler) ProcessPost(event *models.Event) error { } func buildBlobURL(server string, did string, cid string) string { - return server + "/xrpc/com.atproto.sync.getBlob?did=" + url.QueryEscape(did) + "&cid=" + url.QueryEscape(cid) + return server + "/xrpc/com.atproto.sync.getBlob?did=" + url.QueryEscape(did) + "&cid=" + cid } func getLink(event *models.Event) (string, string) { From acbdd41680a402e1c7bc00711694fa52713d34b6 Mon Sep 17 00:00:00 2001 From: astravexton Date: Sun, 14 Sep 2025 09:55:27 +0100 Subject: [PATCH 12/13] Fix video embed --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index f7ada17..eef0dc9 100644 --- a/main.go +++ b/main.go @@ -32,7 +32,7 @@ const ( serverAddr = "wss://jetstream2.us-west.bsky.network/subscribe" // serverAddr = "wss://stream.zio.blue/subscribe" postFormat = "%s\n—\nšŸ¦‹ @%s" - quotePostFormat = "
%s
\nāž”ļø @%s\n—\nšŸ¦‹ @%s" + quotePostFormat = "
%s
\nāž”ļø @%s\n—\nšŸ¦‹ @%s" ) type handler struct { @@ -302,7 +302,7 @@ func (h *handler) ProcessPost(event *models.Event) error { if ps.IsQuotePost() { m.LinkPreviewOptions = tgbotapi.LinkPreviewOptions{ IsDisabled: false, - URL: fmt.Sprintf("https://bsky.app/profile/%s/post/%s", + URL: fmt.Sprintf("https://fxbsky.app/profile/%s/post/%s", strings.Split(ps.Embed.Record.URI, "/")[2], strings.Split(ps.Embed.Record.URI, "/")[4]), PreferSmallMedia: false, From c4d4e915484c027bfe6a556eb27c03d3a833c486 Mon Sep 17 00:00:00 2001 From: astravexton Date: Tue, 23 Sep 2025 17:51:12 +0100 Subject: [PATCH 13/13] check if post is already in channel --- main.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index eef0dc9..2f4ac7e 100644 --- a/main.go +++ b/main.go @@ -88,11 +88,11 @@ func main() { log.Fatal("Unable to send posts from other accounts") } + tgpost, tgposterr := h.bsky.Bluesky.GetTelegramData(s[2]) if *delete { - r, e := h.bsky.Bluesky.GetTelegramData(s[2]) - if e == "" { - log.Printf("Found post %s in channel %d, deleting", s[2], r.ChannelID) - m := tgbotapi.NewDeleteMessages(r.ChannelID, r.MessageID) + if tgposterr == "" { + log.Printf("Found post %s in channel %d, deleting", s[2], tgpost.ChannelID) + m := tgbotapi.NewDeleteMessages(tgpost.ChannelID, tgpost.MessageID) h.tg.Send(m) h.bsky.Bluesky.DeleteRecord([]string{s[2], s[1], "blue.zio.bsky2tg.post"}) } else { @@ -101,6 +101,11 @@ func main() { return } + if tgpost.ChannelID != 0 { + log.Printf("Post %s already sent to channel %d, exiting", s[2], tgpost.ChannelID) + return + } + postJSON := bskyClient.Bluesky.FetchPost(handle, s[2]) p, _ := json.Marshal(postJSON.Record) h.ProcessPost(&models.Event{