Death and rebirth
Signed-off-by: Shin'ya Minazuki <shinyoukai@laidback.moe>
This commit is contained in:
19
README.md
19
README.md
@@ -1,3 +1,20 @@
|
||||
# Yuki (有希)
|
||||
An incomplete client for ActivityPub, which shouldn't even be paid attention to.
|
||||
An alternative client for [WriteFreely](https://writefreely.org)
|
||||
|
||||
## Features
|
||||
* [X] Delete
|
||||
* [ ] Edit
|
||||
* [X] Login
|
||||
* [X] Logout
|
||||
* [X] Posting
|
||||
* [X] Drafts
|
||||
* [X] Font selection
|
||||
* [X] Language selection
|
||||
* [X] Syntax highlighting
|
||||
* [X] Publish to collection
|
||||
* [X] Right-to-left writing mode
|
||||
* [X] Logout
|
||||
* [ ] Update
|
||||
|
||||
## Usage
|
||||
TBD
|
||||
|
||||
10
Taskfile.yml
10
Taskfile.yml
@@ -7,8 +7,9 @@ env:
|
||||
GOTELEMETRY: off
|
||||
|
||||
vars:
|
||||
IMPORT: git.laidback.moe/shinyoukai/yuki
|
||||
VERSION: 0.0.0
|
||||
IMPORT: code.laidback.moe/yuki
|
||||
VERSION: 2026.01.21
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
@@ -16,15 +17,12 @@ tasks:
|
||||
build:
|
||||
desc: Build the interface
|
||||
cmds:
|
||||
- $GO build -v -ldflags='-w -X {{.IMPORT}}.Version=$VERSION' -buildvcs=false -o yuki
|
||||
silent: true
|
||||
- $GO build -v -ldflags='-w -X {{.IMPORT}}.Version={{.VERSION}}' -buildvcs=false -o yuki
|
||||
clean:
|
||||
desc: Remove generated files
|
||||
cmds:
|
||||
- rm -f yuki
|
||||
silent: true
|
||||
tidy:
|
||||
desc: Update go.mod
|
||||
cmds:
|
||||
- $GO mod tidy
|
||||
silent: true
|
||||
|
||||
86
auth.go
86
auth.go
@@ -1,86 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/mattn/go-mastodon"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var authCmd = &cobra.Command{
|
||||
Use: "auth",
|
||||
Short: "Authenticate to a Mastodon/Pleroma instance",
|
||||
Run: func(_ *cobra.Command, args []string) {
|
||||
doLogin()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ConfInit()
|
||||
rootCmd.AddCommand(authCmd)
|
||||
}
|
||||
|
||||
func doLogin() {
|
||||
client := &mastodon.AppConfig{
|
||||
Server: Config.Host,
|
||||
ClientName: "Yuki",
|
||||
Scopes: "read write follow",
|
||||
Website: "https://projects.laidback.moe/yuki/",
|
||||
RedirectURIs: "urn:ietf:wg:oauth:2.0:oob",
|
||||
}
|
||||
|
||||
app, err := mastodon.RegisterApp(context.Background(), client)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
u, err := url.Parse(app.AuthURI)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Go to \n%s\n and copy/paste the given auth code\n", u)
|
||||
var authCode string
|
||||
fmt.Print("Paste the code here:")
|
||||
fmt.Scanln(&authCode)
|
||||
|
||||
conf := &mastodon.Config{
|
||||
Server: Config.Host,
|
||||
ClientID: app.ClientID,
|
||||
ClientSecret: app.ClientSecret,
|
||||
}
|
||||
|
||||
c := mastodon.NewClient(conf)
|
||||
|
||||
err = c.GetUserAccessToken(context.Background(), authCode, app.RedirectURI)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
config, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
log.Println("Unable to obtain user's configuration directory")
|
||||
log.Fatal(err)
|
||||
}
|
||||
configPath := config + "/yuki.ini"
|
||||
|
||||
f, err := os.OpenFile(configPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
client_id := c.Config.ClientID
|
||||
client_secret := c.Config.ClientSecret
|
||||
access_token := c.Config.AccessToken
|
||||
|
||||
f.WriteString("client_id = " + client_id + "\n")
|
||||
f.WriteString("client_secret = " + client_secret + "\n")
|
||||
f.WriteString("token = " + access_token + "\n")
|
||||
}
|
||||
@@ -7,12 +7,14 @@ import (
|
||||
)
|
||||
|
||||
var Config struct {
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
Host string
|
||||
Token string
|
||||
}
|
||||
|
||||
var (
|
||||
ConfigPath string
|
||||
)
|
||||
|
||||
func ConfInit() {
|
||||
config, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
@@ -29,8 +31,6 @@ func Parse(file string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
Config.ClientID = cfg.Section("yuki").Key("client_id").String()
|
||||
Config.ClientSecret = cfg.Section("yuki").Key("client_secret").String()
|
||||
Config.Host = cfg.Section("yuki").Key("host").String()
|
||||
Config.Token = cfg.Section("yuki").Key("token").String()
|
||||
|
||||
|
||||
52
delete.go
Normal file
52
delete.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"code.laidback.moe/go-writefreely"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
id string
|
||||
)
|
||||
|
||||
var deleteCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Aliases: []string{"rm"},
|
||||
Short: "Permanently delete a published post",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
id, err := cmd.Flags().GetString("id")
|
||||
if err != nil {
|
||||
log.Fatal("Unable to get id flag")
|
||||
}
|
||||
DoDelete(id, args)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ConfInit()
|
||||
rootCmd.AddCommand(deleteCmd)
|
||||
deleteCmd.Flags().StringVar(&id, "id", "", "Post ID")
|
||||
}
|
||||
|
||||
func DoDelete(id string, args []string) {
|
||||
writefreely.InstanceURL = Config.Host
|
||||
|
||||
if len(id) == 0 {
|
||||
log.Fatal("No ID has been provided!")
|
||||
}
|
||||
|
||||
c := writefreely.NewClient()
|
||||
c.SetToken(Config.Token)
|
||||
|
||||
err := c.DeletePost(&writefreely.PostParams{
|
||||
ID: id,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Post has been deleted")
|
||||
}
|
||||
10
go.mod
10
go.mod
@@ -1,19 +1,21 @@
|
||||
module git.laidback.moe/shinyoukai/yuki
|
||||
module code.laidback.moe/yuki
|
||||
|
||||
go 1.25.2
|
||||
|
||||
require (
|
||||
github.com/mattn/go-mastodon v0.0.10
|
||||
code.laidback.moe/go-writefreely v1.3.1
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/tj/go-editor v1.0.0
|
||||
golang.org/x/term v0.39.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
code.as/core/socks v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/pkg/errors v0.8.1 // indirect
|
||||
github.com/spf13/pflag v1.0.9 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
|
||||
github.com/writeas/impart v1.1.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
)
|
||||
|
||||
16
go.sum
16
go.sum
@@ -1,12 +1,12 @@
|
||||
code.as/core/socks v1.0.0 h1:SPQXNp4SbEwjOAP9VzUahLHak8SDqy5n+9cm9tpjZOs=
|
||||
code.as/core/socks v1.0.0/go.mod h1:BAXBy5O9s2gmw6UxLqNJcVbWY7C/UPs+801CcSsfWOY=
|
||||
code.laidback.moe/go-writefreely v1.3.1 h1:Gl8AYswfjx+yITYYwp79gPmmtEUn9Vjyr1Kr1EjqafM=
|
||||
code.laidback.moe/go-writefreely v1.3.1/go.mod h1:wt1ofi/PFoZ/rFFY08wfGRf8WROEpFxc1ueMgzMlrL8=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/mattn/go-mastodon v0.0.10 h1:wz1d/aCkJOIkz46iv4eAqXHVreUMxydY1xBWrPBdDeE=
|
||||
github.com/mattn/go-mastodon v0.0.10/go.mod h1:YBofeqh7G6s787787NQR8erBYz6fKDu+KNMrn5RuD6Y=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -20,9 +20,13 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tj/go-editor v1.0.0 h1:VduwRZWk5gxHbzRkS4buB9BIFzAf6Jc+vd2bLZpHzSw=
|
||||
github.com/tj/go-editor v1.0.0/go.mod h1:Jj9Ze2Rn2yj5DmMppydZqr9LbkIcCWrehzZ+7udOT+w=
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
|
||||
github.com/writeas/impart v1.1.0 h1:nPnoO211VscNkp/gnzir5UwCDEvdHThL5uELU60NFSE=
|
||||
github.com/writeas/impart v1.1.0/go.mod h1:g0MpxdnTOHHrl+Ca/2oMXUHJ0PcRAEWtkCzYCJUXC9Y=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
|
||||
72
login.go
Normal file
72
login.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"code.laidback.moe/go-writefreely"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var loginCmd = &cobra.Command{
|
||||
Use: "login",
|
||||
Short: "Login to a WriteFreely instance",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
DoLogin(args)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ConfInit()
|
||||
rootCmd.AddCommand(loginCmd)
|
||||
}
|
||||
|
||||
// DoLogin creates a new client, passing the host defined
|
||||
// in the configuration file as writefreely.InstanceURL,
|
||||
// and authenticates to the instance.
|
||||
func DoLogin(args []string) {
|
||||
var user, pass string
|
||||
writefreely.InstanceURL = Config.Host
|
||||
|
||||
c := writefreely.NewClient()
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
fmt.Print("Username: ")
|
||||
user, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Print("Password: ")
|
||||
data, err := term.ReadPassword(int(syscall.Stdin))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
pass = string(data)
|
||||
|
||||
u, err := c.Login(user, pass)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cfg, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cfgPath := cfg + "/yuki.ini"
|
||||
|
||||
f, err := os.OpenFile(cfgPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
f.WriteString("token = " + u.AccessToken + "\n")
|
||||
|
||||
log.Println("Login successful")
|
||||
}
|
||||
36
logout.go
Normal file
36
logout.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"code.laidback.moe/go-writefreely"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var logoutCmd = &cobra.Command{
|
||||
Use: "logout",
|
||||
Short: "Log out from WriteFreely, invalidating the token",
|
||||
Run: func(_ *cobra.Command, args []string) {
|
||||
DoLogout(args)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ConfInit()
|
||||
rootCmd.AddCommand(logoutCmd)
|
||||
}
|
||||
|
||||
func DoLogout(args []string) {
|
||||
writefreely.InstanceURL = Config.Host
|
||||
|
||||
c := writefreely.NewClient()
|
||||
c.SetToken(Config.Token)
|
||||
|
||||
err := c.Logout()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Logged out")
|
||||
log.Println("Remove the token from the configuration file")
|
||||
}
|
||||
2
main.go
2
main.go
@@ -1,5 +1,7 @@
|
||||
// This file is part of Yuki
|
||||
package main
|
||||
|
||||
// Offload all processing to Cobra
|
||||
func main() {
|
||||
Execute()
|
||||
}
|
||||
|
||||
116
post.go
116
post.go
@@ -1,66 +1,108 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/mattn/go-mastodon"
|
||||
"code.laidback.moe/go-writefreely"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tj/go-editor"
|
||||
)
|
||||
|
||||
var (
|
||||
visibility string
|
||||
collection string
|
||||
font string
|
||||
lang string
|
||||
rtl bool
|
||||
title string
|
||||
)
|
||||
|
||||
var postCmd = &cobra.Command{
|
||||
Use: "post",
|
||||
Short: "Send a message",
|
||||
Run: func(_ *cobra.Command, args []string) {
|
||||
doPost(args)
|
||||
Use: "post",
|
||||
Short: "Create a new post on the WriteFreely instance",
|
||||
Aliases: []string{"new"},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
collection, err := cmd.Flags().GetString("collection")
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't get the collection flag")
|
||||
}
|
||||
font, err := cmd.Flags().GetString("font")
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't get the font flag")
|
||||
}
|
||||
lang, err := cmd.Flags().GetString("lang")
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't get the language flag")
|
||||
}
|
||||
rtl, err := cmd.Flags().GetBool("rtl")
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't get the right-to-left flag")
|
||||
}
|
||||
title, err := cmd.Flags().GetString("title")
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't get the title flag")
|
||||
}
|
||||
|
||||
DoPost(collection, font, lang, rtl, title, args)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ConfInit()
|
||||
rootCmd.AddCommand(postCmd)
|
||||
|
||||
postCmd.Flags().StringVarP(&visibility, "scope", "s", "public", "Visibility")
|
||||
postCmd.Flags().StringVarP(&collection, "collection", "b", "", "Location for the post (default is Drafts if unset)")
|
||||
postCmd.Flags().StringVarP(&font, "font", "f", "mono", "Which font to use for presentation")
|
||||
postCmd.Flags().StringVarP(&lang, "lang", "l", "en", "Post language")
|
||||
postCmd.Flags().BoolVarP(&rtl, "rtl", "r", false, "Whether to use right-to-left writing style (common in Middle Eastern languages)")
|
||||
postCmd.Flags().StringVarP(&title, "title", "t", "", "Title for the entry")
|
||||
}
|
||||
|
||||
func doPost(args []string) {
|
||||
var text string
|
||||
func DoPost(collection, font, lang string, rtl bool, title string, args []string) {
|
||||
writefreely.InstanceURL = Config.Host
|
||||
var err error
|
||||
var w *writefreely.Post
|
||||
|
||||
if rtl {
|
||||
log.Println("Using right-to-left writing style")
|
||||
}
|
||||
if len(title) == 0 {
|
||||
log.Fatal("No title specified!")
|
||||
}
|
||||
|
||||
data, err := editor.Read()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal("Unable to read content from editor")
|
||||
}
|
||||
|
||||
post := string(data)
|
||||
|
||||
c := writefreely.NewClient()
|
||||
c.SetToken(Config.Token)
|
||||
|
||||
if len(collection) == 0 {
|
||||
w, err = c.CreatePost(&writefreely.PostParams{
|
||||
Title: title,
|
||||
Content: post,
|
||||
Font: font,
|
||||
Language: &lang,
|
||||
IsRTL: &rtl,
|
||||
})
|
||||
} else {
|
||||
w, err = c.CreatePost(&writefreely.PostParams{
|
||||
Title: title,
|
||||
Content: post,
|
||||
Font: font,
|
||||
Collection: collection,
|
||||
Language: &lang,
|
||||
IsRTL: &rtl,
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
text = string(data)
|
||||
log.Println("Post created")
|
||||
log.Printf("%s/%s\n", Config.Host, w.ID)
|
||||
|
||||
conf := &mastodon.Config{
|
||||
Server: Config.Host,
|
||||
ClientID: Config.ClientID,
|
||||
ClientSecret: Config.ClientSecret,
|
||||
AccessToken: Config.Token,
|
||||
}
|
||||
|
||||
c := mastodon.NewClient(conf)
|
||||
|
||||
_, err = c.GetAccountCurrentUser(context.Background())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
|
||||
note := mastodon.Toot{
|
||||
Status: text,
|
||||
Visibility: visibility,
|
||||
}
|
||||
|
||||
_, err = c.PostStatus(context.Background(), ¬e)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("%#v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user