Compare commits
No commits in common. "develop" and "v0.4.0" have entirely different histories.
@ -1 +1 @@
|
||||
SN_API_KEY=
|
||||
SN_AUTH_COOKIE=
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,3 @@
|
||||
.env*
|
||||
.env
|
||||
*.sqlite3
|
||||
nitterbot
|
||||
unpaywall
|
||||
|
10
db.go
10
db.go
@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
sn "github.com/ekzyis/snappy"
|
||||
"github.com/ekzyis/sn-goapi"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
@ -15,7 +15,7 @@ var (
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
db, err = sql.Open("sqlite3", "nitterbot.sqlite3")
|
||||
db, err = sql.Open("sqlite3", "unpaywall.sqlite3")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -42,9 +42,7 @@ func ItemHasComment(parentId int) bool {
|
||||
err := db.QueryRow(`SELECT COUNT(1) FROM comments WHERE parent_id = ?`, parentId).Scan(&count)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error during item check: %w", err)
|
||||
log.Println(err)
|
||||
// pretend that item has comment to avoid duplicate comments
|
||||
return true
|
||||
log.Fatal(err)
|
||||
}
|
||||
return count > 0
|
||||
}
|
||||
@ -53,6 +51,6 @@ func SaveComment(comment *sn.Comment) {
|
||||
_, err := db.Exec(`INSERT INTO comments(id, text, parent_id) VALUES (?, ?, ?)`, comment.Id, comment.Text, comment.ParentId)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error during item insert: %w", err)
|
||||
log.Println(err)
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
7
go.mod
7
go.mod
@ -3,6 +3,11 @@ module git.ekzyis.com/ekzyis/unpaywall
|
||||
go 1.21.0
|
||||
|
||||
require (
|
||||
github.com/ekzyis/snappy v0.5.3
|
||||
github.com/ekzyis/sn-goapi v0.3.3
|
||||
github.com/mattn/go-sqlite3 v1.14.17
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/namsral/flag v1.7.4-pre // indirect
|
||||
)
|
||||
|
10
go.sum
10
go.sum
@ -1,4 +1,10 @@
|
||||
github.com/ekzyis/snappy v0.5.3 h1:Pq8b2s4WPz5gnHgCoZR2LVqArEVqWeVCyHSVZCawat4=
|
||||
github.com/ekzyis/snappy v0.5.3/go.mod h1:BxJwdGlCwUw0Q5pQzBr59weAIS6pkVdivBBaZkkWTSo=
|
||||
github.com/ekzyis/sn-goapi v0.3.2 h1:BGY2tOD9IZD40UXUpexysWoC+eLEoUs7JRo9FQ37S6M=
|
||||
github.com/ekzyis/sn-goapi v0.3.2/go.mod h1:FObbYr/NXgnXNWU+EwiWKoWQy+wAaRS6AoW3NgsJ/Oo=
|
||||
github.com/ekzyis/sn-goapi v0.3.3 h1:5WHGLyYVPwZ12lQrRD40eM+gjWEpDdgdWTshwL8CDEE=
|
||||
github.com/ekzyis/sn-goapi v0.3.3/go.mod h1:FObbYr/NXgnXNWU+EwiWKoWQy+wAaRS6AoW3NgsJ/Oo=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/namsral/flag v1.7.4-pre h1:b2ScHhoCUkbsq0d2C15Mv+VU8bl8hAXV8arnWiOHNZs=
|
||||
github.com/namsral/flag v1.7.4-pre/go.mod h1:OXldTctbM6SWH1K899kPZcf65KxJiD7MsceFUpB5yDo=
|
||||
|
150
main.go
150
main.go
@ -1,15 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
sn "github.com/ekzyis/snappy"
|
||||
"github.com/ekzyis/sn-goapi"
|
||||
)
|
||||
|
||||
type NostrClient struct {
|
||||
@ -18,12 +16,30 @@ type NostrClient struct {
|
||||
}
|
||||
|
||||
var (
|
||||
TwitterUrlRegexp = regexp.MustCompile(`(?:https?:\/\/)?(?:www\.)?((?:twitter|x)\.com)\/\w+\/status(?:es)?\/\d+`)
|
||||
TwitterUrlRegexp = regexp.MustCompile(`^(?:https?:\/\/)?((www\.)?(twitter|x)\.com)\/`)
|
||||
// references:
|
||||
// - https://github.com/zedeus/nitter/wiki/Instances
|
||||
// - https://status.d420.de/
|
||||
NitterClearnetUrls = []string{
|
||||
"xcancel.com",
|
||||
"nitter.privacydev.net",
|
||||
}
|
||||
|
||||
// since v0.4.0, bot also replaces nostr links with nostr.com so users can pick their client
|
||||
NostrUrlRegexp = regexp.MustCompile(
|
||||
`^(?:https?:\/\/)?(?:www\.)?` +
|
||||
`(?:` +
|
||||
`primal.net\/(?:e\/)?|snort.social\/(?:e\/)?` +
|
||||
`)((note|nevent)[a-zA-Z0-9]+)$`)
|
||||
NostrClients = []NostrClient{
|
||||
// list from nostr.com
|
||||
NostrClient{"https://primal.net/e/", "primal.net"},
|
||||
NostrClient{"https://snort.social/e/", "snort.social"},
|
||||
NostrClient{"https://nostrudel.ninja/#/n/", "nostrudel.ninja"},
|
||||
NostrClient{"https://satellite.earth/thread/", "satellite.earth"},
|
||||
NostrClient{"https://coracle.social/", "coracle.social"},
|
||||
NostrClient{"https://nostter.app/", "nostter.app"},
|
||||
NostrClient{"https://highlighter.com/a/", "highlighter.com"},
|
||||
NostrClient{"https://iris.to/", "iris.to"},
|
||||
}
|
||||
)
|
||||
|
||||
@ -34,17 +50,41 @@ func WaitUntilNext(d time.Duration) {
|
||||
time.Sleep(dur)
|
||||
}
|
||||
|
||||
func CheckNotifications() {
|
||||
var prevHasNewNotes bool
|
||||
for {
|
||||
log.Println("Checking notifications ...")
|
||||
hasNewNotes, err := sn.CheckNotifications()
|
||||
if err != nil {
|
||||
SendToNostr(fmt.Sprint(err))
|
||||
} else {
|
||||
if !prevHasNewNotes && hasNewNotes {
|
||||
// only send on "rising edge"
|
||||
SendToNostr("new notifications")
|
||||
log.Println("Forwarded notifications to monitoring")
|
||||
} else if hasNewNotes {
|
||||
log.Println("Notifications already forwarded")
|
||||
}
|
||||
}
|
||||
prevHasNewNotes = hasNewNotes
|
||||
WaitUntilNext(time.Hour)
|
||||
}
|
||||
}
|
||||
|
||||
func SessionKeepAlive() {
|
||||
for {
|
||||
log.Println("Refresh session using GET /api/auth/session ...")
|
||||
sn.RefreshSession()
|
||||
WaitUntilNext(time.Hour)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
loadEnv()
|
||||
|
||||
c := sn.NewClient(
|
||||
sn.WithBaseUrl(os.Getenv("SN_BASE_URL")),
|
||||
sn.WithApiKey(os.Getenv("SN_API_KEY")),
|
||||
)
|
||||
|
||||
go CheckNotifications()
|
||||
go SessionKeepAlive()
|
||||
for {
|
||||
log.Println("fetching items ...")
|
||||
r, err := c.Items(&sn.ItemsQuery{Sort: "recent", Type: "all", Limit: 100})
|
||||
r, err := sn.Items(&sn.ItemsQuery{Sort: "recent", Limit: 21})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
SendToNostr(fmt.Sprint(err))
|
||||
@ -53,68 +93,56 @@ func main() {
|
||||
}
|
||||
|
||||
for _, item := range r.Items {
|
||||
var m []string
|
||||
var comment string
|
||||
|
||||
if m = TwitterUrlRegexp.FindStringSubmatch(item.Url); m != nil {
|
||||
comment = strings.Replace(m[0], m[1], "xcancel.com", 1)
|
||||
} else if m = TwitterUrlRegexp.FindStringSubmatch(item.Text); m != nil {
|
||||
comment = strings.Replace(m[0], m[1], "xcancel.com", 1)
|
||||
}
|
||||
|
||||
if comment != "" {
|
||||
if m := TwitterUrlRegexp.FindStringSubmatch(item.Url); m != nil {
|
||||
log.Printf("item %d is twitter link\n", item.Id)
|
||||
|
||||
if ItemHasComment(item.Id) {
|
||||
log.Printf("item %d already has nitter links comment\n", item.Id)
|
||||
continue
|
||||
}
|
||||
|
||||
cId, err := c.CreateComment(item.Id, comment)
|
||||
comment := "**Twitter2Nitter**\n\nClearnet: "
|
||||
for _, nUrl := range NitterClearnetUrls {
|
||||
nitterLink := strings.Replace(item.Url, m[1], nUrl, 1)
|
||||
comment += fmt.Sprintf("[%s](%s) | ", nUrl, nitterLink)
|
||||
}
|
||||
comment = strings.TrimRight(comment, "| ")
|
||||
comment += "\n\n_Nitter is a free and open source alternative Twitter front-end focused on privacy and performance. "
|
||||
comment += "Click [here](https://github.com/zedeus/nitter) for more information._"
|
||||
cId, err := sn.CreateComment(item.Id, comment)
|
||||
if err != nil {
|
||||
log.Println("create comment failed:", err)
|
||||
log.Println(err)
|
||||
SendToNostr(fmt.Sprint(err))
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("created comment %d\n", cId)
|
||||
SaveComment(&sn.Comment{Id: cId, Text: comment, ParentId: item.Id})
|
||||
} else {
|
||||
log.Printf("item %d is not twitter link\n", item.Id)
|
||||
}
|
||||
if m := NostrUrlRegexp.FindStringSubmatch(item.Url); m != nil {
|
||||
log.Printf("item %d is nostr link\n", item.Id)
|
||||
if ItemHasComment(item.Id) {
|
||||
log.Printf("item %d already has nostr links comment\n", item.Id)
|
||||
continue
|
||||
}
|
||||
noteId := m[1]
|
||||
comment := "**Nostr Client Picker**\n\n"
|
||||
for _, client := range NostrClients {
|
||||
comment += fmt.Sprintf("[%s](%s) | ", client.Name, client.Url+noteId)
|
||||
}
|
||||
comment = strings.TrimRight(comment, "| ")
|
||||
cId, err := sn.CreateComment(item.Id, comment)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
SendToNostr(fmt.Sprint(err))
|
||||
continue
|
||||
}
|
||||
log.Printf("created comment %d\n", cId)
|
||||
SaveComment(&sn.Comment{Id: cId, Text: comment, ParentId: item.Id})
|
||||
} else {
|
||||
log.Printf("item %d is not nostr link\n", item.Id)
|
||||
}
|
||||
}
|
||||
|
||||
WaitUntilNext(time.Minute)
|
||||
}
|
||||
}
|
||||
|
||||
func loadEnv() {
|
||||
var (
|
||||
f *os.File
|
||||
s *bufio.Scanner
|
||||
err error
|
||||
)
|
||||
|
||||
if f, err = os.Open(".env"); err != nil {
|
||||
log.Fatalf("error opening .env: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
s = bufio.NewScanner(f)
|
||||
s.Split(bufio.ScanLines)
|
||||
for s.Scan() {
|
||||
line := s.Text()
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
|
||||
// Check if we have exactly 2 parts (key and value)
|
||||
if len(parts) == 2 {
|
||||
os.Setenv(parts[0], parts[1])
|
||||
} else {
|
||||
log.Fatalf(".env: invalid line: %s\n", line)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for errors during scanning
|
||||
if err = s.Err(); err != nil {
|
||||
fmt.Println("error scanning .env:", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user