Compare commits

..

4 commits

Author SHA1 Message Date
c9cc325ef7 Code Refactor
All checks were successful
/ build (push) Successful in 2m43s
2026-03-11 13:58:38 +00:00
85fc508aa3 Update README.md, add LICENSE 2026-03-11 13:58:29 +00:00
02cef12523 RefreshSession code changes 2026-03-11 12:14:42 +00:00
86720ce988 Handle auth error 2026-03-11 12:05:21 +00:00
5 changed files with 276 additions and 86 deletions

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 astra.blue
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.

188
README.md
View file

@ -1,50 +1,188 @@
bsky2tg # bsky2tg
=======
**bsky2tg** will mirror posts from your Bluesky account to a Telegram channel through a bot. It supports creation and deletion of posts on Bluesky but not the other way. A real-time bridge that forwards Bluesky posts to Telegram. Monitor your Bluesky account and automatically send posts to a Telegram channel with full media support, quote posts, and more.
--- ## Features
### Usage - 🦋 **Real-time sync** - Posts appear on Telegram seconds after posting on Bluesky
- 📸 **Full media support** - Images, videos, GIFs (from Tenor)
- 💬 **Quote posts** - Properly formatted with links to original posts
- ✏️ **Edit support** - Updates Telegram message when you edit a Bluesky post
- 🗑️ **Delete sync** - Removes from Telegram when you delete from Bluesky
- 🔗 **Rich links** - @mentions, hashtags, and custom aliases converted to clickable links
- ⏰ **Time filtering** - Ignore old posts and replies if desired
- 🎬 **Video metadata** - Includes duration, dimensions, and thumbnail
Create a `.env` file with the following: ## Setup
```properties ### Prerequisites
TG_TOKEN=
TG_CHANNEL_ID=
BSKY_HANDLE=
BSKY_PASSWORD=
```
If you use a different Telegram bot endpoint, you can set it with - Go 1.21+
- A Bluesky account
- A Telegram bot and channel
```properties ### Installation
TG_API_ENDPOINT=https://api.domain.com/bot%s/%s
```
# Podman 1. **Clone the repository**
```bash
git clone https://git.zio.sh/astra/bsky2tg
cd bsky2tg
```
2. **Build the project**
```bash
go build
```
3. **Set environment variables**
```bash
export BSKY_HANDLE="your.bsky.handle"
export BSKY_PASSWORD="your-app-password" # NOT your main password
export TG_TOKEN="your-telegram-bot-token"
export TG_CHANNEL_ID="your-channel-id"
```
**Optional:**
```bash
export TG_API_ENDPOINT="https://api.telegram.org/bot%s/%s" # Custom Telegram API endpoint
export OLDPOSTTIME="1" # Ignore posts older than this many hours (default: 1)
```
4. **Run the daemon**
```bash
./bsky2tg
```
## Running with Podman
Run the bot in a container using Podman:
### With `.env` file
```bash ```bash
podman run -it --name bsky2tg_<profile> \ podman run -it --name bsky2tg_<profile> \
--env-file /path/to/.env \ --env-file /path/to/.env \
git.zio.sh/astra/bsky2tg:latest git.zio.sh/astra/bsky2tg:latest
``` ```
Or without `.env` file: ### With environment variables
```bash ```bash
podman run -it --name bsky2tg_<profile> \ podman run -it --name bsky2tg_<profile> \
--env TG_TOKEN= \ --env TG_TOKEN=<your-token> \
--env TG_CHANNEL_ID= \ --env TG_CHANNEL_ID=<your-channel-id> \
--env BSKY_HANDLE= \ --env BSKY_HANDLE=<your.handle> \
--env BSKY_PASSWORD= \ --env BSKY_PASSWORD=<your-app-password> \
git.zio.sh/astra/bsky2tg:latest git.zio.sh/astra/bsky2tg:latest
``` ```
### Getting Your Credentials
## Bash **Bluesky App Password:**
- Go to Settings → Privacy and Security → App Passwords
- Create a new app password (NOT your main Bluesky password)
**Telegram Bot Token:**
- Message [@BotFather](https://t.me/BotFather) on Telegram
- Create a new bot with `/newbot`
- Copy the token
**Telegram Channel ID:**
- Create a channel (can also be private)
- Add your bot as an admin
- Use `@userinfobot` to get the channel ID
## Usage
### Daemon Mode
The bot runs continuously and syncs new posts in real-time:
```bash ```bash
source .env
./bsky2tg ./bsky2tg
``` ```
### One-Shot Post Sync
Send a specific post to Telegram:
```bash
./bsky2tg -post "https://bsky.app/profile/user.bsky/post/abc123"
```
### Delete a Post
Remove a post from Telegram (delete from Bluesky first):
```bash
./bsky2tg -post "https://bsky.app/profile/user.bsky/post/abc123" -delete
```
### Ignore Old Posts
Ignore posts created more than 2 hours ago:
```bash
./bsky2tg -oldposttime 2
```
## How It Works
1. **Authentication** - Logs into Bluesky via ATProto and stores the session
2. **Jetstream Connection** - Subscribes to real-time post events from your account
3. **Post Processing** - Parses posts, extracts media, processes facets (links/mentions)
4. **Telegram Delivery** - Sends formatted messages with media to your channel
5. **Metadata Storage** - Records post mapping (Bluesky → Telegram) for edits/deletes
## Configuration
### Post Format
Posts are sent with this format:
```
[Post text with @mentions and #hashtags]
🦋 @your.handle
```
Quote posts include the quoted post above in a blockquote.
### Custom Aliases
You can set up custom link replacements by creating entries in the `blue.zio.bsky2tg.alias` collection on your PDS.
## Troubleshooting
### Auth errors
- Verify `BSKY_HANDLE` and `BSKY_PASSWORD` are correct
- Use an app password, not your main Bluesky password
- Check `auth-session.json` file permissions
### Posts not syncing
- Ensure the bot is admin in the channel
- Check `TG_CHANNEL_ID` is correct
- Verify Jetstream connection with logs
### Video errors
- FFmpeg must be installed for video processing
- Check that video file can be read
## Project Structure
```
.
├── main.go # Event handler, post processing, Telegram sender
├── bsky/
│ ├── client.go # Bluesky session management, handle resolution
│ ├── bluesky.go # ATProto API calls (posts, records, sessions)
│ └── parse.go # Post parsing, facet processing
├── auth-session.json # Stored auth session (auto-created)
└── README.md # This file
```
## API Integration
- **Bluesky ATProto** - Session creation, post fetching, record management
- **Jetstream** - Real-time firehose subscription
- **Telegram Bot API** - Message/media sending, editing, deleting
## Notes
- Auth sessions are persisted in `auth-session.json`
- Tokens are automatically refreshed when expired
- Posts are deduplicated to prevent duplicates on sync restart
- Media is fetched from your PDS via blob endpoints
## License
See LICENSE file

View file

@ -10,6 +10,17 @@ import (
"github.com/dghubble/sling" "github.com/dghubble/sling"
) )
const (
// URI parsing indices for at:// URIs split by "/"
uriRepoIndex = 2
uriCollectionIndex = 3
uriRkeyIndex = 4
// Custom collections
PostCollection = "blue.zio.bsky2tg.post"
AliasCollection = "blue.zio.bsky2tg.alias"
)
type BlueskyConfig struct { type BlueskyConfig struct {
PDSURL string `json:"pds-url"` PDSURL string `json:"pds-url"`
Repo string `json:"repo"` Repo string `json:"repo"`
@ -76,6 +87,7 @@ type Bluesky struct {
HttpClient *http.Client HttpClient *http.Client
Logger *log.Logger Logger *log.Logger
sling *sling.Sling sling *sling.Sling
publicSling *sling.Sling
} }
func (bluesky *Bluesky) CreateSession(cfg *BlueskyConfig) error { func (bluesky *Bluesky) CreateSession(cfg *BlueskyConfig) error {
@ -88,18 +100,18 @@ func (bluesky *Bluesky) CreateSession(cfg *BlueskyConfig) error {
} }
resp := new(BSkySessionResponse) resp := new(BSkySessionResponse)
bluesky.sling.New().Post("/xrpc/com.atproto.server.createSession").BodyJSON(body).ReceiveSuccess(resp) bluesky.sling.New().Client(bluesky.HttpClient).
Post("/xrpc/com.atproto.server.createSession").BodyJSON(body).ReceiveSuccess(resp)
if resp.AccessJWT != "" { if resp.AccessJWT != "" {
cfg.AccessJWT = resp.AccessJWT cfg.AccessJWT = resp.AccessJWT
cfg.RefreshJWT = resp.RefreshJWT cfg.RefreshJWT = resp.RefreshJWT
return nil return nil
} }
bluesky.sling.New().Set("Authorization", fmt.Sprintf("Bearer %s", bluesky.Cfg.AccessJWT))
return errors.New("unable to authenticate, check handle/password") return errors.New("unable to authenticate, check handle/password")
} }
func (bluesky *Bluesky) RefreshSession() error { func (bluesky *Bluesky) RefreshSession() {
resp := new(BSkySessionResponse) resp := new(BSkySessionResponse)
bluesky.sling.New().Set("Authorization", fmt.Sprintf("Bearer %s", bluesky.Cfg.RefreshJWT)). bluesky.sling.New().Set("Authorization", fmt.Sprintf("Bearer %s", bluesky.Cfg.RefreshJWT)).
@ -109,10 +121,13 @@ func (bluesky *Bluesky) RefreshSession() error {
bluesky.Cfg.RefreshJWT = resp.RefreshJWT bluesky.Cfg.RefreshJWT = resp.RefreshJWT
PersistAuthSession(bluesky.Cfg) PersistAuthSession(bluesky.Cfg)
bluesky.sling.Set("Authorization", fmt.Sprintf("Bearer %s", bluesky.Cfg.AccessJWT)) bluesky.sling.Set("Authorization", fmt.Sprintf("Bearer %s", bluesky.Cfg.AccessJWT))
return nil return
}
if resp.Error != "" {
log.Fatalf("RefreshSession error: %s", resp.Message)
} }
return bluesky.CreateSession(bluesky.Cfg) bluesky.CreateSession(bluesky.Cfg)
} }
func (bluesky *Bluesky) CheckSessionValid() { func (bluesky *Bluesky) CheckSessionValid() {
@ -126,7 +141,7 @@ func (bluesky *Bluesky) CheckSessionValid() {
bluesky.sling.New().Set("Authorization", fmt.Sprintf("Bearer %s", bluesky.Cfg.AccessJWT)). bluesky.sling.New().Set("Authorization", fmt.Sprintf("Bearer %s", bluesky.Cfg.AccessJWT)).
Get("/xrpc/app.bsky.actor.getProfile").QueryStruct(params).Receive(resp, resp) Get("/xrpc/app.bsky.actor.getProfile").QueryStruct(params).Receive(resp, resp)
if resp.Error == "ExpiredToken" { if resp.Error != "" {
bluesky.RefreshSession() bluesky.RefreshSession()
} }
} }
@ -158,7 +173,7 @@ func (bluesky *Bluesky) CommitTelegramResponse(data *TelegramRecord, rkey string
Record TelegramRecord `json:"record"` Record TelegramRecord `json:"record"`
}{ }{
Repo: bluesky.Cfg.DID, Repo: bluesky.Cfg.DID,
Collection: "blue.zio.bsky2tg.post", Collection: PostCollection,
RKey: rkey, RKey: rkey,
Record: TelegramRecord{ Record: TelegramRecord{
ChannelID: data.ChannelID, ChannelID: data.ChannelID,
@ -190,7 +205,7 @@ func (bluesky *Bluesky) GetTelegramData(rkey string) (*TelegramRecord, string) {
RKey string `url:"rkey"` RKey string `url:"rkey"`
}{ }{
Repo: bluesky.Cfg.DID, Repo: bluesky.Cfg.DID,
Collection: "blue.zio.bsky2tg.post", Collection: PostCollection,
RKey: rkey, RKey: rkey,
} }
@ -212,9 +227,9 @@ func (bluesky *Bluesky) GetPost(uri string) *Post {
Repo string `url:"repo"` Repo string `url:"repo"`
Collection string `url:"collection"` Collection string `url:"collection"`
}{ }{
RKey: args[4], RKey: args[uriRkeyIndex],
Repo: args[2], Repo: args[uriRepoIndex],
Collection: args[3], Collection: args[uriCollectionIndex],
} }
bluesky.sling.New().Get("/xrpc/com.atproto.repo.getRecord").QueryStruct(params).ReceiveSuccess(&post) bluesky.sling.New().Get("/xrpc/com.atproto.repo.getRecord").QueryStruct(params).ReceiveSuccess(&post)
@ -246,7 +261,7 @@ func (bluesky *Bluesky) FetchAliases() []Records {
Collection string `url:"collection"` Collection string `url:"collection"`
}{ }{
Repo: bluesky.Cfg.DID, Repo: bluesky.Cfg.DID,
Collection: "blue.zio.bsky2tg.alias", Collection: AliasCollection,
} }
bluesky.sling.New().Get("/xrpc/com.atproto.repo.listRecords").QueryStruct(&params).Receive(resp, resp) bluesky.sling.New().Get("/xrpc/com.atproto.repo.listRecords").QueryStruct(&params).Receive(resp, resp)
@ -279,7 +294,6 @@ func (bluesky *Bluesky) FetchPost(did string, rkey string) FetchedPost {
}{ }{
URIs: fmt.Sprintf("at://%s/app.bsky.feed.post/%s", did, rkey), URIs: fmt.Sprintf("at://%s/app.bsky.feed.post/%s", did, rkey),
} }
bluesky.sling.New().Base("https://public.api.bsky.app"). bluesky.sling.New().Get("/xrpc/app.bsky.feed.getPosts").QueryStruct(&params).Receive(resp, resp)
Get("/xrpc/app.bsky.feed.getPosts").QueryStruct(&params).Receive(resp, resp)
return resp.Posts[0] return resp.Posts[0]
} }

