Compare commits
14 commits
Author | SHA1 | Date | |
---|---|---|---|
c4d4e91548 | |||
acbdd41680 | |||
5ff08f5acc | |||
dc7382f162 | |||
aff13c04dd | |||
1690279d5c | |||
bd8a437f43 | |||
dbc89e5b95 | |||
be8b787c52 | |||
ce0709f72d | |||
4f94ea647c | |||
21722264d1 | |||
f572ee3958 | |||
bf2b621171 |
5 changed files with 70 additions and 55 deletions
|
@ -17,6 +17,8 @@ jobs:
|
||||||
- name: Set up Docker Build Push Action
|
- name: Set up Docker Build Push Action
|
||||||
uses: https://github.com/docker/build-push-action@v2
|
uses: https://github.com/docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
tags: git.zio.sh/astra/bsky2tg:latest
|
tags: |
|
||||||
|
git.zio.sh/astra/bsky2tg:latest
|
||||||
|
git.zio.sh/astra/bsky2tg:${{ github.sha }}
|
||||||
push: true
|
push: true
|
||||||
load: false
|
load: false
|
|
@ -16,6 +16,12 @@ BSKY_HANDLE=
|
||||||
BSKY_PASSWORD=
|
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:
|
To run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
@ -127,7 +127,7 @@ func (bluesky *Bluesky) CheckSessionValid() {
|
||||||
|
|
||||||
type TelegramRecord struct {
|
type TelegramRecord struct {
|
||||||
ChannelID int64 `json:"channel_id"`
|
ChannelID int64 `json:"channel_id"`
|
||||||
MessageID int `json:"message_id"`
|
MessageID []int `json:"message_id"`
|
||||||
Link *Link `json:"link"`
|
Link *Link `json:"link"`
|
||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
|
|
|
@ -18,42 +18,50 @@ type Post struct {
|
||||||
Facets *[]Facets `json:"facets,omitempty"`
|
Facets *[]Facets `json:"facets,omitempty"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ref struct {
|
type Ref struct {
|
||||||
Link string `json:"$link,omitempty"`
|
Link string `json:"$link,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Thumb struct {
|
type Thumb struct {
|
||||||
Type string `json:"$type,omitempty"`
|
Type string `json:"$type,omitempty"`
|
||||||
Ref *Ref `json:"ref,omitempty"`
|
Ref *Ref `json:"ref,omitempty"`
|
||||||
MimeType string `json:"mimeType,omitempty"`
|
MimeType string `json:"mimeType,omitempty"`
|
||||||
Size int `json:"size,omitempty"`
|
Size int `json:"size,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type External struct {
|
type External struct {
|
||||||
URI string `json:"uri,omitempty"`
|
URI string `json:"uri,omitempty"`
|
||||||
Thumb *Thumb `json:"thumb,omitempty"`
|
Thumb *Thumb `json:"thumb,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Video struct {
|
type Video struct {
|
||||||
Type string `json:"$type,omitempty"`
|
Type string `json:"$type,omitempty"`
|
||||||
Ref *Ref `json:"ref,omitempty"`
|
Ref *Ref `json:"ref,omitempty"`
|
||||||
MimeType string `json:"mimeType,omitempty"`
|
MimeType string `json:"mimeType,omitempty"`
|
||||||
Size int `json:"size,omitempty"`
|
Size int `json:"size,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Image struct {
|
type Image struct {
|
||||||
Type string `json:"$type,omitempty"`
|
Type string `json:"$type,omitempty"`
|
||||||
Ref *Ref `json:"ref,omitempty"`
|
Ref *Ref `json:"ref,omitempty"`
|
||||||
MimeType string `json:"mimeType,omitempty"`
|
MimeType string `json:"mimeType,omitempty"`
|
||||||
Size int `json:"size,omitempty"`
|
Size int `json:"size,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AspectRatio struct {
|
type AspectRatio struct {
|
||||||
Width int `json:"width,omitempty"`
|
Width int `json:"width,omitempty"`
|
||||||
Height int `json:"height,omitempty"`
|
Height int `json:"height,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Images struct {
|
type Images struct {
|
||||||
Alt string `json:"alt,omitempty"`
|
Alt string `json:"alt,omitempty"`
|
||||||
Image *Image `json:"image,omitempty"`
|
Image *Image `json:"image,omitempty"`
|
||||||
AspectRatio *AspectRatio `json:"aspectRatio,omitempty"`
|
AspectRatio *AspectRatio `json:"aspectRatio,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Media struct {
|
type Media struct {
|
||||||
Type string `json:"$type,omitempty"`
|
Type string `json:"$type,omitempty"`
|
||||||
External *External `json:"external,omitempty"`
|
External *External `json:"external,omitempty"`
|
||||||
|
@ -61,16 +69,19 @@ type Media struct {
|
||||||
Images *[]Images `json:"images,omitempty"`
|
Images *[]Images `json:"images,omitempty"`
|
||||||
AspectRatio *AspectRatio `json:"aspectRatio,omitempty"`
|
AspectRatio *AspectRatio `json:"aspectRatio,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Record struct {
|
type Record struct {
|
||||||
Cid string `json:"cid,omitempty"`
|
Cid string `json:"cid,omitempty"`
|
||||||
URI string `json:"uri,omitempty"`
|
URI string `json:"uri,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostRecord struct {
|
type PostRecord struct {
|
||||||
Type string `json:"$type,omitempty"`
|
Type string `json:"$type,omitempty"`
|
||||||
Cid string `json:"cid,omitempty"`
|
Cid string `json:"cid,omitempty"`
|
||||||
URI string `json:"uri,omitempty"`
|
URI string `json:"uri,omitempty"`
|
||||||
Record *Record `json:"record,omitempty"`
|
Record *Record `json:"record,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Embed struct {
|
type Embed struct {
|
||||||
Type string `json:"$type,omitempty"`
|
Type string `json:"$type,omitempty"`
|
||||||
Media *Media `json:"media,omitempty"`
|
Media *Media `json:"media,omitempty"`
|
||||||
|
@ -79,35 +90,59 @@ type Embed struct {
|
||||||
Record *PostRecord `json:"record,omitempty"`
|
Record *PostRecord `json:"record,omitempty"`
|
||||||
External *External `json:"external,omitempty"`
|
External *External `json:"external,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Values struct {
|
type Values struct {
|
||||||
Val string `json:"val,omitempty"`
|
Val string `json:"val,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Labels struct {
|
type Labels struct {
|
||||||
Type string `json:"$type,omitempty"`
|
Type string `json:"$type,omitempty"`
|
||||||
Values *[]Values `json:"values,omitempty"`
|
Values *[]Values `json:"values,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Root struct {
|
type Root struct {
|
||||||
Cid string `json:"cid,omitempty"`
|
Cid string `json:"cid,omitempty"`
|
||||||
URI string `json:"uri,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 {
|
type Parent struct {
|
||||||
Cid string `json:"cid,omitempty"`
|
Cid string `json:"cid,omitempty"`
|
||||||
URI string `json:"uri,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 {
|
type Reply struct {
|
||||||
Root *Root `json:"root,omitempty"`
|
Root *Root `json:"root,omitempty"`
|
||||||
Parent *Parent `json:"parent,omitempty"`
|
Parent *Parent `json:"parent,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Index struct {
|
type Index struct {
|
||||||
ByteEnd int `json:"byteEnd,omitempty"`
|
ByteEnd int `json:"byteEnd,omitempty"`
|
||||||
ByteStart int `json:"byteStart,omitempty"`
|
ByteStart int `json:"byteStart,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Features struct {
|
type Features struct {
|
||||||
Did string `json:"did,omitempty"`
|
Did string `json:"did,omitempty"`
|
||||||
URI string `json:"uri,omitempty"`
|
URI string `json:"uri,omitempty"`
|
||||||
Tag string `json:"tag,omitempty"`
|
Tag string `json:"tag,omitempty"`
|
||||||
Type string `json:"$type,omitempty"`
|
Type string `json:"$type,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Facets struct {
|
type Facets struct {
|
||||||
Type string `json:"$type"`
|
Type string `json:"$type"`
|
||||||
Index *Index `json:"index,omitempty"`
|
Index *Index `json:"index,omitempty"`
|
||||||
|
@ -118,6 +153,7 @@ type ParsedEmbeds struct {
|
||||||
Type string
|
Type string
|
||||||
MimeType string
|
MimeType string
|
||||||
Ref string
|
Ref string
|
||||||
|
Cid string
|
||||||
URI string
|
URI string
|
||||||
Width int64
|
Width int64
|
||||||
Height int64
|
Height int64
|
||||||
|
|
77
main.go
77
main.go
|
@ -21,8 +21,6 @@ import (
|
||||||
"git.zio.sh/astra/bsky2tg/bsky"
|
"git.zio.sh/astra/bsky2tg/bsky"
|
||||||
tgbotapi "github.com/OvyFlash/telegram-bot-api"
|
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"
|
||||||
"github.com/bluesky-social/jetstream/pkg/client/schedulers/sequential"
|
"github.com/bluesky-social/jetstream/pkg/client/schedulers/sequential"
|
||||||
"github.com/bluesky-social/jetstream/pkg/models"
|
"github.com/bluesky-social/jetstream/pkg/models"
|
||||||
|
@ -86,11 +84,15 @@ func main() {
|
||||||
handle, _ = bskyClient.ResolveHandle(s[1])
|
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 *delete {
|
||||||
r, e := h.bsky.Bluesky.GetTelegramData(s[2])
|
if tgposterr == "" {
|
||||||
if e == "" {
|
log.Printf("Found post %s in channel %d, deleting", s[2], tgpost.ChannelID)
|
||||||
log.Printf("Found post %s in channel %d, deleting", s[2], r.ChannelID)
|
m := tgbotapi.NewDeleteMessages(tgpost.ChannelID, tgpost.MessageID)
|
||||||
m := tgbotapi.NewDeleteMessage(r.ChannelID, r.MessageID)
|
|
||||||
h.tg.Send(m)
|
h.tg.Send(m)
|
||||||
h.bsky.Bluesky.DeleteRecord([]string{s[2], s[1], "blue.zio.bsky2tg.post"})
|
h.bsky.Bluesky.DeleteRecord([]string{s[2], s[1], "blue.zio.bsky2tg.post"})
|
||||||
} else {
|
} else {
|
||||||
|
@ -99,6 +101,11 @@ func main() {
|
||||||
return
|
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])
|
postJSON := bskyClient.Bluesky.FetchPost(handle, s[2])
|
||||||
p, _ := json.Marshal(postJSON.Record)
|
p, _ := json.Marshal(postJSON.Record)
|
||||||
h.ProcessPost(&models.Event{
|
h.ProcessPost(&models.Event{
|
||||||
|
@ -135,47 +142,6 @@ func main() {
|
||||||
log.Fatalf("failed to create client: %v", err)
|
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()
|
cursor := time.Now().UnixMicro()
|
||||||
restartCount := 0
|
restartCount := 0
|
||||||
loop:
|
loop:
|
||||||
|
@ -208,7 +174,7 @@ func (h *handler) HandleEvent(ctx context.Context, event *models.Event) error {
|
||||||
bsky.PersistAuthSession(h.bsky.Bluesky.Cfg)
|
bsky.PersistAuthSession(h.bsky.Bluesky.Cfg)
|
||||||
r, e := h.bsky.Bluesky.GetTelegramData(event.Commit.RKey)
|
r, e := h.bsky.Bluesky.GetTelegramData(event.Commit.RKey)
|
||||||
if e == "" {
|
if e == "" {
|
||||||
m := tgbotapi.NewDeleteMessage(r.ChannelID, r.MessageID)
|
m := tgbotapi.NewDeleteMessages(r.ChannelID, r.MessageID)
|
||||||
h.tg.Send(m)
|
h.tg.Send(m)
|
||||||
h.bsky.Bluesky.DeleteRecord([]string{event.Commit.RKey, event.Did, "blue.zio.bsky2tg.post"})
|
h.bsky.Bluesky.DeleteRecord([]string{event.Commit.RKey, event.Did, "blue.zio.bsky2tg.post"})
|
||||||
}
|
}
|
||||||
|
@ -317,9 +283,13 @@ func (h *handler) ProcessPost(event *models.Event) error {
|
||||||
} else {
|
} else {
|
||||||
resp, _ := h.tg.SendMediaGroup(tgbotapi.NewMediaGroup(cid, mediaGroup))
|
resp, _ := h.tg.SendMediaGroup(tgbotapi.NewMediaGroup(cid, mediaGroup))
|
||||||
uri, cid := getLink(event)
|
uri, cid := getLink(event)
|
||||||
|
var messageIDs []int
|
||||||
|
for _, msgID := range resp {
|
||||||
|
messageIDs = append(messageIDs, msgID.MessageID)
|
||||||
|
}
|
||||||
h.bsky.Bluesky.CommitTelegramResponse(&bsky.TelegramRecord{
|
h.bsky.Bluesky.CommitTelegramResponse(&bsky.TelegramRecord{
|
||||||
ChannelID: resp[0].Chat.ID,
|
ChannelID: resp[0].Chat.ID,
|
||||||
MessageID: resp[0].MessageID,
|
MessageID: messageIDs,
|
||||||
Link: &bsky.Link{
|
Link: &bsky.Link{
|
||||||
Cid: cid,
|
Cid: cid,
|
||||||
URI: uri,
|
URI: uri,
|
||||||
|
@ -337,10 +307,11 @@ func (h *handler) ProcessPost(event *models.Event) error {
|
||||||
if ps.IsQuotePost() {
|
if ps.IsQuotePost() {
|
||||||
m.LinkPreviewOptions = tgbotapi.LinkPreviewOptions{
|
m.LinkPreviewOptions = tgbotapi.LinkPreviewOptions{
|
||||||
IsDisabled: false,
|
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, "/")[2],
|
||||||
strings.Split(ps.Embed.Record.URI, "/")[4]),
|
strings.Split(ps.Embed.Record.URI, "/")[4]),
|
||||||
PreferSmallMedia: true,
|
PreferSmallMedia: false,
|
||||||
|
PreferLargeMedia: true,
|
||||||
ShowAboveText: true,
|
ShowAboveText: true,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -350,7 +321,7 @@ func (h *handler) ProcessPost(event *models.Event) error {
|
||||||
uri, cid := getLink(event)
|
uri, cid := getLink(event)
|
||||||
h.bsky.Bluesky.CommitTelegramResponse(&bsky.TelegramRecord{
|
h.bsky.Bluesky.CommitTelegramResponse(&bsky.TelegramRecord{
|
||||||
ChannelID: resp.Chat.ID,
|
ChannelID: resp.Chat.ID,
|
||||||
MessageID: resp.MessageID,
|
MessageID: []int{resp.MessageID},
|
||||||
Link: &bsky.Link{
|
Link: &bsky.Link{
|
||||||
Cid: cid,
|
Cid: cid,
|
||||||
URI: uri,
|
URI: uri,
|
||||||
|
@ -361,7 +332,7 @@ func (h *handler) ProcessPost(event *models.Event) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildBlobURL(server string, did string, cid string) string {
|
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) {
|
func getLink(event *models.Event) (string, string) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue