Skip to content

Commit

Permalink
feat: add telegram templates (#13)
Browse files Browse the repository at this point in the history
Fixes #11
  • Loading branch information
freak12techno committed Oct 17, 2022
1 parent 9725ebc commit be16232
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 103 deletions.
6 changes: 3 additions & 3 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,19 @@ func (c *Chain) Validate() error {
return nil
}

func (c *Chain) GetName() string {
func (c Chain) GetName() string {
if c.PrettyName != "" {
return c.PrettyName
}

return c.Name
}

func (c *Chain) GetKeplrLink(proposalID string) string {
func (c Chain) GetKeplrLink(proposalID string) string {
return fmt.Sprintf("https://wallet.keplr.app/#/%s/governance?detailId=%s", c.KeplrName, proposalID)
}

func (c *Chain) GetExplorerProposalsLinks(proposalID string) []ExplorerLink {
func (c Chain) GetExplorerProposalsLinks(proposalID string) []ExplorerLink {
if c.MintscanPrefix == "" {
return []ExplorerLink{}
}
Expand Down
134 changes: 44 additions & 90 deletions telegram.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package main

import (
"bytes"
"embed"
"fmt"
"html/template"
"strings"
"time"

Expand All @@ -16,19 +19,23 @@ type TelegramReporter struct {

TelegramBot *tele.Bot
Logger zerolog.Logger
Templates map[ReportEntryType]*template.Template
}

const (
MaxMessageSize = 4096
AuthorDisclaimer = "\nSent by <a href='https://github.com/freak12techno/cosmos-proposals-checker'>cosmos-proposals-checker.</a>"
MaxMessageSize = 4096
)

//go:embed templates/*
var templatesFs embed.FS

func NewTelegramReporter(config TelegramConfig, mutesManager *MutesManager, logger *zerolog.Logger) *TelegramReporter {
return &TelegramReporter{
TelegramToken: config.TelegramToken,
TelegramChat: config.TelegramChat,
MutesManager: mutesManager,
Logger: logger.With().Str("component", "telegram_reporter").Logger(),
Templates: make(map[ReportEntryType]*template.Template, 0),
}
}

Expand Down Expand Up @@ -61,93 +68,40 @@ func (reporter TelegramReporter) Enabled() bool {
return reporter.TelegramToken != "" && reporter.TelegramChat != 0
}

func (reporter *TelegramReporter) SerializeReportEntry(e ReportEntry) string {
if e.Type == ProposalQueryError {
return reporter.SerializeProposalsError(e)
}
if e.Type == VoteQueryError {
return reporter.SerializeVoteError(e)
func (reporter TelegramReporter) GetTemplate(t ReportEntryType) (*template.Template, error) {
if template, ok := reporter.Templates[t]; ok {
reporter.Logger.Trace().Str("type", string(t)).Msg("Using cached template")
return template, nil
}

var sb strings.Builder

messageText := "🔴 <strong>Wallet %s hasn't voted on proposal %s on %s</strong>\n%s\n\n"
if e.HasRevoted() {
messageText = "↔️ <strong>Wallet %s hasn changed its vote on proposal %s on %s</strong>\n%s\n\n"
} else if e.HasVoted() {
messageText = "✅ <strong>Wallet %s has voted on proposal %s on %s</strong>\n%s\n\n"
}
reporter.Logger.Trace().Str("type", string(t)).Msg("Loading template")

sb.WriteString(fmt.Sprintf(
messageText,
e.Wallet,
e.ProposalID,
e.Chain.GetName(),
e.ProposalTitle,
))

if e.HasVoted() {
sb.WriteString(fmt.Sprintf(
"<strong>Vote: </strong>%s\n",
e.Value,
))
}
if e.HasRevoted() {
sb.WriteString(fmt.Sprintf(
"<strong>Old vote: </strong>%s\n",
e.OldValue,
))
filename := fmt.Sprintf("templates/telegram/%s.html", t)
template, err := template.ParseFS(templatesFs, filename)
if err != nil {
return nil, err
}

sb.WriteString(fmt.Sprintf(
"Voting ends at: %s (in %s)\n\n",
e.ProposalVoteEndingTime.Format(time.RFC3339Nano),
time.Until(e.ProposalVoteEndingTime).Round(time.Second),
))
reporter.Templates[t] = template

sb.WriteString(reporter.SerializeLinks(e))
sb.WriteString(AuthorDisclaimer)

return sb.String()
return template, nil
}

func (reporter TelegramReporter) SerializeLinks(e ReportEntry) string {
var sb strings.Builder

if e.Chain.KeplrName != "" {
sb.WriteString(fmt.Sprintf(
"<a href='%s'>Keplr</a>\n",
e.Chain.GetKeplrLink(e.ProposalID),
))
func (reporter *TelegramReporter) SerializeReportEntry(e ReportEntry) (string, error) {
template, err := reporter.GetTemplate(e.Type)
if err != nil {
reporter.Logger.Error().Err(err).Str("type", string(e.Type)).Msg("Error loading template")
return "", err
}

explorerLinks := e.Chain.GetExplorerProposalsLinks(e.ProposalID)
for _, link := range explorerLinks {
sb.WriteString(fmt.Sprintf(
"<a href='%s'>%s</a>\n",
link.Link,
link.Name,
))
var buffer bytes.Buffer
err = template.Execute(&buffer, e)
if err != nil {
reporter.Logger.Error().Err(err).Str("type", string(e.Type)).Msg("Error rendering template")
return "", err
}

return sb.String()
}

func (reporter TelegramReporter) SerializeProposalsError(e ReportEntry) string {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("❌ There was an error querying proposals on %s.\n", e.Chain.GetName()))
sb.WriteString(fmt.Sprintf("<strong>Error text: </strong>%s.\n", e.Value))
sb.WriteString(AuthorDisclaimer)
return sb.String()
}

func (reporter TelegramReporter) SerializeVoteError(e ReportEntry) string {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("❌ There was an error querying proposal on %s.\n", e.Chain.GetName()))
sb.WriteString(fmt.Sprintf("<strong>Proposal ID: </strong>%s.\n", e.ProposalID))
sb.WriteString(fmt.Sprintf("<strong>Error text: </strong>%s.\n", e.Value))
sb.WriteString(AuthorDisclaimer)
return sb.String()
return buffer.String(), nil
}

func (reporter TelegramReporter) SendReport(report Report) error {
Expand All @@ -160,9 +114,13 @@ func (reporter TelegramReporter) SendReport(report Report) error {
continue
}

serializedEntry := reporter.SerializeReportEntry(entry)
serializedEntry, err := reporter.SerializeReportEntry(entry)
if err != nil {
reporter.Logger.Err(err).Msg("Could not serialize report entry")
return err
}

_, err := reporter.TelegramBot.Send(
_, err = reporter.TelegramBot.Send(
&tele.User{
ID: reporter.TelegramChat,
},
Expand Down Expand Up @@ -240,18 +198,14 @@ func (reporter *TelegramReporter) HandleHelp(c tele.Context) error {
Str("text", c.Text()).
Msg("Got help query")

var sb strings.Builder
sb.WriteString("<strong>cosmos-proposals-checker</strong>\n\n")
sb.WriteString("Notifies you about the proposals your wallets hasn't voted upon.\n")
sb.WriteString("Can understand the following commands:\n")
sb.WriteString("- /proposals_mute &lt;duration&gt; &lt;chain&gt; &lt;proposal ID&gt; - mute notifications for a specific proposal\n")
sb.WriteString("- /proposals_mutes - display the active proposals mutes list\n")
sb.WriteString("- /help - display this command\n")
sb.WriteString("Created by <a href=\"https://freak12techno.github.io\">freak12techno</a> with ❤️.\n")
sb.WriteString("This bot is open-sourced, you can get the source code at https://github.com/freak12techno/cosmos-proposals-checker.\n\n")
sb.WriteString("If you like what we're doing, consider <a href=\"https://freak12techno.github.io/validators\">staking with us</a>!\n")
template, _ := reporter.GetTemplate("help")
var buffer bytes.Buffer
if err := template.Execute(&buffer, nil); err != nil {
reporter.Logger.Error().Err(err).Msg("Error rendering telp template")
return err
}

return reporter.BotReply(c, sb.String())
return reporter.BotReply(c, buffer.String())
}

func (reporter *TelegramReporter) BotReply(c tele.Context, msg string) error {
Expand Down
11 changes: 11 additions & 0 deletions templates/telegram/help.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<strong>cosmos-proposals-checker</strong>
Notifies you about the proposals your wallets hasn't voted upon.
Can understand the following commands:
- /proposals_mute &lt;duration&gt; &lt;chain&gt; &lt;proposal ID&gt; - mute notifications for a specific proposal
- /proposals_mutes - display the active proposals mutes list
- /help - display this command

Created by <a href="https://freak12techno.github.io">freak12techno</a> with ❤️.
This bot is open-sourced, you can get the source code at https://github.com/freak12techno/cosmos-proposals-checker.

If you like what we're doing, consider <a href="https://freak12techno.github.io/validators">staking with us</a>!
9 changes: 9 additions & 0 deletions templates/telegram/not_voted.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
🔴 <strong>Wallet {{ .Wallet }} hasn't voted on proposal {{ .ProposalID }} on {{ .Chain.GetName }}</strong>
{{ .ProposalTitle }}

Voting ends at: {{ .GetProposalTime }} (in {{ .GetProposalTimeLeft }})

{{ if .Chain.KeplrName }}<a href='{{ .Chain.GetKeplrLink .ProposalID }}'>Keplr</a>{{ end }}
{{ range .Chain.GetExplorerProposalsLinks .ProposalID }}<a href='{{ .Link }} '>{{ .Name }}</a>
{{ end }}
Sent by <a href='https://github.com/freak12techno/cosmos-proposals-checker'>cosmos-proposals-checker.</a>
4 changes: 4 additions & 0 deletions templates/telegram/proposal_query_error.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
❌ There was an error querying proposals on {{ .Chain.GetName }}.
<strong>Error text: </strong>{{ .Value }}

Sent by <a href='https://github.com/freak12techno/cosmos-proposals-checker'>cosmos-proposals-checker.</a>
11 changes: 11 additions & 0 deletions templates/telegram/revoted.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
↔️ <strong>Wallet {{ .Wallet }} has changed its vote on proposal {{ .ProposalID }} on {{ .Chain.GetName }}</strong>
{{ .ProposalTitle }}

Vote: {{ .GetVote }}
Old vote: {{ .GetOldVote }}
Voting ends at: {{ .GetProposalTime }} (in {{ .GetProposalTimeLeft }})

{{ if .Chain.KeplrName }}<a href='{{ .Chain.GetKeplrLink .ProposalID }}'>Keplr</a>{{ end }}
{{ range .Chain.GetExplorerProposalsLinks .ProposalID }}<a href='{{ .Link }} '>{{ .Name }}</a>
{{ end }}
Sent by <a href='https://github.com/freak12techno/cosmos-proposals-checker'>cosmos-proposals-checker.</a>
5 changes: 5 additions & 0 deletions templates/telegram/vote_query_error.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
❌ There was an error querying proposal on {{ .Chain.GetName }}
<strong>Proposal ID: </strong>{{ .ProposalID }}
<strong>Error text: </strong>{{ .Value }}

Sent by <a href='https://github.com/freak12techno/cosmos-proposals-checker'>cosmos-proposals-checker.</a>
10 changes: 10 additions & 0 deletions templates/telegram/voted.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<strong>Wallet {{ .Wallet }} has voted on proposal {{ .ProposalID }} on {{ .Chain.GetName }}</strong>
{{ .ProposalTitle }}

Vote: {{ .GetVote }}
Voting ends at: {{ .GetProposalTime }} (in {{ .GetProposalTimeLeft }})

{{ if .Chain.KeplrName }}<a href='{{ .Chain.GetKeplrLink .ProposalID }}'>Keplr</a>{{ end }}
{{ range .Chain.GetExplorerProposalsLinks .ProposalID }}<a href='{{ .Link }} '>{{ .Name }}</a>
{{ end }}
Sent by <a href='https://github.com/freak12techno/cosmos-proposals-checker'>cosmos-proposals-checker.</a>
3 changes: 2 additions & 1 deletion tendermint.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net/http"
"strings"
"time"

"github.com/rs/zerolog"
Expand Down Expand Up @@ -62,7 +63,7 @@ func (rpc *RPC) GetVote(proposal, voter string) (*VoteRPCResponse, error) {
return nil, err
}

if vote.IsError() && vote.Code != 3 {
if vote.IsError() && !strings.Contains(vote.Message, "not found") {
return nil, errors.New(vote.Message)
}

Expand Down
49 changes: 40 additions & 9 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ func (r *Report) Empty() bool {
return len(r.Entries) == 0
}

type ReportEntryType int
type ReportEntryType string

const (
NotVoted ReportEntryType = iota
Voted
Revoted
ProposalQueryError
VoteQueryError
NotVoted ReportEntryType = "not_voted"
Voted ReportEntryType = "voted"
Revoted ReportEntryType = "revoted"
ProposalQueryError ReportEntryType = "proposal_query_error"
VoteQueryError ReportEntryType = "vote_query_error"
)

type ReportEntry struct {
Expand All @@ -67,18 +67,49 @@ type ReportEntry struct {
OldValue string
}

func (e *ReportEntry) HasVoted() bool {
func (e ReportEntry) HasVoted() bool {
return e.Value != ""
}

func (e *ReportEntry) HasRevoted() bool {
func (e ReportEntry) HasRevoted() bool {
return e.Value != "" && e.OldValue != ""
}

func (e *ReportEntry) IsVoteOrNotVoted() bool {
func (e ReportEntry) IsVoteOrNotVoted() bool {
return e.Type == NotVoted || e.Type == Voted
}

func (e ReportEntry) GetProposalTime() string {
return e.ProposalVoteEndingTime.Format(time.RFC3339Nano)
}

func (e ReportEntry) GetProposalTimeLeft() string {
return time.Until(e.ProposalVoteEndingTime).Round(time.Second).String()
}

func (e ReportEntry) GetVote() string {
return ResolveVote(e.Value)
}

func (e ReportEntry) GetOldVote() string {
return ResolveVote(e.OldValue)
}

func ResolveVote(value string) string {
votes := map[string]string{
"VOTE_OPTION_YES": "Yes",
"VOTE_OPTION_ABSTAIN": "Abstain",
"VOTE_OPTION_NO": "No",
"VOTE_OPTION_NO_WITH_VETO": "No with veto",
}

if vote, ok := votes[value]; ok && vote != "" {
return vote
}

return value
}

type Reporter interface {
Init()
Enabled() bool
Expand Down

0 comments on commit be16232

Please sign in to comment.