From 4ebfcd27f14016d655c16d0c9c383474a19ac4b5 Mon Sep 17 00:00:00 2001 From: Romain de Laage Date: Mon, 1 Mar 2021 18:20:11 +0100 Subject: [PATCH] Add thread support --- server.go | 229 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 198 insertions(+), 31 deletions(-) diff --git a/server.go b/server.go index 6ee4175..edc4545 100644 --- a/server.go +++ b/server.go @@ -38,6 +38,11 @@ type Account struct { Url string `json:"url"` } +type Thread struct { + Ancestors []Blog `json:"ancestors"` + Descendants []Blog `json:"descendants"` +} + func main() { config := getConfig() @@ -150,8 +155,10 @@ func handleConn(conn *tls.Conn, baseURL, title, home_message string) { return } + // skip first '/' path = path[1:] + // home if path == "" { _, err = fmt.Fprintf(conn, "20 text/gemini\r\n# " + title + "\n\n" + home_message) if err != nil { @@ -161,21 +168,54 @@ func handleConn(conn *tls.Conn, baseURL, title, home_message string) { return } - _, err = strconv.ParseUint(path, 10, 64) - if err != nil { - log.Println("invalid request: %s", err) - _, err = fmt.Fprintf(conn, "59 Can't parse request\r\n") + // profile + if strings.HasPrefix(path, "profile/") { + // skip prefix + path = path[8:] + _, err = strconv.ParseUint(path, 10, 64) if err != nil { - log.Println("send error: %s", err) + log.Println("invalid request: %s", err) + _, err = fmt.Fprintf(conn, "59 Can't parse request\r\n") + if err != nil { + log.Println("send error: %s", err) + return + } + return + } + + log.Println("Received request for account " + path) + + printProfile(conn, baseURL, path) + } /* thread */ else if strings.HasPrefix(path, "thread/") { + // skip prefix + path = path[7:] + + _, err = strconv.ParseUint(path, 10, 64) + if err != nil { + log.Println("invalid request: %s", err) + _, err = fmt.Fprintf(conn, "59 Can't parse request\r\n") + if err != nil { + log.Println("send error: %s", err) + return + } + return + } + + log.Println("Received request for thread " + path) + + printThread(conn, baseURL, path) + } else { + _, err = fmt.Fprintf(conn, "59 Invalid request\r\n") + if err != nil { + log.Println("send: %s", err) return } - return } +} - log.Println("Received request for account " + path) - - account, err := getAccount(baseURL, path) - blogs := getBlog(baseURL, path) +func printProfile(conn *tls.Conn, baseURL, profileID string) { + account, err := getAccount(baseURL, profileID) + blogs := getBlog(baseURL, profileID) if err != nil || blogs == nil { _, err = fmt.Fprintf(conn, "40 Remote mastodon instance failed\r\n") @@ -195,27 +235,7 @@ func handleConn(conn *tls.Conn, baseURL, title, home_message string) { for _, blog := range blogs { date := "\n```\n* Posted at " + blog.Date + " *\n```\n" - text := blog.Content + "\n" - text = strings.ReplaceAll(text, "

", "") - text = strings.ReplaceAll(text, "

", "\n\n") - text = strings.ReplaceAll(text, "
", "\n") - text = strings.ReplaceAll(text, "", "") - text = strings.ReplaceAll(text, "", "") - regexString := "]*)?>" - regex, err := regexp.Compile(regexString) - if err != nil { - log.Println("regex: %s", err) - return - } - text = regex.ReplaceAllLiteralString(text, "") - regexString = "]*)?>" - regex, err = regexp.Compile(regexString) - if err != nil { - log.Println("regex: %s", err) - return - } - text = regex.ReplaceAllLiteralString(text, "") - text = html.UnescapeString(text) + text := removeHTMLTags(blog.Content) + "\n" _, err = fmt.Fprintf(conn, date + text) if err != nil { @@ -231,6 +251,91 @@ func handleConn(conn *tls.Conn, baseURL, title, home_message string) { } } +func printThread(conn *tls.Conn, baseURL, tootID string) { + originalToot, err := getToot(baseURL, tootID) + if err != nil { + _, err = fmt.Fprintf(conn, "40 Remote mastodon instance failed\r\n") + if err != nil { + log.Println("handleConn: %s", err) + return + } + return + } + + thread, err := getThread(baseURL, tootID) + if err != nil { + _, err = fmt.Fprintf(conn, "40 Remote mastodon instance failed\r\n") + if err != nil { + log.Println("handleConn: %s", err) + return + } + return + } + + // Print header + _, err = fmt.Fprintf(conn, "20 text/gemini\r\n# Ancestors\n") + if err != nil { + log.Println("handleConn: %s", err) + return + } + + // Print each anscestor + for _, toot := range thread.Ancestors { + _, err = fmt.Fprintf(conn, "\n```\n* Posted on " + toot.Date + " *\n```\n" + removeHTMLTags(toot.Content) + "\n") + if err != nil { + log.Println("handleConn: %s", err) + return + } + } + + // Print original toot + _, err = fmt.Fprintf(conn, "\n# Toot\n\n```\n* Posted on " + originalToot.Date + " *\n```\n" + removeHTMLTags(originalToot.Content) + "\n") + if err != nil { + log.Println("handleConn: %s", err) + return + } + + // Print each descendant + _, err = fmt.Fprintf(conn, "\n# Descendants\n") + if err != nil { + log.Println("handleConn: %s", err) + return + } + for _, toot := range thread.Descendants { + _, err = fmt.Fprintf(conn, "\n```\n* Posted on " + toot.Date + " *\n```\n" + removeHTMLTags(toot.Content) + "\n") + if err != nil { + log.Println("handleConn: %s", err) + return + } + } +} + +func removeHTMLTags(content string) string { + text := strings.ReplaceAll(content, "

