Compare commits
12 Commits
Author | SHA1 | Date |
---|---|---|
Romain de Laage | 87784a8166 | |
Romain de Laage | 88707930b9 | |
Romain de Laage | 52103bd76d | |
Romain de Laage | 1ae21d1c2b | |
Romain de Laage | 1c5ead27b0 | |
Romain de Laage | 8494120df0 | |
Romain de Laage | 5ba39fbdb8 | |
Romain de Laage | 92ee4fe2bd | |
Romain de Laage | de4113154d | |
Romain de Laage | 545ebe8321 | |
Romain de Laage | d95b1bbccc | |
Romain de Laage | b6b7fd1cb5 |
2
Makefile
2
Makefile
|
@ -1,4 +1,4 @@
|
||||||
VERSION=1.0
|
VERSION=1.2
|
||||||
SOURCES=$(shell find . -name "*.go" -type f)
|
SOURCES=$(shell find . -name "*.go" -type f)
|
||||||
|
|
||||||
all: amd64 arm7 dockerimage
|
all: amd64 arm7 dockerimage
|
||||||
|
|
|
@ -4,5 +4,6 @@
|
||||||
"key_path": "certs/key.rsa",
|
"key_path": "certs/key.rsa",
|
||||||
"base_url": "https://mamot.fr",
|
"base_url": "https://mamot.fr",
|
||||||
"title": "MastoGem",
|
"title": "MastoGem",
|
||||||
"home_message": "Welcome on MastoGem, a Mastodon proxy for Gemini.\nYou can view the last 20 toots of a Mastodon account by providing its id, for example:\n=> gemini://localhost/310515 My Mastodon account"
|
"home_message": "Welcome on MastoGem, a Mastodon proxy for Gemini.\nYou can view the last 20 toots of a Mastodon account by providing its id, for example:\n=> gemini://localhost/310515 My Mastodon account",
|
||||||
|
"rate_limit": 45
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ version: "3.7"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
mastogem:
|
mastogem:
|
||||||
image: dervom/mastogem:1.0
|
image: dervom/mastogem:1.1
|
||||||
build: .
|
build: .
|
||||||
container_name: mastogem
|
container_name: mastogem
|
||||||
volumes:
|
volumes:
|
||||||
|
|
34
main.go
34
main.go
|
@ -17,15 +17,24 @@
|
||||||
*/
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "log"
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type Blog struct {
|
type Blog struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Date string `json:"created_at"`
|
Date string `json:"created_at"`
|
||||||
Author Account `json:"account"`
|
Author Account `json:"account"`
|
||||||
Tags []Tag `json:"tags"`
|
Tags []Tag `json:"tags"`
|
||||||
Mentions []Mention `json:"mentions"`
|
Mentions []Mention `json:"mentions"`
|
||||||
|
Reblog *Blog `json:"reblog"`
|
||||||
|
Medias []Media `json:"media_attachments"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Media struct {
|
||||||
|
Url string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
@ -35,6 +44,7 @@ type Config struct {
|
||||||
BaseURL string `json:"base_url"`
|
BaseURL string `json:"base_url"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
HomeMessage string `json:"home_message"`
|
HomeMessage string `json:"home_message"`
|
||||||
|
RateLimit int `json:"rate_limit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Account struct {
|
type Account struct {
|
||||||
|
@ -58,12 +68,20 @@ type Tag struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Rate struct {
|
||||||
|
Date time.Time
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
var rateMap map[string]Rate
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
config := getConfig()
|
config := getConfig()
|
||||||
|
rateMap = make(map[string]Rate)
|
||||||
|
|
||||||
listener := listen(config.Listen, config.CertPath, config.KeyPath)
|
listener := listen(config.Listen, config.CertPath, config.KeyPath)
|
||||||
log.Println("Server successfully started")
|
log.Println("Server successfully started")
|
||||||
log.Println("Server is listening at " + config.Listen)
|
log.Println("Server is listening at " + config.Listen)
|
||||||
|
|
||||||
serve(listener, config.BaseURL, config.Title, config.HomeMessage)
|
serve(listener, config.BaseURL, config.Title, config.HomeMessage, config.RateLimit)
|
||||||
}
|
}
|
||||||
|
|
154
mastoUtil.go
154
mastoUtil.go
|
@ -21,38 +21,114 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"log"
|
"log"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getBlog(baseURL, account string) []Blog {
|
func getBlogAndReblog(baseURL, account string) ([]Blog, error) {
|
||||||
if baseURL == "" || account == "" {
|
if baseURL == "" || account == "" {
|
||||||
log.Println("baseURL or account is empty")
|
log.Println("baseURL or account is empty")
|
||||||
return nil
|
return nil, fmt.Errorf("BaseURL or account is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := http.Get(baseURL + "/api/v1/accounts/" + account + "/statuses?exclude_reblogs=true&exclude_replies=true")
|
resp, err := http.Get(baseURL + "/api/v1/accounts/" + account + "/statuses?exclude_reblogs=false&exclude_replies=true")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Mastodon API request: %s", err)
|
log.Println("Mastodon API request: %s", err)
|
||||||
return nil
|
return nil, fmt.Errorf("Failed to request Mastodon API")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
log.Println("Mastodon API response: %s", resp.Status)
|
log.Println("Mastodon API response: %s", resp.Status)
|
||||||
return nil
|
return nil, fmt.Errorf("Mastodon instance failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Mastodon response body: %s", err)
|
log.Println("Mastodon response body: %s", err)
|
||||||
|
return nil, fmt.Errorf("Failed to read Mastodon response body")
|
||||||
}
|
}
|
||||||
|
|
||||||
var blogs []Blog
|
var blogs []Blog
|
||||||
json.Unmarshal(body, &blogs)
|
err = json.Unmarshal(body, &blogs)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Mastodon response %s", err)
|
||||||
|
return nil, fmt.Errorf("Failed to parse Mastodon response")
|
||||||
|
}
|
||||||
|
|
||||||
return blogs
|
return blogs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBlog(baseURL, account string) ([]Blog, error) {
|
||||||
|
if baseURL == "" || account == "" {
|
||||||
|
log.Println("baseURL or account is empty")
|
||||||
|
return nil, fmt.Errorf("BaseURL or account is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(baseURL + "/api/v1/accounts/" + account + "/statuses?exclude_reblogs=true&exclude_replies=true")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Mastodon API request: %s", err)
|
||||||
|
return nil, fmt.Errorf("Failed to request Mastodon API")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
log.Println("Mastodon API response: %s", resp.Status)
|
||||||
|
return nil, fmt.Errorf("Mastodon instance failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Mastodon response body: %s", err)
|
||||||
|
return nil, fmt.Errorf("Failed to read Mastodon response body")
|
||||||
|
}
|
||||||
|
|
||||||
|
var blogs []Blog
|
||||||
|
err = json.Unmarshal(body, &blogs)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Mastodon response %s", err)
|
||||||
|
return nil, fmt.Errorf("Failed to parse Mastodon response")
|
||||||
|
}
|
||||||
|
|
||||||
|
return blogs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTimeline(baseURL string) ([]Blog, error) {
|
||||||
|
var toots []Blog
|
||||||
|
|
||||||
|
if baseURL == "" {
|
||||||
|
log.Println("baseURL is empty")
|
||||||
|
return toots, fmt.Errorf("baseURL is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(baseURL + "/api/v1/timelines/public")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Mastodon API request: %s", err)
|
||||||
|
return toots, fmt.Errorf("API request failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
log.Println("Mastodon API response: %s", resp.Status)
|
||||||
|
return toots, fmt.Errorf("API response is not 200")
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Mastodon response body: %s", err)
|
||||||
|
return toots, fmt.Errorf("Failed to read response")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, &toots)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Mastodon response %s", err)
|
||||||
|
return toots, fmt.Errorf("Failed to parse response")
|
||||||
|
}
|
||||||
|
|
||||||
|
return toots, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAccount(baseURL, accountId string) (Account, error) {
|
func getAccount(baseURL, accountId string) (Account, error) {
|
||||||
|
@ -74,14 +150,18 @@ func getAccount(baseURL, accountId string) (Account, error) {
|
||||||
return Account{}, fmt.Errorf("API response is not 200")
|
return Account{}, fmt.Errorf("API response is not 200")
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Mastodon response body: %s", err)
|
log.Println("Mastodon response body: %s", err)
|
||||||
return Account{}, fmt.Errorf("Failed to read response")
|
return Account{}, fmt.Errorf("Failed to read response")
|
||||||
}
|
}
|
||||||
|
|
||||||
var account Account
|
var account Account
|
||||||
json.Unmarshal(body, &account)
|
err = json.Unmarshal(body, &account)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Mastodon response %s", err)
|
||||||
|
return Account{}, fmt.Errorf("Failed to parse response")
|
||||||
|
}
|
||||||
|
|
||||||
return account, nil
|
return account, nil
|
||||||
}
|
}
|
||||||
|
@ -105,18 +185,50 @@ func getToot(baseURL, tootId string) (Blog, error) {
|
||||||
return Blog{}, fmt.Errorf("API response is not 200")
|
return Blog{}, fmt.Errorf("API response is not 200")
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Mastodon response body: %s", err)
|
log.Println("Mastodon response body: %s", err)
|
||||||
return Blog{}, fmt.Errorf("Failed to read response")
|
return Blog{}, fmt.Errorf("Failed to read response")
|
||||||
}
|
}
|
||||||
|
|
||||||
var toot Blog
|
var toot Blog
|
||||||
json.Unmarshal(body, &toot)
|
err = json.Unmarshal(body, &toot)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Mastodon response %s", err)
|
||||||
|
return Blog{}, fmt.Errorf("Failed to parse response")
|
||||||
|
}
|
||||||
|
|
||||||
return toot, nil
|
return toot, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getMedia(mediaURL string) (string, []byte, error) {
|
||||||
|
if mediaURL == "" {
|
||||||
|
log.Println("mediaURL is empty")
|
||||||
|
return "", nil, fmt.Errorf("mediaURL is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(mediaURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Mastodon API request: %s", err)
|
||||||
|
return "", nil, fmt.Errorf("API request failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
log.Println("Mastodon API response: %s", resp.Status)
|
||||||
|
return "", nil, fmt.Errorf("API response is not 200")
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Mastodon response body: %s", err)
|
||||||
|
return "", nil, fmt.Errorf("Failed to read response")
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Header["Content-Type"][0], body , nil
|
||||||
|
}
|
||||||
|
|
||||||
func getThread(baseURL, tootId string) (Thread, error) {
|
func getThread(baseURL, tootId string) (Thread, error) {
|
||||||
if baseURL == "" || tootId == "" {
|
if baseURL == "" || tootId == "" {
|
||||||
log.Println("baseURL or tootID is empty")
|
log.Println("baseURL or tootID is empty")
|
||||||
|
@ -136,14 +248,18 @@ func getThread(baseURL, tootId string) (Thread, error) {
|
||||||
return Thread{}, fmt.Errorf("API response is not 200")
|
return Thread{}, fmt.Errorf("API response is not 200")
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Mastodon response body: %s", err)
|
log.Println("Mastodon response body: %s", err)
|
||||||
return Thread{}, fmt.Errorf("Failed to read response")
|
return Thread{}, fmt.Errorf("Failed to read response")
|
||||||
}
|
}
|
||||||
|
|
||||||
var thread Thread
|
var thread Thread
|
||||||
json.Unmarshal(body, &thread)
|
err = json.Unmarshal(body, &thread)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Mastodon response %s", err)
|
||||||
|
return Thread{}, fmt.Errorf("Failed to parse response")
|
||||||
|
}
|
||||||
|
|
||||||
return thread, nil
|
return thread, nil
|
||||||
}
|
}
|
||||||
|
@ -167,14 +283,18 @@ func getTag(baseURL, tag string) ([]Blog, error) {
|
||||||
return nil, fmt.Errorf("API response is not 200")
|
return nil, fmt.Errorf("API response is not 200")
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Mastodon response body: %s", err)
|
log.Println("Mastodon response body: %s", err)
|
||||||
return nil, fmt.Errorf("Failed to read response")
|
return nil, fmt.Errorf("Failed to read response")
|
||||||
}
|
}
|
||||||
|
|
||||||
var blogs []Blog
|
var blogs []Blog
|
||||||
json.Unmarshal(body, &blogs)
|
err = json.Unmarshal(body, &blogs)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Mastodon response: %s", err)
|
||||||
|
return nil, fmt.Errorf("Failed to parse response")
|
||||||
|
}
|
||||||
|
|
||||||
return blogs, nil
|
return blogs, nil
|
||||||
}
|
}
|
||||||
|
|
221
server.go
221
server.go
|
@ -48,14 +48,14 @@ func listen(address, certFile, keyFile string) net.Listener {
|
||||||
return listener
|
return listener
|
||||||
}
|
}
|
||||||
|
|
||||||
func serve(listener net.Listener, baseURL, title, home_message string) {
|
func serve(listener net.Listener, baseURL, title, home_message string, rateLimit int) {
|
||||||
for {
|
for {
|
||||||
conn, err := listener.Accept()
|
conn, err := listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go handleConn(conn.(*tls.Conn), baseURL, title, home_message)
|
go handleConn(conn.(*tls.Conn), baseURL, title, home_message, rateLimit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,9 +87,19 @@ func getPath(conn *tls.Conn) (string, string, error) {
|
||||||
return parsedURL.Path, parsedURL.RawQuery, nil
|
return parsedURL.Path, parsedURL.RawQuery, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConn(conn *tls.Conn, baseURL, title, home_message string) {
|
func handleConn(conn *tls.Conn, baseURL, title, home_message string, rateLimit int) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
|
if !rateIsOk(rateMap, strings.Split(conn.RemoteAddr().String(), ":")[0], rateLimit) {
|
||||||
|
log.Printf("Too many requests for %s\n", conn.RemoteAddr().String())
|
||||||
|
_, err := fmt.Fprintf(conn, "44 60\r\n")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("send error: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
path, query, err := getPath(conn)
|
path, query, err := getPath(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("get url: %s", err)
|
log.Println("get url: %s", err)
|
||||||
|
@ -105,7 +115,7 @@ func handleConn(conn *tls.Conn, baseURL, title, home_message string) {
|
||||||
if path == "" || path == "/" {
|
if path == "" || path == "/" {
|
||||||
log.Println("Received request for home page")
|
log.Println("Received request for home page")
|
||||||
|
|
||||||
_, err = fmt.Fprintf(conn, "20 text/gemini\r\n# %s\n\n%s\n\n=> /tag Search for a tag\n=> /about About MastoGem", title, home_message)
|
_, err = fmt.Fprintf(conn, "20 text/gemini\r\n# %s\n\n%s\n\n=> /timeline View public timeline\n=> /tag Search for a tag\n=> /about About MastoGem", title, home_message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("send error: %s", err)
|
log.Println("send error: %s", err)
|
||||||
return
|
return
|
||||||
|
@ -115,22 +125,39 @@ func handleConn(conn *tls.Conn, baseURL, title, home_message string) {
|
||||||
|
|
||||||
// profile
|
// profile
|
||||||
if strings.HasPrefix(path, "/profile/") {
|
if strings.HasPrefix(path, "/profile/") {
|
||||||
// skip prefix
|
if strings.HasSuffix(path, "/reblog") {
|
||||||
path = path[9:]
|
// skip prefix and suffix
|
||||||
_, err = strconv.ParseUint(path, 10, 64)
|
path = path[9:len(path)-len("/reblog")]
|
||||||
if err != nil {
|
_, err = strconv.ParseUint(path, 10, 64)
|
||||||
log.Println("invalid request: %s", err)
|
|
||||||
_, err = fmt.Fprintf(conn, "59 Can't parse request\r\n")
|
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
return
|
|
||||||
|
log.Println("Received request for account with reblog " + path)
|
||||||
|
printProfileWithReblog(conn, baseURL, path)
|
||||||
|
} else {
|
||||||
|
// skip prefix
|
||||||
|
path = path[9:]
|
||||||
|
_, 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 account " + path)
|
||||||
|
printProfile(conn, baseURL, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Received request for account " + path)
|
|
||||||
|
|
||||||
printProfile(conn, baseURL, path)
|
|
||||||
} /* thread */ else if strings.HasPrefix(path, "/thread/") {
|
} /* thread */ else if strings.HasPrefix(path, "/thread/") {
|
||||||
// skip prefix
|
// skip prefix
|
||||||
path = path[8:]
|
path = path[8:]
|
||||||
|
@ -203,6 +230,23 @@ This capsule use %s Mastodon instance.
|
||||||
log.Println("Received request for toot " + path)
|
log.Println("Received request for toot " + path)
|
||||||
|
|
||||||
printToot(conn, baseURL, path)
|
printToot(conn, baseURL, path)
|
||||||
|
} /* timeline */ else if strings.HasPrefix(path, "/timeline") {
|
||||||
|
log.Println("Received request for timeline")
|
||||||
|
|
||||||
|
printTimeline(conn, baseURL)
|
||||||
|
} /* media */ else if strings.HasPrefix(path, "/media") {
|
||||||
|
if query == "" {
|
||||||
|
_, err = fmt.Fprintf(conn, "59 Invalid request\r\n")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("send: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Received request for media " + query)
|
||||||
|
|
||||||
|
proxyMedia(conn, baseURL, query)
|
||||||
} else {
|
} else {
|
||||||
_, err = fmt.Fprintf(conn, "59 Invalid request\r\n")
|
_, err = fmt.Fprintf(conn, "59 Invalid request\r\n")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -212,11 +256,62 @@ This capsule use %s Mastodon instance.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func proxyMedia(conn *tls.Conn, baseURL, query string) {
|
||||||
|
mediaURL, err := url.QueryUnescape(query)
|
||||||
|
if err != nil {
|
||||||
|
_, err = fmt.Fprintf(conn, "59 Invalid url encoded media\r\n")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("handleConn: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(mediaURL, baseURL) {
|
||||||
|
_, err = fmt.Fprintf(conn, "59 Invalid media url\r\n")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("handleConn: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mime, media, err := getMedia(mediaURL)
|
||||||
|
if err != nil {
|
||||||
|
_, err = fmt.Fprintf(conn, "40 Remote mastodon instance failed\r\n")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("handleConn: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Fprintf(conn, "20 %s\r\n", mime)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("handleConn: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Write(media)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("handleConn: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func printProfile(conn *tls.Conn, baseURL, profileID string) {
|
func printProfile(conn *tls.Conn, baseURL, profileID string) {
|
||||||
account, err := getAccount(baseURL, profileID)
|
account, err := getAccount(baseURL, profileID)
|
||||||
blogs := getBlog(baseURL, profileID)
|
if err != nil {
|
||||||
|
_, err = fmt.Fprintf(conn, "40 Remote mastodon instance failed\r\n")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("handleConn: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil || blogs == nil {
|
blogs, err := getBlog(baseURL, profileID)
|
||||||
|
if err != nil {
|
||||||
_, err = fmt.Fprintf(conn, "40 Remote mastodon instance failed\r\n")
|
_, err = fmt.Fprintf(conn, "40 Remote mastodon instance failed\r\n")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("handleConn: %s", err)
|
log.Println("handleConn: %s", err)
|
||||||
|
@ -239,13 +334,83 @@ func printProfile(conn *tls.Conn, baseURL, profileID string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = fmt.Fprintf(conn, "\n=> %s Go to %s account", account.Url, account.Name)
|
_, err = fmt.Fprintf(conn, "\n=> /profile/%s/reblog This profile with reblog\n=> %s Go to %s account", account.Id, account.Url, account.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("add link: %s", err)
|
log.Println("add link: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printProfileWithReblog(conn *tls.Conn, baseURL, profileID string) {
|
||||||
|
account, err := getAccount(baseURL, profileID)
|
||||||
|
if err != nil {
|
||||||
|
_, err = fmt.Fprintf(conn, "40 Remote mastodon instance failed\r\n")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("handleConn: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
blogs, err := getBlogAndReblog(baseURL, profileID)
|
||||||
|
if err != nil {
|
||||||
|
_, err = fmt.Fprintf(conn, "40 Remote mastodon instance failed\r\n")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("handleConn: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Fprintf(conn, "20 text/gemini\r\n# Toots for %s account\n", account.Name)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("handleConn: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, blog := range blogs {
|
||||||
|
_, err = fmt.Fprintf(conn, "\n%s\n=> /thread/%s View the thread\n", formatBlog(blog), blog.Id)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("read blogs: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Fprintf(conn, "\n=> /profile/%s This profile without reblog\n=> %s Go to %s account", account.Id, account.Url, account.Name)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("add link: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTimeline(conn *tls.Conn, baseURL string) {
|
||||||
|
toots, err := getTimeline(baseURL)
|
||||||
|
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")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("handleConn: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print toots
|
||||||
|
for _, toot := range toots {
|
||||||
|
_, err = fmt.Fprintf(conn, "\n%s\n=> /thread/%s View the thread\n", formatBlog(toot), toot.Id)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("read timeline: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func printToot(conn *tls.Conn, baseURL, tootID string) {
|
func printToot(conn *tls.Conn, baseURL, tootID string) {
|
||||||
toot, err := getToot(baseURL, tootID)
|
toot, err := getToot(baseURL, tootID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -265,10 +430,18 @@ func printToot(conn *tls.Conn, baseURL, tootID string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print toot
|
// Print toot
|
||||||
_, err = fmt.Fprintf(conn, "# Toot\n\n%s\n=> /thread/%s View the thread\n=> /profile/%s More toots from %s\n", formatBlog(toot), toot.Id, toot.Author.Id, toot.Author.Name)
|
if toot.Reblog == nil {
|
||||||
if err != nil {
|
_, err = fmt.Fprintf(conn, "# Toot\n\n%s\n=> /thread/%s View the thread\n=> /profile/%s More toots from %s\n", formatBlog(toot), toot.Id, toot.Author.Id, toot.Author.Name)
|
||||||
log.Println("handleConn: %s", err)
|
if err != nil {
|
||||||
return
|
log.Println("handleConn: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err = fmt.Fprintf(conn, "# Toot\n\n%s\n=> /toot/%s Original toot\n=> /thread/%s View the thread\n=> /profile/%s More toots from %s\n=> /profile/%s More toots from %s\n", formatBlog(toot), toot.Reblog.Id, toot.Id, toot.Author.Id, toot.Author.Name, toot.Reblog.Author.Id, toot.Reblog.Author.Name)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("handleConn: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// print mentions
|
// print mentions
|
||||||
|
@ -286,7 +459,7 @@ func printToot(conn *tls.Conn, baseURL, tootID string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// print tags
|
// print tags
|
||||||
_, err = fmt.Fprintf(conn, "\n# Tags\n")
|
_, err = fmt.Fprintf(conn, "\n\n# Tags\n")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("handleConn: %s", err)
|
log.Println("handleConn: %s", err)
|
||||||
return
|
return
|
||||||
|
|
12
start.sh
12
start.sh
|
@ -15,18 +15,27 @@ fi
|
||||||
if [ ! -f /key.rsa ]
|
if [ ! -f /key.rsa ]
|
||||||
then
|
then
|
||||||
echo "You must bind a private key at /key.rsa"
|
echo "You must bind a private key at /key.rsa"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$TITLE" ]
|
if [ -z "$TITLE" ]
|
||||||
then
|
then
|
||||||
|
echo "Using default title"
|
||||||
TITLE=MastoGem
|
TITLE=MastoGem
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$HOME_MESSAGE" ]
|
if [ -z "$HOME_MESSAGE" ]
|
||||||
then
|
then
|
||||||
|
echo "Using default home message"
|
||||||
HOME_MESSAGE="Welcome on MastoGem, a Mastodon proxy for Gemini !"
|
HOME_MESSAGE="Welcome on MastoGem, a Mastodon proxy for Gemini !"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -z "$RATE_LIMIT" ]
|
||||||
|
then
|
||||||
|
echo "Using default rate limit"
|
||||||
|
RATE_LIMIT=45
|
||||||
|
fi
|
||||||
|
|
||||||
cat << EOF > /config.json
|
cat << EOF > /config.json
|
||||||
{
|
{
|
||||||
"listen": "0.0.0.0:1965",
|
"listen": "0.0.0.0:1965",
|
||||||
|
@ -34,7 +43,8 @@ cat << EOF > /config.json
|
||||||
"key_path": "/key.rsa",
|
"key_path": "/key.rsa",
|
||||||
"base_url": "$MASTODON_BASE_URL",
|
"base_url": "$MASTODON_BASE_URL",
|
||||||
"title": "$TITLE",
|
"title": "$TITLE",
|
||||||
"home_message": "$HOME_MESSAGE"
|
"home_message": "$HOME_MESSAGE",
|
||||||
|
"rate_limit": $RATE_LIMIT
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|
59
util.go
59
util.go
|
@ -25,6 +25,8 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
|
"time"
|
||||||
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getConfig() Config {
|
func getConfig() Config {
|
||||||
|
@ -39,6 +41,7 @@ func getConfig() Config {
|
||||||
BaseURL: "https://mamot.fr",
|
BaseURL: "https://mamot.fr",
|
||||||
Title: "MastoGem",
|
Title: "MastoGem",
|
||||||
HomeMessage: "Welcome on MastoGem, this is a Mastodon proxy for Gemini. You can view the last 20 toots of a Mastodon account by providing its ID.",
|
HomeMessage: "Welcome on MastoGem, this is a Mastodon proxy for Gemini. You can view the last 20 toots of a Mastodon account by providing its ID.",
|
||||||
|
RateLimit: 45,
|
||||||
}
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
@ -51,7 +54,10 @@ func getConfig() Config {
|
||||||
|
|
||||||
var config Config
|
var config Config
|
||||||
|
|
||||||
json.Unmarshal(configFile, &config)
|
err = json.Unmarshal(configFile, &config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("config file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
@ -83,7 +89,12 @@ func removeHTMLTags(content string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatBlog(toot Blog) string {
|
func formatBlog(toot Blog) string {
|
||||||
content := toot.Content
|
var content string
|
||||||
|
if toot.Reblog == nil {
|
||||||
|
content = toot.Content
|
||||||
|
} else {
|
||||||
|
content = toot.Reblog.Content
|
||||||
|
}
|
||||||
content = removeHTMLTags(content)
|
content = removeHTMLTags(content)
|
||||||
content = strings.Trim(content, " \n\r")
|
content = strings.Trim(content, " \n\r")
|
||||||
content = strings.ReplaceAll(content, "\n#", "\n[#]")
|
content = strings.ReplaceAll(content, "\n#", "\n[#]")
|
||||||
|
@ -99,5 +110,47 @@ func formatBlog(toot Blog) string {
|
||||||
author = toot.Author.DisplayName
|
author = toot.Author.DisplayName
|
||||||
}
|
}
|
||||||
|
|
||||||
return "### Written by " + author + " on " + toot.Date[0:10] + " at " + toot.Date[11:16] + "\n" + content + "\n=> /toot/" + toot.Id + " More informations about this toot"
|
var header string
|
||||||
|
|
||||||
|
if toot.Reblog == nil {
|
||||||
|
header = "### Written by " + author + " on " + toot.Date[0:10] + " at " + toot.Date[11:16]
|
||||||
|
} else {
|
||||||
|
var originalAuthor string
|
||||||
|
if toot.Reblog.Author.DisplayName == "" {
|
||||||
|
originalAuthor = toot.Reblog.Author.Name
|
||||||
|
} else {
|
||||||
|
originalAuthor = toot.Reblog.Author.DisplayName
|
||||||
|
}
|
||||||
|
|
||||||
|
header = "### Shared by " + author + " on " + toot.Date[0:10] + " at " + toot.Date[11:16] + " (original by " + originalAuthor + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
footer := "\n"
|
||||||
|
for _, media := range toot.Medias {
|
||||||
|
mediaURL := url.QueryEscape(media.Url)
|
||||||
|
footer += "=> /media?" + mediaURL + " View attached media\n"
|
||||||
|
}
|
||||||
|
footer += "\n=> /toot/" + toot.Id + " More informations about this toot"
|
||||||
|
|
||||||
|
return header + "\n" + content + "\n" + footer
|
||||||
|
}
|
||||||
|
|
||||||
|
func rateIsOk(tab map[string]Rate, remoteIP string, limit int) bool {
|
||||||
|
elmt, ok := tab[remoteIP]
|
||||||
|
if ok == false {
|
||||||
|
tab[remoteIP] = Rate{time.Now(), 1}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
if time.Since(elmt.Date).Minutes() >= 1 {
|
||||||
|
tab[remoteIP] = Rate{time.Now(), 1}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
if elmt.Count < limit {
|
||||||
|
tab[remoteIP] = Rate{elmt.Date, elmt.Count + 1}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue