Compare commits
No commits in common. "bf2b6211712ad84da9102589d243fd405baa2d0c" and "2bb394623713e422fff94dbc3f2ee7cd963a1c5d" have entirely different histories.
bf2b621171
...
2bb3946237
4 changed files with 33 additions and 240 deletions
|
@ -263,17 +263,3 @@ type Records struct {
|
||||||
Cid string `json:"cid"`
|
Cid string `json:"cid"`
|
||||||
Value Value `json:"value"`
|
Value Value `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bluesky *Bluesky) FetchPost(did string, rkey string) FetchedPost {
|
|
||||||
resp := &struct {
|
|
||||||
Posts []FetchedPost `json:"posts"`
|
|
||||||
}{}
|
|
||||||
params := struct {
|
|
||||||
URIs string `url:"uris"`
|
|
||||||
}{
|
|
||||||
URIs: fmt.Sprintf("at://%s/app.bsky.feed.post/%s", did, rkey),
|
|
||||||
}
|
|
||||||
bluesky.sling.New().Base("https://public.api.bsky.app").
|
|
||||||
Get("/xrpc/app.bsky.feed.getPosts").QueryStruct(¶ms).Receive(resp, resp)
|
|
||||||
return resp.Posts[0]
|
|
||||||
}
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ func NewBSky() *BSky {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BSky) ResolveHandle(handle string) (string, error) {
|
func (b *BSky) getPDS() error {
|
||||||
httpClient := &http.Client{Timeout: 3 * time.Second}
|
httpClient := &http.Client{Timeout: 3 * time.Second}
|
||||||
resp := new(BSkySessionResponse)
|
resp := new(BSkySessionResponse)
|
||||||
errResp := &struct {
|
errResp := &struct {
|
||||||
|
@ -38,29 +38,23 @@ func (b *BSky) ResolveHandle(handle string) (string, error) {
|
||||||
params := struct {
|
params := struct {
|
||||||
Handle string `url:"handle"`
|
Handle string `url:"handle"`
|
||||||
}{
|
}{
|
||||||
Handle: handle,
|
Handle: b.Bluesky.Cfg.Handle,
|
||||||
}
|
}
|
||||||
sling.New().Base("https://public.api.bsky.app/").Client(httpClient).
|
sling.New().Base("https://public.api.bsky.app/").Client(httpClient).
|
||||||
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 != "" {
|
||||||
return "", errors.New(errResp.Message)
|
return errors.New(errResp.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.DID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BSky) getPDS() error {
|
|
||||||
did, _ := b.ResolveHandle(b.Bluesky.Cfg.Handle)
|
|
||||||
|
|
||||||
var didURL url.URL
|
var didURL url.URL
|
||||||
if strings.HasPrefix(did, "did:web:") {
|
if strings.HasPrefix(resp.DID, "did:web:") {
|
||||||
didURL.Host = "https://" + did[8:]
|
didURL.Host = "https://" + resp.DID[8:]
|
||||||
didURL.Path = "/.well-known/did.json"
|
didURL.Path = "/.well-known/did.json"
|
||||||
} else if strings.HasPrefix(did, "did:plc:") {
|
} else if strings.HasPrefix(resp.DID, "did:plc:") {
|
||||||
didURL.Host = "https://plc.directory"
|
didURL.Host = "https://plc.directory"
|
||||||
didURL.Path = "/" + did
|
didURL.Path = "/" + resp.DID
|
||||||
} else {
|
} else {
|
||||||
return errors.New("DID is not supported")
|
return errors.New("DID is not supported")
|
||||||
}
|
}
|
||||||
|
@ -110,7 +104,7 @@ func (b *BSky) Auth(authData []string) error {
|
||||||
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 {
|
||||||
return fmt.Errorf("unable to auth: %s", err)
|
return errors.New(fmt.Sprintf("unable to auth: %s", err))
|
||||||
}
|
}
|
||||||
b.Bluesky.Cfg.AppPassword = "" // we don't need to save this
|
b.Bluesky.Cfg.AppPassword = "" // we don't need to save this
|
||||||
PersistAuthSession(b.Bluesky.Cfg)
|
PersistAuthSession(b.Bluesky.Cfg)
|
||||||
|
|
156
bsky/parse.go
156
bsky/parse.go
|
@ -123,160 +123,6 @@ type ParsedEmbeds struct {
|
||||||
Height int64
|
Height int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type FetchedPost struct {
|
|
||||||
URI string `json:"uri"`
|
|
||||||
Cid string `json:"cid"`
|
|
||||||
Author struct {
|
|
||||||
Did string `json:"did"`
|
|
||||||
Handle string `json:"handle"`
|
|
||||||
DisplayName string `json:"displayName"`
|
|
||||||
Avatar string `json:"avatar"`
|
|
||||||
Associated struct {
|
|
||||||
Chat struct {
|
|
||||||
AllowIncoming string `json:"allowIncoming"`
|
|
||||||
} `json:"chat"`
|
|
||||||
} `json:"associated"`
|
|
||||||
Labels []interface{} `json:"labels"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
} `json:"author"`
|
|
||||||
Record *Post `json:"record"`
|
|
||||||
// Record struct {
|
|
||||||
// Type string `json:"$type"`
|
|
||||||
// CreatedAt time.Time `json:"createdAt"`
|
|
||||||
// Embed struct {
|
|
||||||
// Type string `json:"$type"`
|
|
||||||
// Media struct {
|
|
||||||
// Type string `json:"$type"`
|
|
||||||
// Images []struct {
|
|
||||||
// Alt string `json:"alt"`
|
|
||||||
// AspectRatio struct {
|
|
||||||
// Height int `json:"height"`
|
|
||||||
// Width int `json:"width"`
|
|
||||||
// } `json:"aspectRatio"`
|
|
||||||
// Image struct {
|
|
||||||
// Type string `json:"$type"`
|
|
||||||
// Ref struct {
|
|
||||||
// Link string `json:"$link"`
|
|
||||||
// } `json:"ref"`
|
|
||||||
// MimeType string `json:"mimeType"`
|
|
||||||
// Size int `json:"size"`
|
|
||||||
// } `json:"image"`
|
|
||||||
// } `json:"images"`
|
|
||||||
// } `json:"media"`
|
|
||||||
// Record struct {
|
|
||||||
// Type string `json:"$type"`
|
|
||||||
// Record struct {
|
|
||||||
// Cid string `json:"cid"`
|
|
||||||
// URI string `json:"uri"`
|
|
||||||
// } `json:"record"`
|
|
||||||
// } `json:"record"`
|
|
||||||
// } `json:"embed"`
|
|
||||||
// Labels struct {
|
|
||||||
// Type string `json:"$type"`
|
|
||||||
// Values []struct {
|
|
||||||
// Val string `json:"val"`
|
|
||||||
// } `json:"values"`
|
|
||||||
// } `json:"labels"`
|
|
||||||
// Langs []string `json:"langs"`
|
|
||||||
// Text string `json:"text"`
|
|
||||||
// } `json:"record"`
|
|
||||||
Embed struct {
|
|
||||||
Type string `json:"$type"`
|
|
||||||
Media struct {
|
|
||||||
Type string `json:"$type"`
|
|
||||||
Images []struct {
|
|
||||||
Thumb string `json:"thumb"`
|
|
||||||
Fullsize string `json:"fullsize"`
|
|
||||||
Alt string `json:"alt"`
|
|
||||||
AspectRatio struct {
|
|
||||||
Height int `json:"height"`
|
|
||||||
Width int `json:"width"`
|
|
||||||
} `json:"aspectRatio"`
|
|
||||||
} `json:"images"`
|
|
||||||
} `json:"media"`
|
|
||||||
Record struct {
|
|
||||||
Record struct {
|
|
||||||
Type string `json:"$type"`
|
|
||||||
URI string `json:"uri"`
|
|
||||||
Cid string `json:"cid"`
|
|
||||||
Author struct {
|
|
||||||
Did string `json:"did"`
|
|
||||||
Handle string `json:"handle"`
|
|
||||||
DisplayName string `json:"displayName"`
|
|
||||||
Avatar string `json:"avatar"`
|
|
||||||
Associated struct {
|
|
||||||
Chat struct {
|
|
||||||
AllowIncoming string `json:"allowIncoming"`
|
|
||||||
} `json:"chat"`
|
|
||||||
} `json:"associated"`
|
|
||||||
Labels []interface{} `json:"labels"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
} `json:"author"`
|
|
||||||
Value struct {
|
|
||||||
Type string `json:"$type"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
Embed struct {
|
|
||||||
Type string `json:"$type"`
|
|
||||||
AspectRatio struct {
|
|
||||||
Height int `json:"height"`
|
|
||||||
Width int `json:"width"`
|
|
||||||
} `json:"aspectRatio"`
|
|
||||||
Video struct {
|
|
||||||
Type string `json:"$type"`
|
|
||||||
Ref struct {
|
|
||||||
Link string `json:"$link"`
|
|
||||||
} `json:"ref"`
|
|
||||||
MimeType string `json:"mimeType"`
|
|
||||||
Size int `json:"size"`
|
|
||||||
} `json:"video"`
|
|
||||||
} `json:"embed"`
|
|
||||||
Facets []struct {
|
|
||||||
Type string `json:"$type"`
|
|
||||||
Features []struct {
|
|
||||||
Type string `json:"$type"`
|
|
||||||
Did string `json:"did"`
|
|
||||||
} `json:"features"`
|
|
||||||
Index struct {
|
|
||||||
ByteEnd int `json:"byteEnd"`
|
|
||||||
ByteStart int `json:"byteStart"`
|
|
||||||
} `json:"index"`
|
|
||||||
} `json:"facets"`
|
|
||||||
Langs []string `json:"langs"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
} `json:"value"`
|
|
||||||
Labels []interface{} `json:"labels"`
|
|
||||||
LikeCount int `json:"likeCount"`
|
|
||||||
ReplyCount int `json:"replyCount"`
|
|
||||||
RepostCount int `json:"repostCount"`
|
|
||||||
QuoteCount int `json:"quoteCount"`
|
|
||||||
IndexedAt time.Time `json:"indexedAt"`
|
|
||||||
Embeds []struct {
|
|
||||||
Type string `json:"$type"`
|
|
||||||
Cid string `json:"cid"`
|
|
||||||
Playlist string `json:"playlist"`
|
|
||||||
Thumbnail string `json:"thumbnail"`
|
|
||||||
AspectRatio struct {
|
|
||||||
Height int `json:"height"`
|
|
||||||
Width int `json:"width"`
|
|
||||||
} `json:"aspectRatio"`
|
|
||||||
} `json:"embeds"`
|
|
||||||
} `json:"record"`
|
|
||||||
} `json:"record"`
|
|
||||||
} `json:"embed,omitempty"`
|
|
||||||
ReplyCount int `json:"replyCount"`
|
|
||||||
RepostCount int `json:"repostCount"`
|
|
||||||
LikeCount int `json:"likeCount"`
|
|
||||||
QuoteCount int `json:"quoteCount"`
|
|
||||||
IndexedAt time.Time `json:"indexedAt"`
|
|
||||||
Labels []struct {
|
|
||||||
Src string `json:"src"`
|
|
||||||
URI string `json:"uri"`
|
|
||||||
Cid string `json:"cid"`
|
|
||||||
Val string `json:"val"`
|
|
||||||
Cts time.Time `json:"cts"`
|
|
||||||
} `json:"labels"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BSky) ParsePost(post []byte) (*Post, error) {
|
func (b *BSky) ParsePost(post []byte) (*Post, error) {
|
||||||
var p = &Post{}
|
var p = &Post{}
|
||||||
err := json.Unmarshal(post, &p)
|
err := json.Unmarshal(post, &p)
|
||||||
|
@ -314,12 +160,14 @@ func (post *Post) ProcessFacets(aliases []Records) string {
|
||||||
switch feature.Type {
|
switch feature.Type {
|
||||||
case "app.bsky.richtext.facet#mention":
|
case "app.bsky.richtext.facet#mention":
|
||||||
link := fmt.Sprintf(`<a href="https://bsky.app/profile/%s">%s</a>`, feature.Did, post.Text[start:end])
|
link := fmt.Sprintf(`<a href="https://bsky.app/profile/%s">%s</a>`, feature.Did, post.Text[start:end])
|
||||||
|
if aliases != nil {
|
||||||
for _, alias := range aliases {
|
for _, alias := range aliases {
|
||||||
if alias.Value.Subject == feature.Did {
|
if alias.Value.Subject == feature.Did {
|
||||||
link = fmt.Sprintf(`<a href="%s">%s</a>`,
|
link = fmt.Sprintf(`<a href="%s">%s</a>`,
|
||||||
strings.SplitN(alias.Value.Target, "#", 2)[0], strings.SplitN(alias.Value.Target, "#", 2)[1])
|
strings.SplitN(alias.Value.Target, "#", 2)[0], strings.SplitN(alias.Value.Target, "#", 2)[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
result.WriteString(link)
|
result.WriteString(link)
|
||||||
case "app.bsky.richtext.facet#link":
|
case "app.bsky.richtext.facet#link":
|
||||||
link := fmt.Sprintf(`<a href="%s">%s</a>`, feature.URI, post.Text[start:end])
|
link := fmt.Sprintf(`<a href="%s">%s</a>`, feature.URI, post.Text[start:end])
|
||||||
|
|
73
main.go
73
main.go
|
@ -3,8 +3,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
"io"
|
"io"
|
||||||
|
@ -13,7 +11,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -22,7 +19,6 @@ import (
|
||||||
tgbotapi "github.com/OvyFlash/telegram-bot-api"
|
tgbotapi "github.com/OvyFlash/telegram-bot-api"
|
||||||
|
|
||||||
// apibsky "github.com/bluesky-social/indigo/api/bsky"
|
// 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"
|
||||||
|
@ -43,13 +39,7 @@ type handler struct {
|
||||||
bsky *bsky.BSky
|
bsky *bsky.BSky
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
post = flag.String("post", "", "URL to a BlueSky post")
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
var handle = os.Getenv("BSKY_HANDLE")
|
var handle = os.Getenv("BSKY_HANDLE")
|
||||||
var password = os.Getenv("BSKY_PASSWORD")
|
var password = os.Getenv("BSKY_PASSWORD")
|
||||||
bskyClient := bsky.NewBSky()
|
bskyClient := bsky.NewBSky()
|
||||||
|
@ -58,50 +48,6 @@ func main() {
|
||||||
log.Fatal(err, ". please set BSKY_HANDLE and BSKY_PASSWORD env variables")
|
log.Fatal(err, ". please set BSKY_HANDLE and BSKY_PASSWORD env variables")
|
||||||
}
|
}
|
||||||
|
|
||||||
h := &handler{
|
|
||||||
seenSeqs: make(map[int64]struct{}),
|
|
||||||
bsky: bskyClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoint := "https://api.telegram.org/bot%s/%s"
|
|
||||||
if os.Getenv("TG_API_ENDPOINT") != "" {
|
|
||||||
endpoint = os.Getenv("TG_API_ENDPOINT")
|
|
||||||
}
|
|
||||||
bot, err := tgbotapi.NewBotAPIWithAPIEndpoint(os.Getenv("TG_TOKEN"), endpoint)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
h.tg = bot
|
|
||||||
|
|
||||||
if os.Getenv("TG_CHANNEL_ID") == "" {
|
|
||||||
log.Fatal("TG_CHANNEL_ID is not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
if *post != "" {
|
|
||||||
r := regexp.MustCompile(`^https:\/\/.*?\/profile\/(.*?)\/post\/(.*?)$`)
|
|
||||||
s := r.FindStringSubmatch(*post)
|
|
||||||
handle := s[1]
|
|
||||||
if s[1][0:4] != "did:" {
|
|
||||||
handle, _ = bskyClient.ResolveHandle(s[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
postJSON := bskyClient.Bluesky.FetchPost(handle, s[2])
|
|
||||||
p, _ := json.Marshal(postJSON.Record)
|
|
||||||
h.ProcessPost(&models.Event{
|
|
||||||
Did: postJSON.Author.Did,
|
|
||||||
TimeUS: postJSON.Record.CreatedAt.Unix(),
|
|
||||||
Kind: "",
|
|
||||||
Commit: &models.Commit{
|
|
||||||
CID: postJSON.Cid,
|
|
||||||
Operation: "create",
|
|
||||||
RKey: strings.Split(postJSON.URI, "/")[4],
|
|
||||||
Collection: "app.bsky.feed.post",
|
|
||||||
Record: p,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||||
Level: slog.LevelDebug.Level(),
|
Level: slog.LevelDebug.Level(),
|
||||||
|
@ -114,6 +60,11 @@ func main() {
|
||||||
config.WantedDids = []string{bskyClient.Bluesky.Cfg.DID}
|
config.WantedDids = []string{bskyClient.Bluesky.Cfg.DID}
|
||||||
config.Compress = true
|
config.Compress = true
|
||||||
|
|
||||||
|
h := &handler{
|
||||||
|
seenSeqs: make(map[int64]struct{}),
|
||||||
|
bsky: bskyClient,
|
||||||
|
}
|
||||||
|
|
||||||
scheduler := sequential.NewScheduler("jetstream_localdev", logger, h.HandleEvent)
|
scheduler := sequential.NewScheduler("jetstream_localdev", logger, h.HandleEvent)
|
||||||
|
|
||||||
c, err := client.NewClient(config, logger, scheduler)
|
c, err := client.NewClient(config, logger, scheduler)
|
||||||
|
@ -121,6 +72,20 @@ func main() {
|
||||||
log.Fatalf("failed to create client: %v", err)
|
log.Fatalf("failed to create client: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
endpoint := "https://api.telegram.org/bot%s/%s"
|
||||||
|
if os.Getenv("TG_API_ENDPOINT") != "" {
|
||||||
|
endpoint = os.Getenv("TG_API_ENDPOINT")
|
||||||
|
}
|
||||||
|
bot, err := tgbotapi.NewBotAPIWithAPIEndpoint(os.Getenv("TG_TOKEN"), endpoint)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
h.tg = bot
|
||||||
|
|
||||||
|
if os.Getenv("TG_CHANNEL_ID") == "" {
|
||||||
|
log.Fatal("TG_CHANNEL_ID is not set")
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
// file, err := os.Open("posts.json")
|
// file, err := os.Open("posts.json")
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue