mirror of
https://github.com/by-jp/www.byjp.me.git
synced 2025-08-09 05:36:07 +01:00
Basics of interactions for pixelfed
This commit is contained in:
parent
f0499ba2ed
commit
50278343bc
10 changed files with 204 additions and 43 deletions
11
.syndicate
11
.syndicate
|
@ -3,13 +3,14 @@ publish_url = "https://www.byjp.me/"
|
|||
feeds = ["index.xml"]
|
||||
content_glob = "content/**/*.md"
|
||||
interactions_dir = "data/interactions/"
|
||||
interaction_pfps_dir = "static/interaction-pfps/"
|
||||
|
||||
[sites]
|
||||
[sites.instagram]
|
||||
type = "instagram"
|
||||
username = "jphastings"
|
||||
password_envvar = "INSTAGRAM_PASSWORD"
|
||||
totp_envvar = "INSTAGRAM_TOTP"
|
||||
# [sites.instagram]
|
||||
# type = "instagram"
|
||||
# username = "jphastings"
|
||||
# password_envvar = "INSTAGRAM_PASSWORD"
|
||||
# totp_envvar = "INSTAGRAM_TOTP"
|
||||
|
||||
# [sites.bluesky]
|
||||
# type = "bluesky"
|
||||
|
|
1
data/interactions/photos/mums-garden/interactions.json
Normal file
1
data/interactions/photos/mums-garden/interactions.json
Normal file
|
@ -0,0 +1 @@
|
|||
[{"emoji":"⭐️","author":{"name":"JP","url":"https://hachyderm.io/users/byjp"},"timestamp":"2022-12-14T11:16:00Z"},{"emoji":"⭐️","author":{"name":"Ben!","url":"https://pixelfed.social/bencord0"},"timestamp":"2019-09-14T08:43:33Z"},{"emoji":"⭐️","author":{"name":"Thomas Zimmermann","url":"https://pixelfed.social/curlingtom"},"timestamp":"2022-10-30T14:12:09Z"},{"emoji":"⭐️","author":{"name":"Ivan Jurišić","url":"https://pixelfed.social/ijurisic"},"timestamp":"2019-09-27T22:53:45Z"}]
|
1
data/interactions/photos/sunny-brunch/interactions.json
Normal file
1
data/interactions/photos/sunny-brunch/interactions.json
Normal file
|
@ -0,0 +1 @@
|
|||
[{"emoji":"⭐️","author":{"name":"Rui Almeida","url":"https://pixelfed.social/RJCA_PT"},"timestamp":"2022-12-03T15:13:46Z"}]
|
|
@ -1,17 +1,69 @@
|
|||
package backfeeder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/by-jp/www.byjp.me/tools/syndicate/services"
|
||||
"github.com/by-jp/www.byjp.me/tools/syndicate/shared"
|
||||
)
|
||||
|
||||
type backfeeder struct {
|
||||
services *services.List
|
||||
done map[string]struct{}
|
||||
type BackfeedRef struct {
|
||||
Source string
|
||||
LocalURL string
|
||||
}
|
||||
|
||||
func New(services *services.List) *backfeeder {
|
||||
type ToBackfeedList map[string]BackfeedRef
|
||||
|
||||
type backfeeder struct {
|
||||
services *services.List
|
||||
done map[string]struct{}
|
||||
urlToPath func(string) string
|
||||
}
|
||||
|
||||
func New(services *services.List, urlToPath func(string) string) *backfeeder {
|
||||
return &backfeeder{
|
||||
services: services,
|
||||
done: make(map[string]struct{}),
|
||||
services: services,
|
||||
done: make(map[string]struct{}),
|
||||
urlToPath: urlToPath,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backfeeder) BackfeedAll(toBackfeed ToBackfeedList) error {
|
||||
allIAs := make(map[string][]shared.Interaction)
|
||||
|
||||
for remoteURL, ref := range toBackfeed {
|
||||
ias, err := b.services.Service(ref.Source).Interactions(remoteURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := b.urlToPath(ref.LocalURL)
|
||||
allIAs[path] = append(allIAs[path], ias...)
|
||||
}
|
||||
|
||||
for postDir, ias := range allIAs {
|
||||
if err := writeInteractions(postDir, ias); err != nil {
|
||||
return fmt.Errorf("couldn't write interactions into %s: %w", postDir, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeInteractions(dir string, ias []shared.Interaction) error {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
path := filepath.Join(dir, "interactions.json")
|
||||
|
||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
enc := json.NewEncoder(f)
|
||||
return enc.Encode(ias)
|
||||
}
|
||||
|
|
|
@ -30,13 +30,13 @@ type fileConfig struct {
|
|||
}
|
||||
|
||||
type config struct {
|
||||
feeds []string
|
||||
services *services.List
|
||||
interactionsDir string
|
||||
content []string
|
||||
tagMatcher *regexp.Regexp
|
||||
syndicationMatchers map[string]*regexp.Regexp
|
||||
urlToPath func(string) string
|
||||
feeds []string
|
||||
services *services.List
|
||||
content []string
|
||||
tagMatcher *regexp.Regexp
|
||||
syndicationMatchers map[string]*regexp.Regexp
|
||||
urlToPublicPath func(string) string
|
||||
urlToInteractionsPath func(string) string
|
||||
}
|
||||
|
||||
func parseConfig(cfgPath string) (*config, error) {
|
||||
|
@ -48,11 +48,14 @@ func parseConfig(cfgPath string) (*config, error) {
|
|||
|
||||
cfg := &config{
|
||||
feeds: []string{},
|
||||
services: services.New(),
|
||||
syndicationMatchers: make(map[string]*regexp.Regexp),
|
||||
interactionsDir: cfgData.InteractionsDir,
|
||||
urlToPath: func(url string) string {
|
||||
urlToPublicPath: func(url string) string {
|
||||
return path.Join(cfgData.PublishRoot, strings.TrimPrefix(url, cfgData.PublishURL))
|
||||
},
|
||||
urlToInteractionsPath: func(url string) string {
|
||||
return path.Join(cfgData.InteractionsDir, strings.TrimPrefix(url, cfgData.PublishURL))
|
||||
},
|
||||
}
|
||||
|
||||
cfg.content, err = doublestar.Glob(os.DirFS("."), cfgData.ContentGlob)
|
||||
|
|
|
@ -6,14 +6,13 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/by-jp/www.byjp.me/tools/syndicate/backfeeder"
|
||||
"github.com/by-jp/www.byjp.me/tools/syndicate/poster"
|
||||
"github.com/by-jp/www.byjp.me/tools/syndicate/shared"
|
||||
"github.com/mmcdole/gofeed"
|
||||
)
|
||||
|
||||
type toBackfeedMap map[string]string
|
||||
|
||||
func parseFeed(urlToPath func(string) string, feedReader io.Reader, tagMatcher *regexp.Regexp, syndicationMatchers map[string]*regexp.Regexp) ([]string, poster.ToPostList, toBackfeedMap, error) {
|
||||
func parseFeed(urlToPath func(string) string, feedReader io.Reader, tagMatcher *regexp.Regexp, syndicationMatchers map[string]*regexp.Regexp) ([]string, poster.ToPostList, backfeeder.ToBackfeedList, error) {
|
||||
fp := gofeed.NewParser()
|
||||
feed, err := fp.Parse(feedReader)
|
||||
if err != nil {
|
||||
|
@ -21,7 +20,7 @@ func parseFeed(urlToPath func(string) string, feedReader io.Reader, tagMatcher *
|
|||
}
|
||||
|
||||
toPost := make(poster.ToPostList)
|
||||
toBackfeed := make(toBackfeedMap)
|
||||
toBackfeed := make(backfeeder.ToBackfeedList)
|
||||
services := make(map[string]struct{})
|
||||
|
||||
for _, item := range feed.Items {
|
||||
|
@ -46,7 +45,10 @@ func parseFeed(urlToPath func(string) string, feedReader io.Reader, tagMatcher *
|
|||
|
||||
for sName, bf := range syndicationMatchers {
|
||||
if bf.MatchString(ext.Value) {
|
||||
toBackfeed[ext.Value] = item.Link
|
||||
toBackfeed[ext.Value] = backfeeder.BackfeedRef{
|
||||
Source: sName,
|
||||
LocalURL: item.Link,
|
||||
}
|
||||
services[sName] = struct{}{}
|
||||
break
|
||||
}
|
||||
|
|
|
@ -24,13 +24,13 @@ func main() {
|
|||
check(err)
|
||||
|
||||
pstr := poster.New(cfg.services)
|
||||
bkfd := backfeeder.New(cfg.services)
|
||||
bkfd := backfeeder.New(cfg.services, cfg.urlToInteractionsPath)
|
||||
|
||||
for _, feed := range cfg.feeds {
|
||||
f, err := os.Open(feed)
|
||||
check(err)
|
||||
defer f.Close()
|
||||
services, toPost, toBackfeed, err := parseFeed(cfg.urlToPath, f, cfg.tagMatcher, cfg.syndicationMatchers)
|
||||
services, toPost, toBackfeed, err := parseFeed(cfg.urlToPublicPath, f, cfg.tagMatcher, cfg.syndicationMatchers)
|
||||
check(err)
|
||||
|
||||
if len(services) > 0 {
|
||||
|
@ -43,9 +43,7 @@ func main() {
|
|||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Found %d new syndications to post in %s\n", len(toPost), feed)
|
||||
posted, err := pstr.PostAll(toPost)
|
||||
_ = posted
|
||||
if err != nil {
|
||||
if _, err := pstr.PostAll(toPost); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Couldn't post syndications: %v\n", err)
|
||||
}
|
||||
|
||||
|
@ -56,6 +54,8 @@ func main() {
|
|||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Found %d existing syndications to backfeed from %s\n", len(toBackfeed), feed)
|
||||
_ = bkfd
|
||||
if err := bkfd.BackfeedAll(toBackfeed); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Couldn't backfeed syndications: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,103 @@
|
|||
package mastodon
|
||||
|
||||
import "github.com/by-jp/www.byjp.me/tools/syndicate/shared"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/by-jp/www.byjp.me/tools/syndicate/shared"
|
||||
"github.com/mattn/go-mastodon"
|
||||
)
|
||||
|
||||
func (s *service) Interactions(url string) ([]shared.Interaction, error) {
|
||||
return nil, nil
|
||||
id, err := s.postID(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ias []shared.Interaction
|
||||
|
||||
ctx := context.Background()
|
||||
favs, err := s.masto.GetFavouritedBy(ctx, id, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get favourites for %s: %w", url, err)
|
||||
}
|
||||
|
||||
for _, acc := range favs {
|
||||
ias = append(ias, shared.Interaction{
|
||||
Emoji: "⭐️",
|
||||
Author: shared.Author{
|
||||
Name: acc.DisplayName,
|
||||
URL: acc.URL,
|
||||
AvatarURL: acc.Avatar,
|
||||
},
|
||||
Timestamp: acc.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
reblogs, err := s.masto.GetRebloggedBy(ctx, id, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get boosts for %s: %w", url, err)
|
||||
}
|
||||
|
||||
for _, acc := range reblogs {
|
||||
ias = append(ias, shared.Interaction{
|
||||
Emoji: "🔁",
|
||||
Author: shared.Author{
|
||||
Name: acc.DisplayName,
|
||||
URL: acc.URL,
|
||||
AvatarURL: acc.Avatar,
|
||||
},
|
||||
Timestamp: acc.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
postCtx, err := s.masto.GetStatusContext(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get replies for %s: %w", url, err)
|
||||
}
|
||||
|
||||
for _, reply := range postCtx.Descendants {
|
||||
if reply.Visibility != mastodon.VisibilityPublic {
|
||||
continue
|
||||
}
|
||||
|
||||
ias = append(ias, shared.Interaction{
|
||||
URL: reply.URL,
|
||||
Comment: reply.Content,
|
||||
Author: shared.Author{
|
||||
Name: reply.Account.DisplayName,
|
||||
URL: reply.Account.URL,
|
||||
AvatarURL: reply.Account.Avatar,
|
||||
},
|
||||
Timestamp: reply.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
return ias, nil
|
||||
}
|
||||
|
||||
func (s *service) postID(url string) (mastodon.ID, error) {
|
||||
re, err := s.BackfeedMatcher()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("couldn't create the backfeed matcher: %w", err)
|
||||
}
|
||||
|
||||
m := re.FindStringSubmatch(url)
|
||||
if len(m) == 0 {
|
||||
return "", fmt.Errorf("couldn't extract the post ID from the URL")
|
||||
}
|
||||
|
||||
var id string
|
||||
for i, name := range re.SubexpNames() {
|
||||
if name == "id" {
|
||||
id = m[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if id == "" {
|
||||
return "", fmt.Errorf("couldn't extract the post ID from the URL")
|
||||
}
|
||||
|
||||
return mastodon.ID(id), nil
|
||||
}
|
||||
|
|
|
@ -11,7 +11,9 @@ type List struct {
|
|||
}
|
||||
|
||||
func New() *List {
|
||||
return &List{}
|
||||
return &List{
|
||||
available: make(map[string]shared.Service),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *List) Load(name string, siteConfig any) (shared.Service, error) {
|
||||
|
|
|
@ -26,16 +26,19 @@ type Post struct {
|
|||
|
||||
type Interaction struct {
|
||||
// eg. Repost is 🔁, Facebook is 👍, Instagram is ♥️, Mastodon is ⭐️, Medium is 👏
|
||||
Emoji string
|
||||
Emoji string `json:"emoji,omitempty"`
|
||||
// The URL of the original interaction
|
||||
URL string
|
||||
URL string `json:"url,omitempty"`
|
||||
// If there's a comment associated with the interaction
|
||||
Comment string
|
||||
Comment string `json:"comment,omitempty"`
|
||||
// Details of the author
|
||||
Author struct {
|
||||
Name string
|
||||
URL string
|
||||
Icon []byte
|
||||
}
|
||||
Timestamp time.Time
|
||||
Author Author `json:"author"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
type Author struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Avatar []byte `json:"-"`
|
||||
AvatarURL string `json:"-"`
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue