Skip to content

Commit

Permalink
Merge pull request #51 from kakakaya/release/v14
Browse files Browse the repository at this point in the history
* Use BrowserOpenURL and delete OpenURL

* Update version to v14

* Fix createdAt timezone problem

* Update

* Add Richtext ( hashtag, URL, mention ) support (#54)

* Bump dependency, to use RichText

* Add hashtag / URL / menthion richtext

* Bump

---------

Co-authored-by: Ryota Kayanuma <r.kayanuma@gigei.jp>
  • Loading branch information
kakakaya and PicoSushi authored Mar 2, 2024
2 parents 146f3b2 + fe56c41 commit 672859f
Show file tree
Hide file tree
Showing 10 changed files with 370 additions and 111 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022 kakakaya
Copyright (c) 2022 kakakaya, Ryota Kayanuma

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
19 changes: 0 additions & 19 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
"log/slog"
"net/http"
"os"
"os/exec"
goruntime "runtime"
"strings"
"time"

Expand Down Expand Up @@ -166,23 +164,6 @@ func (a *App) OpenLogDirectory() error {
return openDirectory(f)
}

func OpenURL(url string) error {
var cmd string
var args []string

switch goruntime.GOOS {
case "windows":
cmd = "cmd"
args = []string{"/c", "start"}
case "darwin":
cmd = "open"
default:
cmd = "xdg-open"
}
args = append(args, url)
return exec.Command(cmd, args...).Start()
}

func (a *App) SetXRPCClient() error {

// Build xrpc.Client
Expand Down
134 changes: 130 additions & 4 deletions bluesky.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,151 @@
package main

import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"strings"
"time"

"github.com/PuerkitoBio/goquery"
comatproto "github.com/bluesky-social/indigo/api/atproto"
"github.com/bluesky-social/indigo/api/bsky"
appbsky "github.com/bluesky-social/indigo/api/bsky"
lexutil "github.com/bluesky-social/indigo/lex/util"
"github.com/bluesky-social/indigo/xrpc"
)

func addLink(xrpcc *xrpc.Client, post *bsky.FeedPost, link string) {
doc, err := goquery.NewDocument(link)
var title string
var description string
var imgURL string

if err == nil {
title = doc.Find(`title`).Text()
description, _ = doc.Find(`meta[property="description"]`).Attr("content")
imgURL, _ = doc.Find(`meta[property="og:image"]`).Attr("content")
if title == "" {
title, _ = doc.Find(`meta[property="og:title"]`).Attr("content")
if title == "" {
title = link
}
}
if description == "" {
description, _ = doc.Find(`meta[property="og:description"]`).Attr("content")
if description == "" {
description = link
}
}
post.Embed.EmbedExternal = &bsky.EmbedExternal{
External: &bsky.EmbedExternal_External{
Description: description,
Title: title,
Uri: link,
},
}
} else {
post.Embed.EmbedExternal = &bsky.EmbedExternal{
External: &bsky.EmbedExternal_External{
Uri: link,
},
}
}
if imgURL != "" && post.Embed.EmbedExternal != nil {
resp, err := http.Get(imgURL)
if err == nil && resp.StatusCode == http.StatusOK {
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err == nil {
resp, err := comatproto.RepoUploadBlob(context.TODO(), xrpcc, bytes.NewReader(b))
if err == nil {
post.Embed.EmbedExternal.External.Thumb = &lexutil.LexBlob{
Ref: resp.Blob.Ref,
MimeType: http.DetectContentType(b),
Size: resp.Blob.Size,
}
}
}
}
}
}

func BskyFeedPost(xrpcc *xrpc.Client, text string) (string, error) {
// Post given text to Bluesky, with app.bsky.feed.post.
if text == "" || strings.TrimSpace(text) == "" {
return "", fmt.Errorf("no text specified")
}

post := &appbsky.FeedPost{
Text: text,
CreatedAt: time.Time.UTC(time.Now()).Format("2006-01-02T15:04:05.000Z"),
}

for _, entry := range extractLinksBytes(text) {
post.Facets = append(post.Facets, &appbsky.RichtextFacet{
Features: []*appbsky.RichtextFacet_Features_Elem{
{
RichtextFacet_Link: &appbsky.RichtextFacet_Link{
Uri: entry.text,
},
},
},
Index: &appbsky.RichtextFacet_ByteSlice{
ByteStart: entry.start,
ByteEnd: entry.end,
},
})
if post.Embed == nil {
post.Embed = &appbsky.FeedPost_Embed{}
}
if post.Embed.EmbedExternal == nil {
addLink(xrpcc, post, entry.text)
}
}

for _, entry := range extractMentionsBytes(text) {
profile, err := appbsky.ActorGetProfile(context.TODO(), xrpcc, entry.text)
if err != nil {
return "", err
}
post.Facets = append(post.Facets, &appbsky.RichtextFacet{
Features: []*appbsky.RichtextFacet_Features_Elem{
{
RichtextFacet_Mention: &appbsky.RichtextFacet_Mention{
Did: profile.Did,
},
},
},
Index: &appbsky.RichtextFacet_ByteSlice{
ByteStart: entry.start,
ByteEnd: entry.end,
},
})
}

for _, entry := range extractTagsBytes(text) {
post.Facets = append(post.Facets, &appbsky.RichtextFacet{
Features: []*appbsky.RichtextFacet_Features_Elem{
{
RichtextFacet_Tag: &appbsky.RichtextFacet_Tag{
Tag: entry.text,
},
},
},
Index: &appbsky.RichtextFacet_ByteSlice{
ByteStart: entry.start,
ByteEnd: entry.end,
},
})
}

resp, err := comatproto.RepoCreateRecord(context.TODO(), xrpcc, &comatproto.RepoCreateRecord_Input{
Collection: "app.bsky.feed.post",
Repo: xrpcc.Auth.Did,
Record: &lexutil.LexiconTypeDecoder{
Val: &appbsky.FeedPost{
Text: text,
CreatedAt: time.Now().Format("2006-01-02T15:04:05.000Z"),
},
Val: post,
},
})
if err != nil {
Expand Down
16 changes: 8 additions & 8 deletions continent.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@ func (a *App) DispatchCommand(text string) string {
case "/help":
{
if len(words) < 2 {
OpenURL("https://github.com/kakakaya/mazesoba-continent/blob/main/README.md")
runtime.BrowserOpenURL(a.ctx, "https://github.com/kakakaya/mazesoba-continent/blob/main/README.md")
}
switch words[1] {
case "config":
{
OpenURL("https://github.com/kakakaya/mazesoba-continent/tree/main/docs/CONFIG.md")
runtime.BrowserOpenURL(a.ctx, "https://github.com/kakakaya/mazesoba-continent/tree/main/docs/CONFIG.md")
}
case "command":
{
OpenURL("https://github.com/kakakaya/mazesoba-continent/tree/main/docs/SLASH_COMMAND.md")
runtime.BrowserOpenURL(a.ctx, "https://github.com/kakakaya/mazesoba-continent/tree/main/docs/SLASH_COMMAND.md")
}
default:
{
OpenURL("https://github.com/kakakaya/mazesoba-continent/blob/main/README.md")
runtime.BrowserOpenURL(a.ctx, "https://github.com/kakakaya/mazesoba-continent/blob/main/README.md")
}
}
return ""
Expand All @@ -54,7 +54,7 @@ func (a *App) DispatchCommand(text string) string {
case "profile":
{
handle_or_did := words[2]
OpenURL(fmt.Sprintf("https://bsky.app/profile/%s", handle_or_did))
runtime.BrowserOpenURL(a.ctx, fmt.Sprintf("https://bsky.app/profile/%s", handle_or_did))
}
case "search", "s":
{
Expand All @@ -71,11 +71,11 @@ func (a *App) DispatchCommand(text string) string {
q.Add("q", strings.Join(words[2:], " "))
req.URL.RawQuery = q.Encode()

OpenURL(req.URL.String())
runtime.BrowserOpenURL(a.ctx, req.URL.String())
}
default:
{
OpenURL(words[1]) // NOTE: TBC
runtime.BrowserOpenURL(a.ctx, words[1]) // NOTE: TBC
}
}
return ""
Expand Down Expand Up @@ -112,7 +112,7 @@ func (a *App) DispatchCommand(text string) string {
switch words[1] {
case "egosearch", "egs":
{
OpenURL("https://bsky.app/search?q=%E3%81%BE%E3%81%9C%E3%81%9D%E3%81%B0%E5%A4%A7%E9%99%B8")
runtime.BrowserOpenURL(a.ctx, "https://bsky.app/search?q=%E3%81%BE%E3%81%9C%E3%81%9D%E3%81%B0%E5%A4%A7%E9%99%B8")
}
}
return ""
Expand Down
103 changes: 103 additions & 0 deletions extract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// This file is copied from: https://github.com/mattn/bsky/blob/05cbf0a2797313b779950ee4afaa9adcf92c58f3/extract.go
package main

import (
"regexp"
"strings"
)

const (
urlPattern = `https?://[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+`
mentionPattern = `@[a-zA-Z0-9.]+`
tagPattern = `\B#\S+`
)

var (
urlRe = regexp.MustCompile(urlPattern)
mentionRe = regexp.MustCompile(mentionPattern)
tagRe = regexp.MustCompile(tagPattern)
)

type entry struct {
start int64
end int64
text string
}

func extractLinks(text string) []entry {
var result []entry
matches := urlRe.FindAllStringSubmatchIndex(text, -1)
for _, m := range matches {
result = append(result, entry{
text: text[m[0]:m[1]],
start: int64(len([]rune(text[0:m[0]]))),
end: int64(len([]rune(text[0:m[1]])))},
)
}
return result
}

func extractLinksBytes(text string) []entry {
var result []entry
matches := urlRe.FindAllStringSubmatchIndex(text, -1)
for _, m := range matches {
result = append(result, entry{
text: text[m[0]:m[1]],
start: int64(len(text[0:m[0]])),
end: int64(len(text[0:m[1]]))},
)
}
return result
}

func extractMentions(text string) []entry {
var result []entry
matches := mentionRe.FindAllStringSubmatchIndex(text, -1)
for _, m := range matches {
result = append(result, entry{
text: strings.TrimPrefix(text[m[0]:m[1]], "@"),
start: int64(len([]rune(text[0:m[0]]))),
end: int64(len([]rune(text[0:m[1]])))},
)
}
return result
}

func extractMentionsBytes(text string) []entry {
var result []entry
matches := mentionRe.FindAllStringSubmatchIndex(text, -1)
for _, m := range matches {
result = append(result, entry{
text: strings.TrimPrefix(text[m[0]:m[1]], "@"),
start: int64(len(text[0:m[0]])),
end: int64(len(text[0:m[1]]))},
)
}
return result
}

func extractTags(text string) []entry {
var result []entry
matches := tagRe.FindAllStringSubmatchIndex(text, -1)
for _, m := range matches {
result = append(result, entry{
text: strings.TrimPrefix(text[m[0]:m[1]], "#"),
start: int64(len([]rune(text[0:m[0]]))),
end: int64(len([]rune(text[0:m[1]])))},
)
}
return result
}

func extractTagsBytes(text string) []entry {
var result []entry
matches := tagRe.FindAllStringSubmatchIndex(text, -1)
for _, m := range matches {
result = append(result, entry{
text: strings.TrimPrefix(text[m[0]:m[1]], "#"),
start: int64(len(text[0:m[0]])),
end: int64(len(text[0:m[1]]))},
)
}
return result
}
2 changes: 1 addition & 1 deletion frontend/package.json.md5
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8bc973e96983b95b62df8eb19fc1e169
6faf32c9fcd1f3bdc344cce03c10821a
Loading

0 comments on commit 672859f

Please sign in to comment.