Support posting.
This commit is contained in:
parent
fe171d9fa9
commit
0f39286db5
|
@ -0,0 +1,2 @@
|
|||
iris-news
|
||||
tmp/*
|
161
main.go
161
main.go
|
@ -2,10 +2,14 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/mail"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
@ -60,7 +64,7 @@ func writeLocation(location string) error {
|
|||
var group = &nntp.Group{
|
||||
Name: "ctrl-c.iris",
|
||||
Description: "The iris message board.",
|
||||
Posting: nntp.PostingNotPermitted,
|
||||
Posting: nntp.PostingPermitted,
|
||||
Low: 1,
|
||||
}
|
||||
|
||||
|
@ -156,25 +160,96 @@ func (b backend) Authenticate(user, pass string) (nntpserver.Backend, error) {
|
|||
}
|
||||
|
||||
func (b backend) AllowPost() bool {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
func (b backend) Post(article *nntp.Article) error {
|
||||
return nntpserver.ErrPostingNotPermitted
|
||||
msg, err := msgToIris(article)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return appendMessage(msg)
|
||||
}
|
||||
|
||||
type irisDump []struct {
|
||||
Hash string `json:"hash"`
|
||||
EditHash string `json:"edit_hash"`
|
||||
IsDeleted *bool `json:"is_deleted"`
|
||||
type irisMsg struct {
|
||||
Hash string `json:"hash"`
|
||||
EditHash *string `json:"edit_hash"`
|
||||
IsDeleted *bool `json:"is_deleted"`
|
||||
Data struct {
|
||||
Author string `json:"author"`
|
||||
Parent *string `json:"parent"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Message string `json:"message"`
|
||||
Author string `json:"author"`
|
||||
Parent *string `json:"parent"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Message string `json:"message"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func (m irisMsg) calcHash() (string, error) {
|
||||
/*
|
||||
Careful coding here to match ruby's hash calculation:
|
||||
```
|
||||
Base64.encode64(Digest::SHA1.digest(m["data"].to_json))
|
||||
```
|
||||
|
||||
* have to use an encoder rather than json.Marshal so we can
|
||||
turn off the default HTML escaping (ruby doesn't do this)
|
||||
* strip trailing newline from JSON encoding output
|
||||
* add a trailing newline to base64 encoded form
|
||||
*/
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
enc := json.NewEncoder(b)
|
||||
enc.SetEscapeHTML(false)
|
||||
if err := enc.Encode(m.Data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
arr := sha1.Sum(bytes.TrimSuffix(b.Bytes(), []byte("\n")))
|
||||
s := base64.StdEncoding.EncodeToString(arr[:])
|
||||
if !strings.HasSuffix(s, "\n") {
|
||||
s += "\n"
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func msgToIris(article *nntp.Article) (*irisMsg, error) {
|
||||
postTime := time.Now().UTC().Format(time.RFC3339)
|
||||
|
||||
body, err := io.ReadAll(article.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var msg irisMsg
|
||||
msg.Data.Author = irisAuthor(article.Header.Get("From"))
|
||||
msg.Data.Timestamp = postTime
|
||||
msg.Data.Message = string(body)
|
||||
refs := article.Header.Get("References")
|
||||
if refs != "" {
|
||||
spl := strings.Split(refs, " ")
|
||||
ref := fromMsgID(spl[len(spl)-1])
|
||||
msg.Data.Parent = &ref
|
||||
}
|
||||
|
||||
hash, err := msg.calcHash()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg.Hash = hash
|
||||
|
||||
return &msg, nil
|
||||
}
|
||||
|
||||
func irisAuthor(nntpAuthor string) string {
|
||||
addr, err := mail.ParseAddress(nntpAuthor)
|
||||
if err != nil {
|
||||
return nntpAuthor
|
||||
}
|
||||
|
||||
return addr.Address
|
||||
}
|
||||
|
||||
type irisDump []irisMsg
|
||||
|
||||
func (id irisDump) Articles() []*nntp.Article {
|
||||
out := make([]*nntp.Article, len(id))
|
||||
subjects := make(map[string]string)
|
||||
|
@ -186,7 +261,7 @@ func (id irisDump) Articles() []*nntp.Article {
|
|||
"Message-Id": []string{msgID},
|
||||
"From": []string{d.Data.Author},
|
||||
"Newsgroups": []string{group.Name},
|
||||
"Date": []string{d.Data.Timestamp.Format(time.RFC1123Z)},
|
||||
"Date": []string{d.Data.Timestamp},
|
||||
},
|
||||
Body: bytes.NewBufferString(d.Data.Message),
|
||||
Bytes: len(d.Data.Message),
|
||||
|
@ -207,6 +282,68 @@ func (id irisDump) Articles() []*nntp.Article {
|
|||
return out
|
||||
}
|
||||
|
||||
func (id irisDump) MarshalJSON() ([]byte, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetEscapeHTML(false)
|
||||
|
||||
out := bytes.NewBufferString("[\n ")
|
||||
|
||||
for i, msg := range id {
|
||||
if err := enc.Encode(msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if i > 0 {
|
||||
_, _ = out.WriteString(",\n ")
|
||||
}
|
||||
_, _ = out.Write(bytes.TrimSuffix(buf.Bytes(), []byte("\n")))
|
||||
buf.Reset()
|
||||
}
|
||||
_, _ = out.WriteString("\n]")
|
||||
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
func toMsgID(irisID string) string {
|
||||
return "<" + strings.TrimSuffix(irisID, "\n") + ">"
|
||||
}
|
||||
|
||||
func fromMsgID(nntpID string) string {
|
||||
return strings.TrimSuffix(strings.TrimPrefix(nntpID, "<"), ">") + "\n"
|
||||
}
|
||||
|
||||
func appendMessage(msg *irisMsg) error {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgFile, err := os.Open(path.Join(home, msgfile))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var msgs irisDump
|
||||
if err := json.NewDecoder(msgFile).Decode(&msgs); err != nil {
|
||||
_ = msgFile.Close()
|
||||
return err
|
||||
}
|
||||
_ = msgFile.Close()
|
||||
msgs = append(msgs, *msg)
|
||||
|
||||
msgFile, err = os.Create(path.Join(home, msgfile))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = msgFile.Close() }()
|
||||
|
||||
out, err := msgs.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = msgFile.Write(out)
|
||||
return err
|
||||
}
|
||||
|
||||
const msgfile = ".iris.messages"
|
||||
|
|
Loading…
Reference in New Issue