View file

@ -13,9 +13,14 @@ import (
"github.com/dghubble/sling" "github.com/dghubble/sling"
) )
const (
didWebPrefixLen = len("did:web:")
atPrefixLen = len("at://")
httpClientTimeout = 3 * time.Second
)
type BSky struct { type BSky struct {
Bluesky *Bluesky Bluesky *Bluesky
DID string
} }
func NewBSky() *BSky { func NewBSky() *BSky {
@ -23,13 +28,13 @@ func NewBSky() *BSky {
Bluesky: &Bluesky{ Bluesky: &Bluesky{
Cfg: &BlueskyConfig{}, Cfg: &BlueskyConfig{},
HttpClient: &http.Client{}, HttpClient: &http.Client{},
sling: sling.New().Client(&http.Client{Timeout: time.Second * 3}), sling: sling.New().Client(&http.Client{Timeout: httpClientTimeout}),
publicSling: sling.New().Base("https://public.api.bsky.app/").Client(&http.Client{Timeout: httpClientTimeout}),
}, },
} }
} }
func (b *BSky) ResolveHandle(handle string) (string, error) { func (b *BSky) ResolveHandle(handle string) (string, error) {
httpClient := &http.Client{Timeout: 3 * time.Second}
resp := new(BSkySessionResponse) resp := new(BSkySessionResponse)
errResp := &struct { errResp := &struct {
Message string `json:"message"` Message string `json:"message"`
@ -40,8 +45,7 @@ func (b *BSky) ResolveHandle(handle string) (string, error) {
}{ }{
Handle: handle, Handle: handle,
} }
sling.New().Base("https://public.api.bsky.app/").Client(httpClient). b.Bluesky.publicSling.New().Get("/xrpc/com.atproto.identity.resolveHandle").QueryStruct(params).
Get("/xrpc/com.atproto.identity.resolveHandle").QueryStruct(params).
Receive(resp, errResp) Receive(resp, errResp)
if errResp.Error != "" { if errResp.Error != "" {
@ -51,62 +55,70 @@ func (b *BSky) ResolveHandle(handle string) (string, error) {
return resp.DID, nil return resp.DID, nil
} }
func parseDIDURL(did string) (*url.URL, error) {
if strings.HasPrefix(did, "did:web:") {
return url.Parse("https://" + did[didWebPrefixLen:] + "/.well-known/did.json")
} else if strings.HasPrefix(did, "did:plc:") {
return url.Parse("https://plc.directory/" + did)
}
return nil, errors.New("DID is not supported")
}
func (b *BSky) getPDS() error { func (b *BSky) getPDS() error {
did, _ := b.ResolveHandle(b.Bluesky.Cfg.Handle) did, _ := b.ResolveHandle(b.Bluesky.Cfg.Handle)
var didURL url.URL didURL, err := parseDIDURL(did)
if strings.HasPrefix(did, "did:web:") { if err != nil {
didURL.Host = "https://" + did[8:] return err
didURL.Path = "/.well-known/did.json"
} else if strings.HasPrefix(did, "did:plc:") {
didURL.Host = "https://plc.directory"
didURL.Path = "/" + did
} else {
return errors.New("DID is not supported")
} }
didResp := new(DIDResponse) didResp := new(DIDResponse)
sling.New().Base(didURL.Host).Get(didURL.Path).ReceiveSuccess(didResp) baseURL := fmt.Sprintf("%s://%s", didURL.Scheme, didURL.Host)
sling.New().Base(baseURL).Get(didURL.Path).ReceiveSuccess(didResp)
if didResp.ID == "" { if didResp.ID == "" {
return errors.New("unable to resolve DID") return errors.New("unable to resolve DID")
} }
b.Bluesky.Cfg.DID = didResp.ID b.Bluesky.Cfg.DID = didResp.ID
b.Bluesky.Cfg.PDSURL = didResp.Service[0].ServiceEndpoint if len(didResp.Service) == 0 {
b.Bluesky.sling.Base(didResp.Service[0].ServiceEndpoint) return errors.New("DID response has no services")
}
pdsURL := didResp.Service[0].ServiceEndpoint
if pdsURL == "" {
return errors.New("service endpoint is empty")
}
b.Bluesky.Cfg.PDSURL = pdsURL
b.Bluesky.sling.Base(pdsURL)
return nil return nil
} }
func (b *BSky) GetHandleFromDID(did string) (handle string, err error) { func (b *BSky) GetHandleFromDID(did string) (handle string, err error) {
var didURL url.URL didURL, err := parseDIDURL(did)
if strings.HasPrefix(did, "did:web:") { if err != nil {
didURL.Host = "https://" + did[8:] return "", err
didURL.Path = "/.well-known/did.json"
} else if strings.HasPrefix(did, "did:plc:") {
didURL.Host = "https://plc.directory"
didURL.Path = "/" + did
} else {
return "", errors.New("DID is not supported")
} }
didResp := new(DIDResponse) didResp := new(DIDResponse)
sling.New().Base(didURL.Host).Get(didURL.Path).ReceiveSuccess(didResp) baseURL := fmt.Sprintf("%s://%s", didURL.Scheme, didURL.Host)
sling.New().Base(baseURL).Get(didURL.Path).ReceiveSuccess(didResp)
if didResp.ID == "" { if didResp.ID == "" {
return "", errors.New("unable to resolve DID") return "", errors.New("unable to resolve DID")
} }
return didResp.AlsoKnownAs[0][5:], nil return didResp.AlsoKnownAs[0][atPrefixLen:], nil
} }
func (b *BSky) GetPDS(handle string) string { func (b *BSky) GetPDS() string {
return b.Bluesky.Cfg.PDSURL return b.Bluesky.Cfg.PDSURL
} }
func (b *BSky) Auth(authData []string) error { func (b *BSky) Auth(authData []string) error {
b.Bluesky.Cfg.Handle = authData[0] b.Bluesky.Cfg.Handle = authData[0]
b.getPDS() b.getPDS()
auth, _ := loadAuth() auth, err := loadAuth()
if auth == nil || auth.AccessJWT == "" { // no auth session found if err != nil { // no auth session found
b.Bluesky.Cfg.AppPassword = authData[1] b.Bluesky.Cfg.AppPassword = authData[1]
err := b.Bluesky.CreateSession(b.Bluesky.Cfg) err := b.Bluesky.CreateSession(b.Bluesky.Cfg)
if err != nil { if err != nil {
@ -118,7 +130,6 @@ func (b *BSky) Auth(authData []string) error {
b.Bluesky.Cfg.Cursor = auth.Cursor b.Bluesky.Cfg.Cursor = auth.Cursor
b.Bluesky.Cfg.AccessJWT = auth.AccessJWT b.Bluesky.Cfg.AccessJWT = auth.AccessJWT
b.Bluesky.Cfg.RefreshJWT = auth.RefreshJWT b.Bluesky.Cfg.RefreshJWT = auth.RefreshJWT
// b.RefreshSession()
b.Bluesky.CheckSessionValid() b.Bluesky.CheckSessionValid()
} }
@ -151,6 +162,9 @@ func loadAuth() (*BlueskyConfig, error) {
} }
var auth *BlueskyConfig var auth *BlueskyConfig
json.Unmarshal(fBytes, &auth) err = json.Unmarshal(fBytes, &auth)
if err != nil {
return nil, fmt.Errorf("failed to parse auth file: %w", err)
}
return auth, nil return auth, nil
} }

17
main.go
View file

@ -97,7 +97,7 @@ func main() {
log.Printf("Found post %s in channel %d, deleting", s[2], tgpost.ChannelID) log.Printf("Found post %s in channel %d, deleting", s[2], tgpost.ChannelID)
m := tgbotapi.NewDeleteMessages(tgpost.ChannelID, tgpost.MessageID) m := tgbotapi.NewDeleteMessages(tgpost.ChannelID, tgpost.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], bsky.PostCollection})
} else { } else {
log.Printf("Unable to find post %s on PDS", s[2]) log.Printf("Unable to find post %s on PDS", s[2])
} }
@ -179,7 +179,7 @@ func (h *handler) HandleEvent(ctx context.Context, event *models.Event) error {
if e == "" { if e == "" {
m := tgbotapi.NewDeleteMessages(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, bsky.PostCollection})
} }
} }
@ -206,6 +206,9 @@ func (h *handler) ProcessPost(event *models.Event) error {
isEditedPost = true isEditedPost = true
} }
aliases := h.bsky.Bluesky.FetchAliases()
facets := ps.ProcessFacets(aliases)
var captionText string var captionText string
if ps.IsQuotePost() { if ps.IsQuotePost() {
ownHandle, handleErr := h.bsky.GetHandleFromDID(h.bsky.Bluesky.Cfg.DID) ownHandle, handleErr := h.bsky.GetHandleFromDID(h.bsky.Bluesky.Cfg.DID)
@ -216,7 +219,7 @@ func (h *handler) ProcessPost(event *models.Event) error {
handle, _ := h.bsky.GetHandleFromDID(strings.Split(ps.Embed.Record.Record.URI, "/")[2]) handle, _ := h.bsky.GetHandleFromDID(strings.Split(ps.Embed.Record.Record.URI, "/")[2])
captionText = fmt.Sprintf( captionText = fmt.Sprintf(
quotePostFormat, quotePostFormat,
ps.ProcessFacets(h.bsky.Bluesky.FetchAliases()), facets,
strings.Split(ps.Embed.Record.Record.URI, "/")[2], strings.Split(ps.Embed.Record.Record.URI, "/")[2],
strings.Split(ps.Embed.Record.Record.URI, "/")[4], strings.Split(ps.Embed.Record.Record.URI, "/")[4],
handle, handle,
@ -227,7 +230,7 @@ func (h *handler) ProcessPost(event *models.Event) error {
handle, _ := h.bsky.GetHandleFromDID(strings.Split(ps.Embed.Record.URI, "/")[2]) handle, _ := h.bsky.GetHandleFromDID(strings.Split(ps.Embed.Record.URI, "/")[2])
captionText = fmt.Sprintf( captionText = fmt.Sprintf(
quotePostFormat, quotePostFormat,
ps.ProcessFacets(h.bsky.Bluesky.FetchAliases()), facets,
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],
handle, handle,
@ -242,8 +245,8 @@ func (h *handler) ProcessPost(event *models.Event) error {
if handleErr != nil { if handleErr != nil {
ownHandle = h.bsky.Bluesky.Cfg.Handle ownHandle = h.bsky.Bluesky.Cfg.Handle
} }
if ps.ProcessFacets(h.bsky.Bluesky.FetchAliases()) != "" { if facets != "" {
captionText = fmt.Sprintf(postFormat, ps.ProcessFacets(h.bsky.Bluesky.FetchAliases()), h.bsky.Bluesky.Cfg.DID, event.Commit.RKey, ownHandle) captionText = fmt.Sprintf(postFormat, facets, h.bsky.Bluesky.Cfg.DID, event.Commit.RKey, ownHandle)
} else { } else {
captionText = fmt.Sprintf("<a href=\"https://bsky.app/profile/%s/post/%s\">🦋 @%s</a>", h.bsky.Bluesky.Cfg.DID, event.Commit.RKey, ownHandle) captionText = fmt.Sprintf("<a href=\"https://bsky.app/profile/%s/post/%s\">🦋 @%s</a>", h.bsky.Bluesky.Cfg.DID, event.Commit.RKey, ownHandle)
} }
@ -325,7 +328,7 @@ func (h *handler) ProcessPost(event *models.Event) error {
} else { } else {
m := tgbotapi.MessageConfig{} m := tgbotapi.MessageConfig{}
if captionText == "" { if captionText == "" {
m = tgbotapi.NewMessage(cid, fmt.Sprintf(postFormat, ps.ProcessFacets(h.bsky.Bluesky.FetchAliases()), h.bsky.Bluesky.Cfg.DID, event.Commit.RKey, h.bsky.Bluesky.Cfg.Handle)) m = tgbotapi.NewMessage(cid, fmt.Sprintf(postFormat, facets, h.bsky.Bluesky.Cfg.DID, event.Commit.RKey, h.bsky.Bluesky.Cfg.Handle))
} else { } else {
m = tgbotapi.NewMessage(cid, captionText) m = tgbotapi.NewMessage(cid, captionText)
} }