masto.go (view raw)
1package main
2
3import (
4 "bytes"
5 "crypto/rand"
6 "fmt"
7 "net/http"
8 "time"
9
10 "humungus.tedunangst.com/r/webs/junk"
11 "humungus.tedunangst.com/r/webs/login"
12)
13
14type NotResponseWriter struct {
15 body bytes.Buffer
16 header http.Header
17}
18
19func (w NotResponseWriter) Header() http.Header {
20 return w.header
21}
22
23func (w NotResponseWriter) Write(b []byte) (int, error) {
24 return w.body.Write(b)
25}
26
27func (w NotResponseWriter) WriteHeader(statusCode int) {}
28
29func snowflake() uint64 {
30 ts := time.Now()
31 return uint64(uint64(ts.UnixNano()/int64(time.Millisecond))<<16 | uint64(time.Now().Nanosecond()&0xffff))
32}
33
34func badjunk(rw http.ResponseWriter, j junk.Junk, path string) {
35 elog.Printf("%s: bad junk: %s", path, j.ToString())
36 http.Error(rw, fmt.Sprintf("%s: bad junk", path), http.StatusBadRequest)
37}
38
39func goodjunk(rw http.ResponseWriter, j junk.Junk) {
40 rw.WriteHeader(http.StatusOK)
41 rw.Header().Set("Content-Type", "application/json; charset=utf-8")
42 rw.Write(j.ToBytes())
43}
44
45func showoauthlogin(rw http.ResponseWriter, r *http.Request) {
46 templinfo := make(map[string]interface{})
47 templinfo = getInfo(r)
48 if err := readviews.Execute(rw, "oauthlogin.html", templinfo); err != nil {
49 elog.Println(err)
50 }
51}
52
53// https://docs.joinmastodon.org/methods/apps/#create
54func apiapps(rw http.ResponseWriter, r *http.Request) {
55 if err := r.ParseForm(); err != nil {
56 http.Error(rw, "invalid input", http.StatusUnprocessableEntity)
57 elog.Println(err)
58 return
59 }
60 clientName := r.Form.Get("client_name")
61 redirectUri := r.Form.Get("redirect_uris")
62 scopes := r.Form.Get("scopes")
63 website := r.Form.Get("website")
64 clientID := tokengen()
65 clientSecret := tokengen()
66 vapidKey := tokengen()
67
68 _, err := stmtSaveMastoApp.Exec(clientName, redirectUri, scopes, clientID, clientSecret, vapidKey)
69 if err != nil {
70 elog.Printf("error saving masto app: %v", err)
71 http.Error(rw, "error saving masto app", http.StatusUnprocessableEntity)
72 return
73 }
74
75 j := junk.New()
76 j["id"] = fmt.Sprintf("%d", snowflake())
77 j["website"] = website
78 j["name"] = clientName
79 j["redirect_uri"] = redirectUri
80 j["client_id"] = clientID
81 j["client_secret"] = clientSecret
82 j["vapid_key"] = vapidKey
83
84 fmt.Println(j.ToString())
85 goodjunk(rw, j)
86}
87
88func tokengen() string {
89 b := make([]byte, 32)
90 rand.Read(b)
91 return fmt.Sprintf("%x", b)
92}
93
94// https://docs.joinmastodon.org/methods/oauth/#authorize
95func oauthorize(rw http.ResponseWriter, r *http.Request) {
96 nrw := NotResponseWriter{}
97 login.LoginFunc(nrw, r)
98 dlog.Println("got code!", string(nrw.body))
99 rw.WriteHeader(http.StatusOK)
100 return
101}
102
103// https://docs.joinmastodon.org/methods/instance/#v2
104func instance(rw http.ResponseWriter, r *http.Request) {
105 j := junk.New()
106
107 var servername string
108 if err := getconfig("servername", &servername); err != nil {
109 http.Error(rw, "getting servername", http.StatusInternalServerError)
110 return
111 }
112
113 j["uri"] = servername
114 j["title"] = "honk"
115 j["description"] = "federated honk conveyance"
116 j["version"] = "develop"
117
118 thumbnail := junk.New()
119 thumbnail["url"] = fmt.Sprintf("https://%s/icon.png", servername)
120 j["thumbnail"] = thumbnail
121 j["languages"] = []string{"en"}
122
123 config := junk.New()
124
125 a := junk.New()
126 a["max_featured_tags"] = 10
127 config["accounts"] = a
128
129 s := junk.New()
130 s["max_characters"] = 5000
131 s["max_media_attachments"] = 1
132 s["characters_reserved_per_url"] = 23
133 config["statuses"] = s
134
135 m := junk.New()
136 m["supported_mime_types"] = []string{
137 "image/jpeg",
138 "image/png",
139 "image/gif",
140 "image/heic",
141 "image/heif",
142 "image/webp",
143 "image/avif",
144 "video/webm",
145 "video/mp4",
146 "video/quicktime",
147 "video/ogg",
148 "audio/wave",
149 "audio/wav",
150 "audio/x-wav",
151 "audio/x-pn-wave",
152 "audio/vnd.wave",
153 "audio/ogg",
154 "audio/vorbis",
155 "audio/mpeg",
156 "audio/mp3",
157 "audio/webm",
158 "audio/flac",
159 "audio/aac",
160 "audio/m4a",
161 "audio/x-m4a",
162 "audio/mp4",
163 "audio/3gpp",
164 "video/x-ms-asf",
165 }
166
167 m["image_size_limit"] = 10485760
168 m["image_matrix_limit"] = 16777216
169 m["video_size_limit"] = 41943040
170 m["video_frame_rate_limit"] = 60
171 m["video_matrix_limit"] = 2304000
172 j["media_attachments"] = m
173
174 rw.WriteHeader(http.StatusOK)
175 rw.Write(j.ToBytes())
176
177 return
178}