Compare commits

..

No commits in common. "develop" and "v0.3.1" have entirely different histories.

7 changed files with 111 additions and 80 deletions

View File

@ -1 +1 @@
SN_API_KEY=
SN_AUTH_COOKIE=

4
.gitignore vendored
View File

@ -1,3 +1,3 @@
.env*
.env
*.sqlite3
nitterbot
unpaywall

View File

@ -1,2 +0,0 @@
nitterbot: *.go go.sum go.mod
go build -o nitterbot .

10
db.go
View File

@ -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
View File

@ -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.2
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
)

8
go.sum
View File

@ -1,4 +1,8 @@
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/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=

158
main.go
View File

@ -1,29 +1,46 @@
package main
import (
"bufio"
"fmt"
"log"
"os"
"regexp"
"strings"
"time"
sn "github.com/ekzyis/snappy"
"github.com/ekzyis/sn-goapi"
)
type NostrClient struct {
Url string
Name string
}
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.cz",
"nitter.woodland.cafe",
"nitter.unixfox.eu",
"nitter.privacydev.net",
"nitter.perennialte.ch",
"nitter.soopy.moe",
"nitter.1d4.us",
"nitter.adminforge.de",
}
NitterOnionUrls = []string{
"nitter7bryz3jv7e3uekphigvmoyoem4al3fynerxkj22dmoxoq553qd.onion",
"26oq3gioiwcmfojub37nz5gzbkdiqp7fue5kvye7d4txv4ny6fb4wwid.onion",
"vfaomgh4jxphpbdfizkm5gbtjahmei234giqj4facbwhrfjtcldauqad.onion",
"nitraeju2mipeziu2wtcrqsxg7h62v5y4eqgwi75uprynkj74gevvuqd.onion",
"codeine3hsqnnkb3dsu6ft4tunlomr3lmuml5hcoqmfkgiqfv2brdqqd.onion",
}
NitterI2PUrls = []string{
"axd6uavsstsrvstva4mzlzh4ct76rc6zdug3nxdgeitrzczhzf4q.b32.i2p",
"u6ikd6zndl3c4dsdq4mmujpntgeevdk5qzkfb57r4tnfeccrn2qa.b32.i2p",
"gseczlzmiv23p5vhsktyd7whquq2uy3c5fgkmdohh453qp3daoua.b32.i2p",
"tm4rwkeysv3zz3q5yacyr4rlmca2c4etkdobfvuqzt6vsfsu4weq.b32.i2p",
"vernzdedoxuflrrxc4vbatbkpjh4k22ecgiqgimdiif62onhagva.b32.i2p",
}
NitterLokinetUrls = []string{
"nitter.priv.loki/",
}
)
@ -34,17 +51,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 +94,53 @@ 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\nTor: "
for _, nUrl := range NitterOnionUrls {
nitterLink := strings.Replace(item.Url, m[1], nUrl, 1)
nitterLink = strings.Replace(nitterLink, "https://", "http://", 1)
comment += fmt.Sprintf("[%s..%s](%s) | ", nUrl[:12], nUrl[len(nUrl)-12:], nitterLink)
}
comment = strings.TrimRight(comment, "| ")
comment += "\n\nI2P: "
for _, nUrl := range NitterI2PUrls {
nitterLink := strings.Replace(item.Url, m[1], nUrl, 1)
nitterLink = strings.Replace(nitterLink, "https://", "http://", 1)
comment += fmt.Sprintf("[%s..%s](%s) | ", nUrl[:12], nUrl[len(nUrl)-12:], nitterLink)
}
comment = strings.TrimRight(comment, "| ")
comment += "\n\nLokinet: "
for _, nUrl := range NitterLokinetUrls {
nitterLink := strings.Replace(item.Url, m[1], nUrl, 1)
nitterLink = strings.Replace(nitterLink, "https://", "http://", 1)
comment += fmt.Sprintf("[%s](%s)\n", nUrl, nitterLink)
}
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)
}
}
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)
}
}