some this is very exciting.
The increase in volume has affected the administrators more than anyone, no
-doubht, but the users have also been affected. While Mastodon's filters and feed
+doubt, but the users have also been affected. While Mastodon's filters and feed
settings are exceedingly powerful, there are a few more things I'd like to be
able to do with my Fedi data.
rootCmd.AddCommand(cmdApp)
}
-/*
-dead-tooter app create --client-name ""
-*/
-
var cmdApp = &cobra.Command{
Use: "application",
Short: "Register and perform application-level actions",
"github.com/spf13/cobra"
)
+var host string
+
func init() {
+ loginCmd.Flags().StringVarP(&host,
+ "host", "H", "", "Mastodon host where your account lives.")
+ loginCmd.MarkFlagRequired("host")
+
rootCmd.AddCommand(loginCmd)
}
Short: "Login to Mastodon",
Long: "Initiate login to your Mastodon server of choice",
Run: func(cmd *cobra.Command, args []string) {
+ login()
},
}
Scopes: "read write follow push",
Website: "https://dead-tooter.vexingworkshop.com",
}
- app, err = mastodon.Create(client)
+
+ app, err = mastodon.Create(host, client)
+ if err != nil {
+ panic(err.Error())
+ }
+
+ err = app.Save()
if err != nil {
panic(err.Error())
}
- app.Save()
}
var account mastodon.Account
- code := account.Authorize(app)
- token, err := account.RequestToken(app, code)
+ code := account.Authorize(host, app)
+ token, err := account.RequestToken(host, app, code)
if err != nil {
panic(err.Error())
}
- err = account.VerifyCredentials(token)
+ err = account.VerifyCredentials(host, token)
if err != nil {
panic(err.Error())
}
"os/exec"
)
+// Account represents a user of Mastodon and their associated profile.
type Account struct {
ID string `json:"id"`
UserName string `json:"username"`
Fields []Field `json:"fields"`
}
+// Source is an extra attribute that contains source values to be used with API
+// methods that verify and update credentials.
type Source struct {
Privacy string `json:"privacy"`
Sensitive bool `json:"sensitive"`
FollowRequestsCount int `json:"follow_requests_count"`
}
+// Field stores custom field data on the user account.
type Field struct {
Name string `json:"name"`
Value string `json:"value"`
VerifiedAt string `json:"verified_at"`
}
+// Emoji is the emoji portion of the user account.
type Emoji struct {
ShortCode string `json:"shortcode"`
- Url string `json:"url"`
- StaticUrl string `json:"static_url"`
+ URL string `json:"url"`
+ StaticURL string `json:"static_url"`
VisibleInPicker bool `json:"visible_in_picker"`
}
-func (a *Account) Authorize(app Application) (code string) {
+// Authorize opens the default browser to initiate the authorization flow
+// for the current user.
+func (a *Account) Authorize(host string, app Application) (code string) {
v := url.Values{}
v.Set("client_id", app.ClientID)
v.Set("response_type", "code")
v.Set("redirect_uri", RedirectUris)
u := url.URL{
- Host: mastohost,
+ Host: host,
Path: "oauth/authorize",
RawQuery: v.Encode(),
}
return
}
-func (a *Account) RequestToken(app Application, code string) (token Token, err error) {
+// RequestToken takes the provided authorization code and returns a structure
+// containing a bearer token necessary to make future, authenticated requests
+// on the part of the user.
+func (a *Account) RequestToken(
+ host string, app Application, code string) (token Token, err error) {
+
v := url.Values{}
v.Set("client_id", app.ClientID)
v.Set("client_secret", app.ClientSecret)
v.Set("grant_type", "authorization_code")
u := url.URL{
- Host: mastohost,
+ Host: host,
Path: "oauth/token",
RawQuery: v.Encode(),
}
return
}
-func (a *Account) VerifyCredentials(token Token) (err error) {
+// VerifyCredentials hydrates the account object by validating the bearer token
+// against the Mastodon API
+func (a *Account) VerifyCredentials(host string, token Token) (err error) {
client := &http.Client{}
u := url.URL{
- Host: mastohost,
+ Host: host,
Path: "api/v1/accounts/verify_credentials",
}
u.Scheme = "https"
return
}
-func (a *Account) GetFollowers(token Token) (followers []Account, err error) {
+// GetFollowers returns a list of all accounts following the logged in user
+func (a *Account) GetFollowers(
+ host string, token Token) (followers []Account, err error) {
+
client := &http.Client{}
id := url.PathEscape(a.ID)
}
u := url.URL{
- Host: mastohost,
+ Host: host,
Path: path,
}
u.Scheme = "https"
)
const configdir = "foodir/"
-const mastohost = "hackers.town"
// RedirectUris when passed to the redirect_uris parameter, will
// show return the authorization code instead of redirecting the client.
CreatedAt int `json:"created_at"`
}
+// APIError contains the application specific error message returned
+// by the Mastodon API.
+type APIError struct {
+ Error string `json:"error"`
+ ErrorDescription string `json:"error_description"`
+}
+
// Application represents the data and functions that establish a
// Mastodon API application.
type Application struct {
}
// Create calls the Mastodon API to regsiter the application.
-func Create(client Client) (app Application, err error) {
+func Create(host string, client Client) (app Application, err error) {
v := url.Values{}
v.Set("client_name", client.ClientName)
v.Set("redirect_uris", client.RedirectUris)
v.Set("website", client.Website)
u := url.URL{
- Host: mastohost,
- Path: "oauth/token",
- RawQuery: v.Encode(),
+ Host: host,
+ Path: "api/v1/apps",
}
u.Scheme = "https"
resp, err := http.PostForm(u.String(), v)
if err != nil {
- panic(err.Error())
+ return
+ }
+ if resp.StatusCode != 200 {
+ err = errors.New(resp.Status)
+ return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return
+ }
err = json.Unmarshal(body, &app)
return
}
// Save will store a serialized version of the Application struct to a file.
-func (a *Application) Save() {
- err := os.Mkdir(configdir, 0750)
+func (a *Application) Save() (err error) {
+ err = os.Mkdir(configdir, 0750)
if err != nil && !os.IsExist(err) {
- panic(err.Error())
+ return
}
data, err := json.Marshal(a)
if err != nil {
- panic(err.Error())
+ return
}
err = os.WriteFile(configdir+a.Name, data, 0666)
if err != nil {
- panic(err.Error())
+ return
}
+
+ return nil
}
// Login authenticates the application to the Mastodon API, returning
// a bearer token to be used with future requests.
-func (a *Application) Login() (token Token, err error) {
+func (a *Application) Login(host string) (token Token, err error) {
v := url.Values{}
v.Set("client_id", a.ClientID)
v.Set("client_secret", a.ClientSecret)
v.Set("grant_type", "client_credentials")
u := url.URL{
- Host: mastohost,
+ Host: host,
Path: "oauth/token",
RawQuery: v.Encode(),
}
// VerifyCredentials accepts a Token object and validates the contained
// token against the Mastodon API.
-func (a *Application) VerifyCredentials(token Token) (err error) {
+func (a *Application) VerifyCredentials(host string, token Token) (err error) {
client := &http.Client{}
u := url.URL{
- Host: mastohost,
+ Host: host,
Path: "api/v1/apps/verify_credentials",
}
u.Scheme = "https"