Compare commits

...
Sign in to create a new pull request.

14 commits
post ... main

Author SHA1 Message Date
c4d4e91548 check if post is already in channel
All checks were successful
/ build (push) Successful in 59s
2025-09-23 17:51:12 +01:00
acbdd41680 Fix video embed
All checks were successful
/ build (push) Successful in 1m11s
2025-09-14 09:55:27 +01:00
5ff08f5acc Change quote post to use deer.social for embeds
All checks were successful
/ build (push) Successful in 1m58s
2025-09-13 17:46:14 +01:00
dc7382f162 Update parse.go 2025-09-13 17:45:28 +01:00
aff13c04dd use deleteMessages instead
All checks were successful
/ build (push) Successful in 52s
2025-07-11 14:32:00 +01:00
1690279d5c add support for multiple message IDs
All checks were successful
/ build (push) Successful in 1m25s
2025-07-11 14:15:09 +01:00
bd8a437f43 Change link media to be large
All checks were successful
/ build (push) Successful in 1m3s
2025-07-10 19:19:49 +01:00
dbc89e5b95 remove test stuff 2025-07-03 18:10:35 +01:00
be8b787c52 add error message
All checks were successful
/ build (push) Successful in 1m13s
2025-07-03 16:10:36 +01:00
ce0709f72d Update .forgejo/workflows/build.yml
All checks were successful
/ build (push) Successful in 53s
2025-07-03 15:45:10 +02:00
4f94ea647c Update .forgejo/workflows/build.yml 2025-07-03 15:43:05 +02:00
21722264d1 update readme for TG_API_ENDPOINT
All checks were successful
/ build (push) Successful in 56s
2025-07-03 10:24:07 +01:00
f572ee3958 add delete flag, fix deleteRecord
All checks were successful
/ build (push) Successful in 56s
2025-07-02 16:40:20 +01:00
bf2b621171 Merge pull request 'add support for links as posts' (#2) from post into main
All checks were successful
/ build (push) Successful in 55s
Reviewed-on: #2
2025-07-01 08:09:08 +02:00
5 changed files with 82 additions and 53 deletions

View file

@ -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

View file

@ -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

View file

@ -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"`

View file

@ -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

87
main.go
View file

@ -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"
@ -44,7 +42,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 +84,28 @@ func main() {
handle, _ = bskyClient.ResolveHandle(s[1])
}
if handle != bskyClient.Bluesky.Cfg.DID {
log.Fatal("Unable to send posts from other accounts")
}
tgpost, tgposterr := h.bsky.Bluesky.GetTelegramData(s[2])
if *delete {
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 {
log.Printf("Unable to find post %s on PDS", s[2])
}
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{
@ -121,47 +142,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:
@ -194,9 +174,9 @@ 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)
m := tgbotapi.NewDeleteMessages(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"})
}
}
@ -303,9 +283,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,
@ -323,10 +307,11 @@ 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: true,
PreferSmallMedia: false,
PreferLargeMedia: true,
ShowAboveText: true,
}
} else {
@ -336,7 +321,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,
@ -347,7 +332,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) {