diff --git a/idec/messages.go b/idec/messages.go new file mode 100644 index 0000000..68c0f41 --- /dev/null +++ b/idec/messages.go @@ -0,0 +1,35 @@ +package idec + +import ( + "encoding/base64" + "strings" +) + +// Message IDEC message structure +type Message struct { + From string `json:"from"` + To string `json:"to"` + Address string `json:"address"` + Echo string `json:"echo"` + Subg string `json:"subg"` + ID string `json:"id"` + Timestamp int `json:"timestamp"` + Body string `json:"body"` + Tags string `json:"tags"` + Repto string `json:"repto"` +} + +// PrepareMessageForSend Make base64 encoded message +func PrepareMessageForSend(m Message) string { + var result string + + var rawMessage string + if m.Repto != "" { + rawMessage = strings.Join([]string{m.Echo, m.To, m.Subg, "", m.Repto, m.Body}, "\n") + } + rawMessage = strings.Join([]string{m.Echo, m.To, m.Subg, "", m.Body}, "\n") + + result = base64.StdEncoding.EncodeToString([]byte(rawMessage)) + + return result +} diff --git a/idec/parser.go b/idec/parser.go new file mode 100644 index 0000000..db78ef0 --- /dev/null +++ b/idec/parser.go @@ -0,0 +1,56 @@ +package idec + +import ( + "encoding/base64" + "strconv" + "strings" +) + +// ParseMessage ... +func ParseMessage(message string) (Message, error) { + var m Message + plainMessage, err := base64.StdEncoding.DecodeString(message) + if err != nil { + return m, err + } + + txtMessage := strings.Split(string(plainMessage), "\n") + + var body string + for i := 8; i < len(txtMessage); i++ { + body = strings.Join([]string{body, txtMessage[i]}, "\n") + } + + ts, err := strconv.Atoi(txtMessage[2]) + if err != nil { + return m, err + } + m.Tags = txtMessage[0] + m.Echo = txtMessage[1] + m.Timestamp = ts + m.From = txtMessage[3] + m.Address = txtMessage[4] + m.To = txtMessage[5] + m.Subg = txtMessage[6] + m.Body = body + + return m, err +} + +// ParseEchoList parse /list.txt +func ParseEchoList(list string) ([]Echo, error) { + var echoes []Echo + for _, e := range strings.Split(list, "\n") { + desc := strings.Split(e, ":") + if len(desc) <= 1 { + break + } + count, err := strconv.Atoi(desc[1]) + if err != nil { + return echoes, err + } + echoes = append(echoes, Echo{desc[0], count, desc[2]}) + } + + return echoes, nil +} diff --git a/idec/proto.go b/idec/proto.go new file mode 100644 index 0000000..32fb8ba --- /dev/null +++ b/idec/proto.go @@ -0,0 +1,183 @@ +package idec + +// Base IDEC protocol implementation + +import ( + "errors" + "io/ioutil" + "net/http" + "strconv" + "strings" +) + +// IDEC Extensions. see: https://ii-net.tk/idec-doc/?p=extensions +const ( + listTXT = "list.txt" + blacklistTXT = "blacklist.txt" + features = "x/features" + xcount = "x/c/" + echoSchema = "u/e/" + messageSchema = "u/m/" +) + +// Extensions IDEC extensions +type Extensions struct { + ListTXT string `json:"list_txt"` + BlacklistTXT string `json:"backlist_txt"` + Features string `json:"features"` + XCount string `json:"xcount"` +} + +// NewExtensions ... +func NewExtensions() Extensions { + e := Extensions{ + listTXT, + blacklistTXT, + features, + xcount, + } + return e +} + +// FetchConfig node, echo, and other connection settings +type FetchConfig struct { + Node string `json:"node"` + Echoes []string `json:"echo"` + Num int `json:"count"` + Offset int `json:"offset"` + Limit int `json:"limit"` +} + +// ID ... +type ID struct { + Echo string `json:"echo"` + MsgID string `json:"msgids"` +} + +// GetMessagesIDS get message ids from node +func (f FetchConfig) GetMessagesIDS() ([]ID, error) { + var ids []ID + + var getURI string + getEchoes := strings.Join(f.Echoes, "/") + + // Make strings + offset := strconv.Itoa(f.Offset) + limit := strconv.Itoa(f.Limit) + + getURI = strings.Join([]string{f.Node, echoSchema, getEchoes, "/", offset, ":", limit}, "") + + // Get messages ids + response, err := http.Get(getURI) + if err != nil { + return ids, err + } + + defer response.Body.Close() + c, err := ioutil.ReadAll(response.Body) + if err != nil { + return ids, err + } + + var i ID + var curEcho string + rawIDS := strings.Split(string(c), "\n") + for _, line := range rawIDS { + + // Match echoarea + if strings.Contains(line, ".") { + curEcho = line + continue + } + + // Match message ID + if !strings.Contains(line, ".") && !strings.Contains(line, ":") && line != "" { + i.Echo = curEcho + i.MsgID = line + ids = append(ids, i) + } + } + + return ids, nil +} + +// GetRawMessages get messages from node +func (f FetchConfig) GetRawMessages(ids []ID) ([]string, error) { + var messages []string + + var messagesIDS []string + for _, id := range ids { + messagesIDS = append(messagesIDS, id.MsgID) + } + getMessages := strings.Join(messagesIDS, "/") + + getURI := strings.Join([]string{f.Node, messageSchema, getMessages}, "") + + // Get messages ids + response, err := http.Get(getURI) + if err != nil { + return messages, err + } + + defer response.Body.Close() + c, err := ioutil.ReadAll(response.Body) + if err != nil { + return messages, err + } + + for _, m := range strings.Split(string(c), "\n") { + if len(m) == 0 { + break + } + message := strings.Split(m, ":") + if len(message) > 0 { + messages = append(messages, message[1]) + } + } + + return messages, err +} + +// Echo echo description +type Echo struct { + Name string `json:"name"` + Size int `json:"size"` + Description string `json:"description"` +} + +// GetEchoList ... +func (f FetchConfig) GetEchoList() ([]Echo, error) { + var echoes []Echo + + // Check node features support + fres, err := http.Get(strings.Join([]string{f.Node, features}, "/")) + if err != nil { + return echoes, err + } + defer fres.Body.Close() + + c, err := ioutil.ReadAll(fres.Body) + if err != nil { + return echoes, err + } + + if !strings.Contains(string(c), listTXT) { + err = errors.New("Node does not support echoes list") + return echoes, err + } + + lres, err := http.Get(strings.Join([]string{f.Node, listTXT}, "/")) + if err != nil { + return echoes, err + } + defer lres.Body.Close() + + l, err := ioutil.ReadAll(lres.Body) + if err != nil { + return echoes, err + } + + echoes, err = ParseEchoList(string(l)) + + return echoes, err +}