Yuki N. > COMMIT;

Signed-off-by: Shin'ya Minazuki <shinyoukai@laidback.moe>
This commit is contained in:
2026-01-03 11:31:22 -03:00
commit 3736c10d58
12 changed files with 328 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/yuki

15
COPYING Normal file
View File

@@ -0,0 +1,15 @@
ISC License (ISCL)
Copyright (C) 2026 Shin'ya Minazuki <shinyoukai@laidback.moe>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# Yuki (有希)
An incomplete client for ActivityPub, which shouldn't even be paid attention to.

30
Taskfile.yml Normal file
View File

@@ -0,0 +1,30 @@
# https://taskfile.dev
version: '3'
env:
GO: go
GOTELEMETRY: off
vars:
IMPORT: git.laidback.moe/shinyoukai/yuki
VERSION: 0.0.0
tasks:
default:
cmds:
- task: build
build:
desc: Build the interface
cmds:
- $GO build -v -ldflags='-w -X {{.IMPORT}}.Version=$VERSION' -buildvcs=false -o yuki
silent: true
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 Normal file
View File

@@ -0,0 +1,86 @@
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")
}

38
config.go Normal file
View File

@@ -0,0 +1,38 @@
package main
import (
"log"
"os"
"gopkg.in/ini.v1"
)
var Config struct {
ClientID string
ClientSecret string
Host string
Token string
}
func ConfInit() {
config, err := os.UserConfigDir()
if err != nil {
log.Println("Unable to obtain user's configuration directory")
log.Fatal(err)
}
configPath := config + "/yuki.ini"
Parse(configPath)
}
func Parse(file string) error {
cfg, err := ini.Load(file)
if err != nil {
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()
return nil
}

19
go.mod Normal file
View File

@@ -0,0 +1,19 @@
module git.laidback.moe/shinyoukai/yuki
go 1.25.2
require (
github.com/mattn/go-mastodon v0.0.10
github.com/spf13/cobra v1.10.2
github.com/tj/go-editor v1.0.0
gopkg.in/ini.v1 v1.67.0
)
require (
github.com/gorilla/websocket v1.5.3 // 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
)

30
go.sum Normal file
View File

@@ -0,0 +1,30 @@
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=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
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=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

5
main.go Normal file
View File

@@ -0,0 +1,5 @@
package main
func main() {
Execute()
}

66
post.go Normal file
View File

@@ -0,0 +1,66 @@
package main
import (
"context"
"log"
"github.com/mattn/go-mastodon"
"github.com/spf13/cobra"
"github.com/tj/go-editor"
)
var (
visibility string
)
var postCmd = &cobra.Command{
Use: "post",
Short: "Send a message",
Run: func(_ *cobra.Command, args []string) {
doPost(args)
},
}
func init() {
ConfInit()
rootCmd.AddCommand(postCmd)
postCmd.Flags().StringVarP(&visibility, "scope", "s", "public", "Visibility")
}
func doPost(args []string) {
var text string
data, err := editor.Read()
if err != nil {
log.Fatal(err)
}
text = string(data)
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(), &note)
if err != nil {
log.Fatalf("%#v\n", err)
}
}

18
root.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"log"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "yuki",
Short: "An extraterrestial (but limited) activitypub client",
}
func Execute() {
err := rootCmd.Execute()
if err != nil {
log.Fatal(err)
}
}

17
version.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import (
"fmt"
)
var (
Revision = "0"
Version = "0"
)
func FullVersion() string {
return fmt.Sprintf("%s (r%s)", Version, Revision)
}
func PrintVersion() string {
return fmt.Sprintf("%s", Version)
}