Notify OP on HN about claimable sats

This commit is contained in:
ekzyis 2023-04-17 01:58:38 +02:00
parent 0e47ca21fd
commit 9f3b488d05
4 changed files with 117 additions and 6 deletions

View File

@ -1,2 +1,3 @@
SN_AUTH_COOKIE= SN_AUTH_COOKIE=
SN_USERNAME=hn
HN_AUTH_COOKIE= HN_AUTH_COOKIE=

13
hn.go
View File

@ -159,3 +159,16 @@ func HackerNewsUserLink(user string) string {
func HackerNewsItemLink(id int) string { func HackerNewsItemLink(id int) string {
return fmt.Sprintf("%s/item?id=%d", HackerNewsUrl, id) return fmt.Sprintf("%s/item?id=%d", HackerNewsUrl, id)
} }
func FindHackerNewsItemId(text string) int {
re := regexp.MustCompile(fmt.Sprintf(`\[HN\]\(%s/item\?id=([0-9]+)\)`, HackerNewsUrl))
match := re.FindStringSubmatch(text)
if len(match) == 0 {
log.Fatal("No Hacker News item URL found")
}
id, err := strconv.Atoi(match[1])
if err != nil {
panic(err)
}
return id
}

79
main.go
View File

@ -1,9 +1,88 @@
package main package main
import (
"fmt"
"log"
"time"
"github.com/joho/godotenv"
"github.com/namsral/flag"
)
var (
SnUserName string
)
func init() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
flag.StringVar(&SnUserName, "SN_USERNAME", "", "Username of bot on SN")
flag.Parse()
if SnUserName == "" {
log.Fatal("SN_USERNAME not set")
}
}
func GenerateHnComment(id int, sats int, nComments int) string {
lnInvoiceDocs := "https://docs.lightning.engineering/the-lightning-network/payment-lifecycle/understanding-lightning-invoices"
return fmt.Sprintf(
""+
"Your post received %d sats and %d comments on %s [0].\n\n"+
"To claim your sats, reply to this comment with a LN address or invoice [1].\n\n"+
"You can create a SN account to obtain a LN address.\n"+
"\n\n"+
"[0] %s/r/%s (referral link)\n\n"+
"[1] %s",
sats,
nComments,
StackerNewsUrl,
StackerNewsItemLink(id),
SnUserName,
lnInvoiceDocs,
)
}
func GenerateSnReply(sats int, nComments int) string {
return fmt.Sprintf("Notified OP on HN that their post received %d sats and %d comments.", sats, nComments)
}
func main() { func main() {
stories := FetchHackerNewsTopStories() stories := FetchHackerNewsTopStories()
filtered := CurateContentForStackerNews(&stories) filtered := CurateContentForStackerNews(&stories)
for _, story := range *filtered { for _, story := range *filtered {
PostStoryToStackerNews(&story) PostStoryToStackerNews(&story)
} }
items := FetchStackerNewsUserItems(SnUserName)
now := time.Now()
for _, item := range *items {
duration := now.Sub(item.CreatedAt)
if duration >= 24*time.Hour && item.Sats > 0 {
log.Printf("Found SN item (id=%d) older than 24 hours with %d sats and %d comments\n", item.Id, item.Sats, item.NComments)
for _, comment := range item.Comments {
if comment.User.Name == SnUserName {
snReply := GenerateSnReply(item.Sats, item.NComments)
// Check if OP on HN was already notified
alreadyNotified := false
for _, comment2 := range comment.Comments {
if comment2.User.Name == SnUserName {
alreadyNotified = true
}
}
if alreadyNotified {
log.Println("OP on HN was already notified")
break
}
text := comment.Text
hnItemId := FindHackerNewsItemId(text)
hnComment := GenerateHnComment(item.Id, item.Sats, item.NComments)
CommentHackerNewsStory(hnComment, hnItemId)
CommentStackerNewsPost(snReply, comment.Id)
break
}
}
}
}
} }

30
sn.go
View File

@ -34,17 +34,19 @@ type User struct {
Name string `json:"name"` Name string `json:"name"`
} }
type Comment struct { type Comment struct {
Id int `json:"id,string"` Id int `json:"id,string"`
Text string `json:"text"` Text string `json:"text"`
User User `json:"user"` User User `json:"user"`
Comments []Comment `json:"comments"`
} }
type Item struct { type Item struct {
Id int `json:"id,string"` Id int `json:"id,string"`
Title string `json:"title"` Title string `json:"title"`
Url string `json:"url"` Url string `json:"url"`
Sats int `json:"sats"` Sats int `json:"sats"`
CreatedAt string `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
Comments []Comment `json:"comments"` Comments []Comment `json:"comments"`
NComments int `json:"ncomments"`
} }
type UpsertLinkResponse struct { type UpsertLinkResponse struct {
@ -63,11 +65,13 @@ type ItemsResponse struct {
} }
var ( var (
SnApiUrl string StackerNewsUrl string
SnAuthCookie string SnApiUrl string
SnAuthCookie string
) )
func init() { func init() {
StackerNewsUrl = "https://stacker.news"
SnApiUrl = "https://stacker.news/api/graphql" SnApiUrl = "https://stacker.news/api/graphql"
err := godotenv.Load() err := godotenv.Load()
if err != nil { if err != nil {
@ -207,12 +211,22 @@ func FetchStackerNewsUserItems(user string) *[]Item {
title title
url url
sats sats
createdAt
comments { comments {
id
text text
user { user {
name name
} }
comments {
id
text
user {
name
}
}
} }
ncomments
} }
cursor cursor
} }
@ -251,3 +265,7 @@ func FetchStackerNewsUserItems(user string) *[]Item {
return &items return &items
} }
func StackerNewsItemLink(id int) string {
return fmt.Sprintf("%s/items/%d", StackerNewsUrl, id)
}