master
GeneralHurricane 2022-12-18 21:22:21 -06:00
parent 5cd9604642
commit 9d62be0869
9 changed files with 459 additions and 26 deletions

3
.dockerignore 100644
View File

@ -0,0 +1,3 @@
.git
bible_databases

24
Dockerfile 100644
View File

@ -0,0 +1,24 @@
FROM golang:1.16-buster AS build
WORKDIR /app
COPY go.mod ./
COPY go.sum ./
ENV CGO_ENABLED 0
RUN go mod download
COPY *.go ./
RUN go build -o /biblebot *.go
##
## Deploy
##
FROM alpine
WORKDIR /
COPY --from=build /biblebot /biblebot
ENTRYPOINT ["/biblebot"]

View File

@ -9,3 +9,15 @@ git submodule update
```
Ensure Docker and docker-compose are installed
Create a file called `.env` and add the following to it:
```
RDRAMA_API_TOKEN=
RDRAMA_URL=https://rdrama.net
MARIADB_RANDOM_ROOT_PASSWORD=yes
MARIADB_USER=admin
MARIADB_PASSWORD=mysafeadminpassword
MARIADB_DATABASE=bible
BIBLEDB_HOST=bibledb
BIBLEBOT_ID=
```
Fill in your `RDRAMA_API_TOKEN` and `BIBLEBOT_ID`. You can change the user and password.

View File

@ -11,6 +11,75 @@ var BibleRegex = regexp.MustCompile(`(?P<prefix>\w*?)? *(?P<book>[a-zA-Z]+?)\s?(
var MatchError = errors.New("Error matching verse")
var CanonicalName = map[int]string{
1: "Genesis",
2: "Exodus",
3: "Leviticus",
4: "Numbers",
5: "Deuteronomy",
6: "Joshua",
7: "Judges",
8: "Ruth",
9: "1 Samuel",
10: "2 Samuel",
11: "1 Kings",
12: "2 Kings",
13: "1 Chronicles",
14: "2 Chronicles",
15: "Ezra",
16: "Nehemiah",
17: "Esther",
18: "Job",
19: "Psalms",
20: "Proverbs",
21: "Ecclesiastes",
22: "Song of Solomon",
23: "Isaiah",
24: "Jeremiah",
25: "Lamentations",
26: "Ezekiel",
27: "Daniel",
28: "Hosea",
29: "Joel",
30: "Amos",
31: "Obadiah",
32: "Jonah",
33: "Micah",
34: "Nahum",
35: "Habakkuk",
36: "Zephaniah",
37: "Haggai",
38: "Zechariah",
39: "Malachi",
40: "Matthew",
41: "Mark",
42: "Luke",
43: "John",
44: "Acts",
45: "Romans",
46: "1 Corinthians",
47: "2 Corinthians",
48: "Galatians",
49: "Ephesians",
50: "Philippians",
51: "Colossians",
52: "1 Thessalonians",
53: "2 Thessalonians",
54: "1 Timothy",
55: "2 Timothy",
56: "Titus",
57: "Philemon",
58: "Hebrews",
59: "James",
60: "1 Peter",
61: "2 Peter",
62: "1 John",
63: "2 John",
64: "3 John",
65: "Jude",
66: "Revelation",
}
var Books = map[string]int{
"GEN": 1,
"EXO": 2,
@ -158,6 +227,17 @@ func contains(s []string, str string) bool {
return false
}
func (b *BibleReference) CanonicalName() (string, error) {
book, ok := CanonicalName[b.BookNum]
if !ok {
return "", errors.New("Can't find book, maybe you're Catholic?")
}
if b.StartKey == b.EndKey {
return fmt.Sprintf("%s %s:%s", book, b.Chapter, b.StartVerse), nil
}
return fmt.Sprintf("%s %s:%s-%s", book, b.Chapter, b.StartVerse, b.EndVerse), nil
}
func (b *BibleReference) Validate() error {
if contains(FirstPrefixes, b.Prefix) {
b.Prefix = "1"
@ -179,6 +259,7 @@ func (b *BibleReference) Validate() error {
if !ok {
return MatchError
}
b.BookNum = num
chapter, err := strconv.Atoi(b.Chapter)
if err != nil {

View File

@ -0,0 +1,31 @@
version: '3'
#
# Docker Compose configuration for MariaDB
#
# To start the MariaDB instance and import the Bible data: docker-compose up
#
# To connect to the database from the host (presuming you have mysql-client or
# mariadb-client installed), use a command like this:
# mysql -h 127.0.0.1 -u root -p bible
#
# More information about Docker Compose: https://docs.docker.com/compose/
#
services:
bibledb:
image: mariadb:10.5
volumes:
- ./bible_databases/sql:/docker-entrypoint-initdb.d
env_file: ['.env']
restart: always
biblebot:
build:
context: .
env_file: ['.env']
depends_on:
- bibledb
volumes:
- ./lastrun.txt:/lastrun.txt
# restart: unless-stopped

5
go.mod 100644
View File

@ -0,0 +1,5 @@
module fsdfsd.net/GeneralHurricane/BibleBot
go 1.15
require github.com/go-sql-driver/mysql v1.7.0

2
go.sum 100644
View File

@ -0,0 +1,2 @@
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=

226
main.go
View File

@ -1,14 +1,39 @@
package main
import (
"bufio"
"database/sql"
"fmt"
"log"
"os"
"strconv"
"strings"
"time"
_ "github.com/go-sql-driver/mysql"
)
const NAME string = "Tʜᴇ Lʀᴅ"
var BibleDB *sql.DB
var BibleQuery *sql.Stmt
var Client *RDramaClient
var WAIT_TIME time.Duration
var ONE_SEC time.Duration
var BibleBotID int
func main() {
ERRCOUNT := 0
WAIT_TIME, err := time.ParseDuration("1m")
if err != nil {
log.Fatal("Error parsing time")
}
time.Sleep(WAIT_TIME)
ONE_SEC, err = time.ParseDuration("1s")
if err != nil {
log.Fatal("Error parsing time 2")
}
fmt.Printf("Running...\n")
siteURL := os.Getenv("RDRAMA_URL")
if siteURL == "" {
@ -20,24 +45,191 @@ func main() {
log.Fatal("RDRAMA_API_TOKEN environment variable not set")
}
client := NewRDramaClient(siteURL, siteToken)
now := time.Now()
comments, err := client.GetComments(SearchParams{
Hole: "dankchristianmemes",
After: time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC),
})
botId := os.Getenv("BIBLEBOT_ID")
if botId == "" {
log.Fatal("BIBLEBOT_ID environment variable not set")
}
BibleBotID, err = strconv.Atoi(botId)
if err != nil {
log.Fatal(err)
panic(err)
}
fmt.Printf("Found comments: %d\n", len(comments))
for _, comment := range comments {
// fmt.Printf("%s: %s\n", comment.Author, comment.Text)
_ = GetReferences(strings.ToUpper(comment.Text))
log.Fatal("Error loading BIBLEBOT_ID: ", botId)
}
// Use the client to make API requests
Client = NewRDramaClient(siteURL, siteToken)
username := os.Getenv("MARIADB_USER")
password := os.Getenv("MARIADB_PASSWORD")
host := os.Getenv("BIBLEDB_HOST")
database := os.Getenv("MARIADB_DATABASE")
for ERRCOUNT < 5 {
BibleDB, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s", username, password, host, database))
if err != nil {
fmt.Println("ERROR connecting: ", err)
fmt.Println("DB string: ", fmt.Sprintf("%s:%s@tcp(%s)/%s", username, password, host, database))
ERRCOUNT = ERRCOUNT + 1
time.Sleep(WAIT_TIME)
} else {
ERRCOUNT = 0
break
}
}
defer BibleDB.Close()
BibleQuery, err = BibleDB.Prepare("SELECT t FROM t_web WHERE id BETWEEN ? AND ?")
if err != nil {
fmt.Println(err)
log.Fatal("Unable to prepare query")
}
defer BibleQuery.Close()
now := time.Now()
lastrun := time.Date(now.Year(), now.Month(), now.Day(), now.Hour()-1, 0, 0, 0, time.UTC)
file, err := os.Open("lastrun.txt")
if err == nil {
// Create a new scanner to read the file
scanner := bufio.NewScanner(file)
// Read the first line of the file
scanner.Scan()
line := scanner.Text()
// Convert the line to an integer
num, err := strconv.ParseInt(line, 10, 64)
if err == nil {
newTime := time.Unix(num, 0)
// fmt.Println("New time: ", newTime)
if newTime != time.Unix(0, 0) {
lastrun = newTime
}
} else {
fmt.Println("error parsing time", err)
}
file.Close()
} else {
fmt.Println("Err opening lastrun: ", err)
}
for ERRCOUNT < 5 {
fmt.Println("\n\n-------------------------------------------------------")
fmt.Printf("Posts between %v - %v\n", lastrun, now)
posts, err := Client.GetPosts(SearchParams{
Hole: "dankchristianmemes",
After: lastrun,
Before: now,
})
if err == nil {
fmt.Printf("Found posts: %d\n", len(posts))
for _, post := range posts {
// fmt.Printf("%s: %s\n", comment.AuthorName, comment.Text)
if post.Author.ID != BibleBotID { // originally forgot this lol
handleAndReply(fmt.Sprintf("%s\n%s", post.Title, post.Text), fmt.Sprintf("p_%v", post.ID))
} else {
fmt.Println("Skipping own post")
}
}
} else {
ERRCOUNT = ERRCOUNT + 1
time.Sleep(ONE_SEC)
continue
}
fmt.Printf("Comments between %v - %v\n", lastrun, now)
comments, err := Client.GetComments(SearchParams{
Hole: "dankchristianmemes",
After: lastrun,
Before: now,
})
if err == nil {
fmt.Printf("Found comments: %d\n", len(comments))
for _, comment := range comments {
// fmt.Printf("%s: %s\n", comment.AuthorName, comment.Text)
if comment.Author.ID != BibleBotID {
handleAndReply(comment.Text, fmt.Sprintf("c_%v", comment.ID))
} else {
fmt.Println("Skipping own comment")
}
// Client.UpvoteComment(comment.ID) // not allowed for bots
}
lastrun = now.Add(ONE_SEC)
file, err = os.Create("lastrun.txt")
if err == nil {
_, err = file.WriteString(strconv.FormatInt(lastrun.Unix(), 10))
if err != nil {
fmt.Println("FILE ERROR: ", err)
}
err = file.Sync()
if err != nil {
fmt.Println("FILE ERROR: ", err)
}
file.Close()
}
if err != nil {
fmt.Println("FILE ERROR: ", err)
}
} else {
ERRCOUNT = ERRCOUNT + 1
if ERRCOUNT > 4 {
log.Fatal(err)
}
}
time.Sleep(WAIT_TIME)
now = time.Now()
}
}
func handleAndReply(text string, parent_fullname string) {
refs := GetReferences(strings.ToUpper(text))
if len(refs) == 0 {
return
}
var post strings.Builder
for _, ref := range refs {
rows, err := BibleQuery.Query(ref.StartKey, ref.EndKey)
if err != nil {
fmt.Println(err)
continue
}
canName, err := ref.CanonicalName()
post.WriteString("**")
if err != nil {
post.WriteString(ref.RegexMatch)
} else {
post.WriteString(canName)
}
post.WriteString("**\n> ")
for rows.Next() {
var verse string
err = rows.Scan(&verse)
if err != nil {
fmt.Println(err)
continue
}
verse = strings.ReplaceAll(verse, "Yahweh", NAME)
verse = strings.ReplaceAll(verse, "\n", "\n> ")
post.WriteString(verse)
post.WriteString(" ")
}
rows.Close()
post.WriteString("\n\n")
}
fmt.Println(post.String())
err := Client.PostComment(post.String(), parent_fullname)
if err != nil {
fmt.Println(err)
} else {
time.Sleep(ONE_SEC)
}
}

101
rdrama.go
View File

@ -1,10 +1,12 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
neturl "net/url"
"time"
)
@ -20,15 +22,20 @@ func NewRDramaClient(baseURL, apiKey string) *RDramaClient {
}
}
func (c *RDramaClient) makeRequest(method, path string, v interface{}) error {
func (c *RDramaClient) makeRequest(method, path string, body string, response interface{}) error {
url := c.baseURL + path
req, err := http.NewRequest(method, url, nil)
var reader io.Reader
reader = bytes.NewBufferString(body)
req, err := http.NewRequest(method, url, reader)
if err != nil {
return err
}
req.Header.Add("Authorization", fmt.Sprintf("%s", c.apiKey))
if body != "" {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
@ -37,28 +44,71 @@ func (c *RDramaClient) makeRequest(method, path string, v interface{}) error {
defer resp.Body.Close()
if !(resp.StatusCode >= 200 && resp.StatusCode < 300) {
fmt.Println("request:\n", req)
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
return json.NewDecoder(resp.Body).Decode(v)
if response != nil {
return json.NewDecoder(resp.Body).Decode(response)
}
// fmt.Println("reponse: ")
// _, err = io.Copy(os.Stdout, resp.Body)
if err != nil {
fmt.Println("Error copyng response body: ", err)
// handle the error
}
return nil
}
type CommentResponse struct {
Data []Comment `json:"data"`
}
type PostResponse struct {
Data []Post `json:"data"`
}
type Comment struct {
ID int `json:"id"`
Author string `json:"author_name"`
Author Author `json:"author"`
AuthorName string `json:"author_name"`
Text string `json:"body"`
}
type Author struct {
ID int `json:id`
}
type Post struct {
ID int `json:"id"`
Author Author `json:"author"`
AuthorName string `json:"author_name"`
Text string `json:"body"`
Title string `json:"title"`
}
type CommentPost struct {
ParentID string `json:parent_fullname`
Body string `json:body`
}
type SearchParams struct {
Hole string
After time.Time
Before time.Time
}
func (c *RDramaClient) UpvotePost(id int) error {
url := fmt.Sprintf("/vote/post/%v/1", id)
return c.makeRequest("POST", url, "", nil)
}
func (c *RDramaClient) UpvoteComment(id int) error {
url := fmt.Sprintf("/vote/comment/%v/1", id)
return c.makeRequest("POST", url, "", nil)
}
func (c *RDramaClient) GetComments(params SearchParams) ([]Comment, error) {
var s string
if params.Hole != "" {
@ -71,14 +121,47 @@ func (c *RDramaClient) GetComments(params SearchParams) ([]Comment, error) {
if params.Before != nullTime {
s += fmt.Sprintf(" before:%d", params.Before.Unix())
}
v := url.Values{}
v := neturl.Values{}
v.Set("q", s)
url := "/search/comments?" + v.Encode()
fmt.Printf("URL: %s\n", url)
var comments CommentResponse
if err := c.makeRequest("GET", url, &comments); err != nil {
if err := c.makeRequest("GET", url, "", &comments); err != nil {
return nil, err
}
return comments.Data, nil
}
func (c *RDramaClient) GetPosts(params SearchParams) ([]Post, error) {
var s string
if params.Hole != "" {
s += fmt.Sprintf("hole:%s", params.Hole)
}
nullTime := time.Time{}
if params.After != nullTime {
s += fmt.Sprintf(" after:%d", params.After.Unix())
}
if params.Before != nullTime {
s += fmt.Sprintf(" before:%d", params.Before.Unix())
}
v := neturl.Values{}
v.Set("q", s)
url := "/search/posts?" + v.Encode()
fmt.Printf("URL: %s\n", url)
var posts PostResponse
if err := c.makeRequest("GET", url, "", &posts); err != nil {
return nil, err
}
return posts.Data, nil
}
func (c *RDramaClient) PostComment(body string, parent string) error {
v := neturl.Values{}
v.Set("parent_fullname", parent)
v.Set("body", body)
fmt.Println(v.Encode())
return c.makeRequest("POST", "/comment", v.Encode(), nil)
}