ref: 35959699c979b584314702e7d6f27c6cebf25c69
dir: /mastodon.go/
package main
import (
"bytes"
"context"
"fmt"
"log"
"net/url"
"strings"
"github.com/k3a/html2text"
"github.com/mattn/go-mastodon"
"golang.org/x/net/html"
)
func ConfigureClient() *mastodon.Client {
appConfig := &mastodon.AppConfig{
Server: "https://eldritch.cafe",
ClientName: "hellclient",
Scopes: "read write follow",
Website: "https://github.com/mattn/go-mastodon",
RedirectURIs: "urn:ietf:wg:oauth:2.0:oob",
}
app, err := mastodon.RegisterApp(context.Background(), appConfig)
if err != nil {
log.Fatal(err)
}
// Have the user manually get the token and send it back to us
u, err := url.Parse(app.AuthURI)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Open your browser to \n%s\n and copy/paste the given authroization code\n", u)
var userAuthorizationCode string
fmt.Print("Paste the code here:")
fmt.Scanln(&userAuthorizationCode)
config := &mastodon.Config{
Server: "https://eldritch.cafe",
ClientID: app.ClientID,
ClientSecret: app.ClientSecret,
}
// Create the client
c := mastodon.NewClient(config)
// Exchange the User authentication code with an access token, that can be used to interact with the api on behalf of the user
err = c.GetUserAccessToken(context.Background(), userAuthorizationCode, app.RedirectURI)
if err != nil {
log.Fatal(err)
}
return c
}
func initClient(account *account) *mastodon.Client {
clientID := account.MASTODON_CLIENT_ID
clientSecret := account.MASTODON_CLIENT_ID
accessToken := account.MASTODON_ACCESS_TOKEN
url := account.URL
config := &mastodon.Config{
Server: url,
ClientID: clientID,
ClientSecret: clientSecret,
AccessToken: accessToken,
}
c := mastodon.NewClient(config)
return c
}
func renderStatus(content string) string {
doc, err := html.Parse(strings.NewReader(content))
if err != nil {
log.Fatal(err)
}
for node := range doc.Descendants() {
if node.Data == "a" {
for attr := range node.Attr {
if node.Attr[attr].Key == "class" && strings.Contains(node.Attr[attr].Val, "mention") {
node.Data = "span"
}
}
}
}
//Rip off the HTML body the parser made for us
for node := range doc.Descendants() {
if node.Data == "body" {
node.Type = html.DocumentNode
doc = node
}
}
var rendered bytes.Buffer
err = html.Render(&rendered, doc)
if err != nil {
log.Fatal(err)
}
return rendered.String()
}
func postReply(posttext string, account *account, client mastodon.Client, visibility string, replyto mastodon.ID) (status *mastodon.Status, err error) {
toot := mastodon.Toot{
Status: posttext,
Visibility: visibility,
InReplyToID: replyto,
}
status, err = postStatusDetailed(posttext, account, client, visibility, toot)
return
}
func postStatus(posttext string, account *account, client mastodon.Client, visibility string) (status *mastodon.Status, err error) {
// Post a toot
toot := mastodon.Toot{
Status: posttext,
Visibility: visibility,
}
status, err = postStatusDetailed(posttext, account, client, visibility, toot)
return
}
func postStatusDetailed(posttext string, account *account, client mastodon.Client, visibility string, toot mastodon.Toot) (status *mastodon.Status, err error) {
status, err = client.PostStatus(context.Background(), &toot)
if err != nil {
printMastodonErr(err)
return
}
return
}
func getUserString(post *mastodon.Status) string {
return post.Account.Acct
}
func formatAccount(account *mastodon.Account) string {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("%v\n", account.DisplayName))
sb.WriteString(fmt.Sprintf("%v <%v>\n", account.Username, account.Acct))
sb.WriteString(fmt.Sprintf("Posts: %v Followers: %v Following: %v\n", account.StatusesCount, account.FollowersCount, account.FollowingCount))
sb.WriteString(fmt.Sprintf("Created %v\n", account.CreatedAt))
sb.WriteString(fmt.Sprintf("Locked: %v\n", account.Locked))
sb.WriteString(fmt.Sprintf("%v\n\n", html2text.HTML2Text(account.Note)))
return sb.String()
}
// Spaces before prefixes (no space if you're not passing a prefix)
func formatReblog(post *mastodon.Status, index string) string {
reblogString := fmt.Sprintf(" <%v> Reblogged", post.Account.Username)
return hyphenate(formatStatusDetailed(post.Reblog, index, reblogString))
}
func formatFavorite(post *mastodon.Status, index string) string {
favString := fmt.Sprintf("Favorited: %v", index)
return hyphenate(formatStatusDetailed(post, "", favString))
}
func formatBookmark(post *mastodon.Status, index string) string {
favString := fmt.Sprintf("Bookmarked: %v", index)
return hyphenate(formatStatusDetailed(post, "", favString))
}
func formatUnbookmark(post *mastodon.Status, index string) string {
favString := fmt.Sprintf("Unbookmarked: %v", index)
return hyphenate(formatStatusDetailed(post, "", favString))
}
func formatStatus(post *mastodon.Status, index string) string {
return formatStatusDetailed(post, index, " ")
}
func formatStatusDetailed(post *mastodon.Status, index string, prefix string) string {
renderedPost := renderStatus(post.Content)
return fmt.Sprintf("%v%v <%v> %v", index, prefix, post.Account.Username, html2text.HTML2Text(renderedPost))
}
func formatEdit(post *mastodon.Status, index string) string {
editString := fmt.Sprintf(" <%v> EDITED:", post.Account.Username)
return hyphenate(formatStatusDetailed(post, index, editString))
}
func printMastodonErr(err error) {
fmt.Printf("\r%w\n", err)
}
func printPost(postref string, post *mastodon.Status) *mastodon.Status {
return printPostDetailed(postref, post, "")
}
func printPostDetailed(postref string, post *mastodon.Status, prefix string) *mastodon.Status {
post, plaintext := RenderPostPlaintext(post, postref, prefix)
fmt.Println(plaintext)
return post
}
func RenderPostPlaintext(post *mastodon.Status, postref string, prefix string) (selectedPost *mastodon.Status, plaintext string) {
poststring := ""
postfix := ""
var media []mastodon.Attachment
if post.Reblog != nil {
poststring = formatReblog(post, postref)
selectedPost = post.Reblog
media = post.Reblog.MediaAttachments
} else {
poststring = formatStatusDetailed(post, postref, prefix)
selectedPost = post
media = post.MediaAttachments
}
for _, item := range media {
if item.Description != "" {
postfix += fmt.Sprintf("\n🖼️[%v]", item.Description)
continue
}
postfix += "🖼️"
}
plaintext = hyphenate(fmt.Sprintf("%v %v", poststring, postfix))
return
}
func StreamHomeTimeline(client *mastodon.Client, postMap map[string]*mastodon.Status, hc *Hellclient) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
eventCh, err := client.StreamingUser(ctx)
if err != nil {
log.Fatalf("Error starting user stream: %v\n", err)
}
initReferenceSystem()
postref := "a"
plaintext := ""
idmap := make(map[mastodon.ID]*mastodon.Status)
isPaused := false
var actionBuffer []func()
// Enter a loop to continuously listen for events from the event channel.
for {
select {
case event, ok := <-eventCh: // Read from the event channel, checking 'ok' for closure
if !ok {
// The channel was closed, which indicates the stream has ended.
fmt.Println("Stream closed.\n")
return // Exit the function
}
func() {
func() {
hc.lock()
defer hc.unlock()
newPauseState := hc.isPaused
if newPauseState == isPaused {
return
}
isPaused = newPauseState
if !isPaused {
fmt.Println("Output resumed")
for _, action := range actionBuffer {
action()
}
actionBuffer = nil
}
}()
hc.lock()
defer hc.unlock()
switch post := event.(type) {
case *mastodon.UpdateEvent:
if isPaused {
currentPostRef := postref
capturedPost := *post
actionBuffer = append(actionBuffer, func() {
capturedPost.Status = printPost(currentPostRef, capturedPost.Status)
})
} else {
post.Status = printPost(postref, post.Status)
}
saveRef(postMap, post.Status, postref)
idmap[post.Status.ID] = post.Status
postref = IncrementString(postref)
case *mastodon.UpdateEditEvent:
if isPaused {
currentPostRef := postref
capturedPost := post
actionBuffer = append(actionBuffer, func() {
fmt.Println(formatEdit(capturedPost.Status, currentPostRef))
})
} else {
fmt.Println(formatEdit(post.Status, postref))
}
saveRef(postMap, post.Status, postref)
postref = IncrementString(postref)
idmap[post.Status.ID] = post.Status
case *mastodon.DeleteEvent:
deleted, ok := idmap[post.ID]
//didn't have this in the cache
if !ok {
capturedID := post.ID
if isPaused {
actionBuffer = append(actionBuffer, func() {
fmt.Printf("Deleted: ID %v\n", capturedID)
})
} else {
fmt.Printf("Deleted: ID %v\n", capturedID)
}
return
}
if isPaused {
actionBuffer = append(actionBuffer, func() {
printPostDetailed("", deleted, "Deleted:")
})
} else {
printPostDetailed("", deleted, "Deleted:")
}
return
case *mastodon.NotificationEvent:
if post.Notification.Status == nil {
notification := fmt.Sprintf("Notification [%v] from <%v>\n", post.Notification.Type, post.Notification.Account.Acct)
if isPaused {
actionBuffer = append(actionBuffer, func() {
fmt.Printf(hyphenate(notification))
})
} else {
fmt.Printf(hyphenate(notification))
}
return
}
_, plaintext = RenderPostPlaintext(post.Notification.Status, postref, "")
notification := fmt.Sprintf("Notification [%v] from <%v>: %v\n", post.Notification.Type, post.Notification.Account.Acct, plaintext)
if isPaused {
actionBuffer = append(actionBuffer, func() {
fmt.Printf(hyphenate(notification))
})
} else {
fmt.Printf(hyphenate(notification))
}
saveRef(postMap, post.Notification.Status, postref)
postref = IncrementString(postref)
default:
// Catch any other unexpected event types.
unhandledEvent := event
if isPaused {
actionBuffer = append(actionBuffer, func() {
fmt.Printf("Unhandled event: %T\n", unhandledEvent)
})
} else {
fmt.Printf("Unhandled event: %T\n", unhandledEvent)
}
}
}()
}
}
}