", "") + text = strings.ReplaceAll(text, "

", "\n\n") + text = strings.ReplaceAll(text, "
", "\n") + text = strings.ReplaceAll(text, "
", "\n") + text = strings.ReplaceAll(text, "", "") + text = strings.ReplaceAll(text, "", "") + regexString := "]*)?>" + regex, err := regexp.Compile(regexString) + if err != nil { + log.Println("regex: %s", err) + return "" + } + text = regex.ReplaceAllLiteralString(text, "") + regexString = "]*)?>" + regex, err = regexp.Compile(regexString) + if err != nil { + log.Println("regex: %s", err) + return "" + } + text = regex.ReplaceAllLiteralString(text, "") + text = html.UnescapeString(text) + + return text +} + func getBlog(baseURL, account string) []Blog { if baseURL == "" || account == "" { log.Println("baseURL or account is empty") @@ -291,3 +396,65 @@ func getAccount(baseURL, accountId string) (Account, error) { return account, nil } + +func getToot(baseURL, tootId string) (Blog, error) { + if baseURL == "" || tootId == "" { + log.Println("baseURL or tootID is empty") + return Blog{}, fmt.Errorf("baseURL or tootID is empty") + } + + resp, err := http.Get(baseURL + "/api/v1/statuses/" + tootId) + if err != nil { + log.Println("Mastodon API request: %s", err) + return Blog{}, fmt.Errorf("API request failed") + } + + defer resp.Body.Close() + + if resp.StatusCode != 200 { + log.Println("Mastodon API response: %s", resp.Status) + return Blog{}, fmt.Errorf("API response is not 200") + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println("Mastodon response body: %s", err) + return Blog{}, fmt.Errorf("Failed to read response") + } + + var toot Blog + json.Unmarshal(body, &toot) + + return toot, nil +} + +func getThread(baseURL, tootId string) (Thread, error) { + if baseURL == "" || tootId == "" { + log.Println("baseURL or tootID is empty") + return Thread{}, fmt.Errorf("baseURL or tootID is empty") + } + + resp, err := http.Get(baseURL + "/api/v1/statuses/" + tootId + "/context") + if err != nil { + log.Println("Mastodon API request: %s", err) + return Thread{}, fmt.Errorf("API request failed") + } + + defer resp.Body.Close() + + if resp.StatusCode != 200 { + log.Println("Mastodon API response: %s", resp.Status) + return Thread{}, fmt.Errorf("API response is not 200") + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println("Mastodon response body: %s", err) + return Thread{}, fmt.Errorf("Failed to read response") + } + + var thread Thread + json.Unmarshal(body, &thread) + + return thread, nil +}