Add thread support

This commit is contained in:
Romain de Laage 2021-03-01 18:20:11 +01:00
parent e49948777d
commit 4ebfcd27f1
Signed by: rdelaage
GPG Key ID: 534845FADDF0C329
1 changed files with 198 additions and 31 deletions

229
server.go
View File

@ -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, "<p>", "")
text = strings.ReplaceAll(text, "</p>", "\n\n")
text = strings.ReplaceAll(text, "<br />", "\n")
text = strings.ReplaceAll(text, "</a>", "")
text = strings.ReplaceAll(text, "</span>", "")
regexString := "<a( [^>]*)?>"
regex, err := regexp.Compile(regexString)
if err != nil {
log.Println("regex: %s", err)
return
}
text = regex.ReplaceAllLiteralString(text, "")
regexString = "<span( [^>]*)?>"
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, "<p>", "")
text = strings.ReplaceAll(text, "</p>", "\n\n")
text = strings.ReplaceAll(text, "<br />", "\n")
text = strings.ReplaceAll(text, "<br>", "\n")
text = strings.ReplaceAll(text, "</a>", "")
text = strings.ReplaceAll(text, "</span>", "")
regexString := "<a( [^>]*)?>"
regex, err := regexp.Compile(regexString)
if err != nil {
log.Println("regex: %s", err)
return ""
}
text = regex.ReplaceAllLiteralString(text, "")
regexString = "<span( [^>]*)?>"
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
}