init
This commit is contained in:
commit
0832ebf702
|
@ -0,0 +1,4 @@
|
||||||
|
*.db
|
||||||
|
go-dette
|
||||||
|
godette
|
||||||
|
godot.users.json
|
|
@ -0,0 +1,77 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/glebarez/go-sqlite"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var db *sqlx.DB
|
||||||
|
|
||||||
|
func InitDb() error {
|
||||||
|
newdb, err := sqlx.Connect("sqlite", "./bot.db?_time_format=sqlite")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
db = newdb
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := InitDb(); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Id int64 `json:"uid" db:"id"`
|
||||||
|
Name string `json:"full_name" db:"name"`
|
||||||
|
Username string `json:"username" db:"username"`
|
||||||
|
MessageCount int `json:"messagesCount" db:"message_count"`
|
||||||
|
Karma int `json:"karma" db:"karma_history"`
|
||||||
|
KarmaSent int `json:"karmaChanged" db:"karma_sent_history"`
|
||||||
|
KarmaReceived int `json:"karmaGot" db:"karma_received_history"`
|
||||||
|
Created time.Time `db:"created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var users []User
|
||||||
|
|
||||||
|
data, _ := os.ReadFile("godot.users.json")
|
||||||
|
err := json.Unmarshal(data, &users)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
var dbuser User
|
||||||
|
err := db.Get(&dbuser, `SELECT * FROM users WHERE id = $1`, user.Id)
|
||||||
|
if err != nil {
|
||||||
|
db.Exec(
|
||||||
|
`INSERT INTO users (id, name, username, message_count, karma_history, karma_sent_history, karma_received_history) VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
||||||
|
user.Id,
|
||||||
|
user.Name,
|
||||||
|
user.Username,
|
||||||
|
user.MessageCount,
|
||||||
|
user.Karma,
|
||||||
|
user.KarmaSent,
|
||||||
|
user.KarmaReceived,
|
||||||
|
)
|
||||||
|
db.Exec(
|
||||||
|
`INSERT INTO karma (from_user, to_user, change, message) VALUES (-1, $1, $2, "historical")`,
|
||||||
|
user.Id,
|
||||||
|
user.Karma,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
if user.MessageCount > dbuser.MessageCount || user.Karma > dbuser.Karma {
|
||||||
|
_, err := db.Exec(`UPDATE users SET karma_history=$2, message_count=$3, karma_sent_history=$4, karma_received_history=$5 WHERE id=$1;`, user.Id, user.Karma, user.MessageCount, user.KarmaSent, user.KarmaReceived)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(user.Id, err)
|
||||||
|
}
|
||||||
|
db.Exec(`UPDATE karma SET change=$2 WHERE from_user=-1 AND to_user=$1;`, user.Id, user.Karma)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Id int64 `db:"id"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
Username string `db:"username"`
|
||||||
|
MessageCount int `db:"message_count"`
|
||||||
|
Karma int `db:"karma_history"`
|
||||||
|
KarmaSent int `db:"karma_sent_history"`
|
||||||
|
KarmaReceived int `db:"karma_received_history"`
|
||||||
|
Created time.Time `db:"created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Karma struct {
|
||||||
|
Id int `db:"id"`
|
||||||
|
From int `db:"from_user"`
|
||||||
|
To int `db:"to_user"`
|
||||||
|
Change int `db:"change"`
|
||||||
|
Message string `db:"message"`
|
||||||
|
Created time.Time `db:"created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TotalKarma struct {
|
||||||
|
Id int64 `db:"id"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
Total int `db:"total"`
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-telegram/bot"
|
||||||
|
)
|
||||||
|
|
||||||
|
var docApiURL = "https://docs.godotengine.org/_/api/v3/search/?q=project:godot %s"
|
||||||
|
var docURL = "https://docs.godotengine.org/en/stable/search.html?q=%s"
|
||||||
|
var docApiURL_ru = "https://docs.godotengine.org/_/api/v3/search/?q=project:godot-ru %s"
|
||||||
|
var docURL_ru = "https://docs.godotengine.org/ru/4.x/search.html?q=%s"
|
||||||
|
var lastDocSearch = time.Now().Add(-1 * time.Hour)
|
||||||
|
|
||||||
|
type DocResponse struct {
|
||||||
|
Count uint `json:"count"`
|
||||||
|
Next string `json:"next"`
|
||||||
|
Previous string `json:"previous"`
|
||||||
|
Results []DocResult `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DocResult struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Highlights DocHighlights `json:"highlights"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DocHighlights struct {
|
||||||
|
Title []string `json:"title"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAndDecode[T any](u string) (*T, error) {
|
||||||
|
req, err := url.ParseRequestURI(u)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Can't parse url: ", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result, err := http.Get(req.String())
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Can't get http: ", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer result.Body.Close()
|
||||||
|
var response T
|
||||||
|
err = json.NewDecoder(result.Body).Decode(&response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDocs(topic string, russian bool) (string, error) {
|
||||||
|
apiURL := docApiURL
|
||||||
|
pageURL := docURL
|
||||||
|
if russian {
|
||||||
|
apiURL = docApiURL_ru
|
||||||
|
pageURL = docURL_ru
|
||||||
|
}
|
||||||
|
since := time.Since(lastDocSearch)
|
||||||
|
GO_SEARCH_YOURSELF := fmt.Sprintf("\n\n[Попробуйте поискать самостоятельно\\!](%s)", fmt.Sprintf(pageURL, url.QueryEscape(topic)))
|
||||||
|
if since < 5*time.Second {
|
||||||
|
return "Извините, запросы происходят слишком часто" + GO_SEARCH_YOURSELF, nil
|
||||||
|
}
|
||||||
|
lastDocSearch = time.Now()
|
||||||
|
|
||||||
|
topic_escaped := bot.EscapeMarkdown(topic)
|
||||||
|
not_found := fmt.Sprintf("Извините, по запросу *%s* ничего не найдено\\."+GO_SEARCH_YOURSELF, topic_escaped)
|
||||||
|
exactUrl := fmt.Sprintf(apiURL, url.QueryEscape(topic))
|
||||||
|
looselyUrl := fmt.Sprintf(apiURL, url.QueryEscape(topic+"*"))
|
||||||
|
response, err := getAndDecode[DocResponse](exactUrl)
|
||||||
|
if err != nil || len(response.Results) == 0 {
|
||||||
|
response, err = getAndDecode[DocResponse](looselyUrl)
|
||||||
|
if err != nil || len(response.Results) == 0 {
|
||||||
|
return not_found, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textResults := ""
|
||||||
|
for i, r := range response.Results {
|
||||||
|
if i > 9 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
text := bot.EscapeMarkdown(r.Title)
|
||||||
|
link, _ := url.JoinPath(r.Domain, r.Path)
|
||||||
|
textResults += fmt.Sprintf("%d\\. [%s](%s)\n", i+1, text, link)
|
||||||
|
}
|
||||||
|
textResults += fmt.Sprintf("\n\n Или вы можете попробовать [поискать самостоятельно](%s)\\!", fmt.Sprintf(pageURL, topic))
|
||||||
|
text := fmt.Sprintf("Вот что я нашла по запросу *%s*: \n\n%s", topic_escaped, textResults)
|
||||||
|
return text, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleDocRequest(g Godette) {
|
||||||
|
topic := strings.TrimPrefix(g.update.Message.Text, "/docs ")
|
||||||
|
text, docerr := getDocs(topic, false)
|
||||||
|
if docerr != nil {
|
||||||
|
log.Println("Can't get docs: ", docerr)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := g.ReplyWithMarkdown(text)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleDocRequestRu(g Godette) {
|
||||||
|
topic := strings.TrimPrefix(g.update.Message.Text, "/docs_ru ")
|
||||||
|
text, docerr := getDocs(topic, true)
|
||||||
|
if docerr != nil {
|
||||||
|
log.Println("Can't get docs: ", docerr)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := g.ReplyWithMarkdown(text)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
module git.nefrace.ru/nefrace/go-dette
|
||||||
|
|
||||||
|
go 1.23.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/glebarez/go-sqlite v1.22.0
|
||||||
|
github.com/go-telegram/bot v1.13.3
|
||||||
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/google/uuid v1.5.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
golang.org/x/sys v0.15.0 // indirect
|
||||||
|
modernc.org/libc v1.37.6 // indirect
|
||||||
|
modernc.org/mathutil v1.6.0 // indirect
|
||||||
|
modernc.org/memory v1.7.2 // indirect
|
||||||
|
modernc.org/sqlite v1.28.0 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,36 @@
|
||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||||
|
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
|
github.com/go-telegram/bot v1.13.3 h1:r2erpHI5rMQsR5TFWJ/XVqWHq9R228fcaejLFvXJsmM=
|
||||||
|
github.com/go-telegram/bot v1.13.3/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM=
|
||||||
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||||
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||||
|
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||||
|
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw=
|
||||||
|
modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
|
||||||
|
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||||
|
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||||
|
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||||
|
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||||
|
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
|
||||||
|
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
|
|
@ -0,0 +1,244 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/go-telegram/bot"
|
||||||
|
"github.com/go-telegram/bot/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func KarmaChange(g Godette, change int) {
|
||||||
|
log.Println("karma...")
|
||||||
|
from := g.update.Message.From
|
||||||
|
if g.update.Message.ReplyToMessage == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
to := g.update.Message.ReplyToMessage.From
|
||||||
|
|
||||||
|
if g.update.Message.ReplyToMessage.ForumTopicCreated != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Sent karma to themself
|
||||||
|
if from.ID == to.ID {
|
||||||
|
text, err := GetRandomTemplateByTag("karma_self")
|
||||||
|
if err != nil {
|
||||||
|
text = "_Осуждаю самолайки_"
|
||||||
|
}
|
||||||
|
// b.SendMessage(ctx, &sendMessageParams)
|
||||||
|
g.ReplyWithMarkdown(text)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sent karma to me
|
||||||
|
if me, err := g.GetMe(g.ctx); err == nil {
|
||||||
|
if to.ID == me.ID {
|
||||||
|
text, err := GetRandomTemplateByTag("karma_me")
|
||||||
|
if err != nil {
|
||||||
|
text = "_Спасибо, но мне это не нужно_"
|
||||||
|
}
|
||||||
|
g.ReplyWithMarkdown(text)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
k := Karma{}
|
||||||
|
if err := db.Get(&k, `SELECT * FROM karma WHERE from_user=$1 AND to_user=$2 AND strftime("%s", created) > strftime("%s", datetime("now"), "-1 minutes");`, from.ID, to.ID, change); err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
log.Println("karma not found")
|
||||||
|
_, insert_err := db.Exec(`INSERT INTO KARMA (from_user, to_user, change, message) VALUES ($1, $2, $3, $4)`, from.ID, to.ID, change, g.update.Message.Text)
|
||||||
|
log.Println("Insert karma error:", insert_err)
|
||||||
|
|
||||||
|
var from_karma, to_karma int
|
||||||
|
db.Get(&from_karma, `SELECT total FROM total_karma WHERE id=$1`, from.ID)
|
||||||
|
db.Get(&to_karma, `SELECT total FROM total_karma WHERE id=$1`, to.ID)
|
||||||
|
|
||||||
|
tag_text := "raise"
|
||||||
|
karma_text := "повысил"
|
||||||
|
if change < 0 {
|
||||||
|
tag_text = "lower"
|
||||||
|
karma_text = "понизил"
|
||||||
|
}
|
||||||
|
default_karma_text := fmt.Sprintf(
|
||||||
|
`*%[1]s \(%[3]d\)* %[5]s карму *%[2]s \(%[4]d\)*`,
|
||||||
|
bot.EscapeMarkdown(from.FirstName),
|
||||||
|
bot.EscapeMarkdown(to.FirstName),
|
||||||
|
from_karma,
|
||||||
|
to_karma,
|
||||||
|
karma_text)
|
||||||
|
text, err := GetRandomTemplateByTag(fmt.Sprintf("karma_%s", tag_text))
|
||||||
|
if err != nil {
|
||||||
|
text = default_karma_text
|
||||||
|
}
|
||||||
|
g.ReplyWithMarkdown(text)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println(err)
|
||||||
|
} else {
|
||||||
|
text, err := GetRandomTemplateByTag("too_fast")
|
||||||
|
if err != nil {
|
||||||
|
text = "_Ты слишком шустрый, попробуй ещё раз позднее\\._"
|
||||||
|
}
|
||||||
|
g.ReplyWithMarkdown(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func KarmaGoUp(ctx context.Context, b *bot.Bot, update *models.Update) {
|
||||||
|
KarmaChange(Godette{b, ctx, update}, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func KarmaGoDown(ctx context.Context, b *bot.Bot, update *models.Update) {
|
||||||
|
KarmaChange(Godette{b, ctx, update}, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopUsers(g Godette, reverse bool) {
|
||||||
|
type User struct {
|
||||||
|
Id int64 `db:"id"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
Total int `db:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `SELECT * FROM total_karma ORDER BY TOTAL %s LIMIT 10;`
|
||||||
|
descent := "DESC"
|
||||||
|
header := "*Вот наш ТОП\\-10 пользователей:*\n\n"
|
||||||
|
if reverse {
|
||||||
|
descent = ""
|
||||||
|
header = "*Вот наш _ОТРИЦАТЕЛЬНЫЙ_ ТОП\\-10:*\n\n"
|
||||||
|
}
|
||||||
|
query = fmt.Sprintf(query, descent)
|
||||||
|
|
||||||
|
users := []User{}
|
||||||
|
|
||||||
|
err := db.Select(&users, query)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("can't top users:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
textResult := ""
|
||||||
|
for i, user := range users {
|
||||||
|
name := bot.EscapeMarkdown(user.Name)
|
||||||
|
if user.Id == g.update.Message.From.ID {
|
||||||
|
name = "_*" + name + "*_"
|
||||||
|
}
|
||||||
|
textResult += fmt.Sprintf("%d \\- %s \\(%s\\)\n", i, name, bot.EscapeMarkdown(strconv.Itoa(user.Total)))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, send_err := g.Bot.SendMessage(g.ctx, &bot.SendMessageParams{
|
||||||
|
Text: fmt.Sprintf("%s%s", header, textResult),
|
||||||
|
ParseMode: models.ParseModeMarkdown,
|
||||||
|
ChatID: g.update.Message.Chat.ID,
|
||||||
|
MessageThreadID: g.update.Message.MessageThreadID,
|
||||||
|
})
|
||||||
|
if send_err != nil {
|
||||||
|
log.Println("can't send top: ", send_err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleUsersTop(g Godette) {
|
||||||
|
GetTopUsers(g, false)
|
||||||
|
}
|
||||||
|
func HandleUsersBottom(g Godette) {
|
||||||
|
GetTopUsers(g, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleStats(g Godette) {
|
||||||
|
user := g.update.Message.From
|
||||||
|
userinfo := User{}
|
||||||
|
var total_karma, karma_sent, karma_received, place int
|
||||||
|
var same_karma []TotalKarma
|
||||||
|
|
||||||
|
uerr := db.Get(&userinfo, `SELECT * FROM users WHERE id=$1;`, user.ID)
|
||||||
|
if uerr != nil {
|
||||||
|
g.ReplyWithText("Не могу найти пользователя")
|
||||||
|
log.Println("can't find user info:", uerr)
|
||||||
|
}
|
||||||
|
db.Get(&total_karma, `SELECT total FROM total_karma WHERE id=$1;`, user.ID)
|
||||||
|
db.Get(&karma_sent, `SELECT COUNT(*) FROM karma WHERE from_user=$1;`, user.ID)
|
||||||
|
db.Get(&karma_received, `SELECT COUNT(*) FROM karma WHERE to_user=$1 AND from_user!=-1;`, user.ID)
|
||||||
|
db.Get(&place, `SELECT COUNT(DISTINCT(total)) FROM total_karma WHERE total > $1`, total_karma)
|
||||||
|
db.Select(&same_karma, `SELECT * FROM total_karma WHERE total = $1`, total_karma)
|
||||||
|
|
||||||
|
same_karma_text := ""
|
||||||
|
if len(same_karma) > 1 {
|
||||||
|
same_karma_text += "\nПользователи с такой же кармой: "
|
||||||
|
count := 0
|
||||||
|
for _, u := range same_karma {
|
||||||
|
if u.Id == user.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
same_karma_text += bot.EscapeMarkdown(u.Name)
|
||||||
|
count += 1
|
||||||
|
if count == 5 {
|
||||||
|
same_karma_text += fmt.Sprintf(" \\(и ещё %d\\)", len(same_karma)-5)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
same_karma_text += ", "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text := `
|
||||||
|
Вот что я знаю о тебе, *%[1]s*:
|
||||||
|
|
||||||
|
Примерное количество сообщений: *%[5]d*
|
||||||
|
Твоя карма: *%[2]s*
|
||||||
|
Кармы отправил: *%[6]d*
|
||||||
|
Кармы получил: *%[7]d*
|
||||||
|
Примерное место среди пользователей: *%[3]d* %[4]s
|
||||||
|
`
|
||||||
|
text_formatted := fmt.Sprintf(
|
||||||
|
text,
|
||||||
|
bot.EscapeMarkdown(user.FirstName),
|
||||||
|
bot.EscapeMarkdown(strconv.Itoa(total_karma)),
|
||||||
|
place+1, same_karma_text,
|
||||||
|
userinfo.MessageCount,
|
||||||
|
karma_sent+userinfo.KarmaSent,
|
||||||
|
karma_received+userinfo.KarmaReceived,
|
||||||
|
)
|
||||||
|
|
||||||
|
_, err := g.ReplyWithMarkdown(text_formatted)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("can't send status:", err)
|
||||||
|
fmt.Println(text_formatted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleReactions(ctx context.Context, b *bot.Bot, u *models.Update) {
|
||||||
|
reaction := u.MessageReaction
|
||||||
|
|
||||||
|
had_like_before := false
|
||||||
|
for _, r := range reaction.OldReaction {
|
||||||
|
if r.Type == models.ReactionTypeTypeEmoji {
|
||||||
|
switch r.ReactionTypeEmoji.Emoji {
|
||||||
|
case "❤️", "👍":
|
||||||
|
had_like_before = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
has_like_now := false
|
||||||
|
for _, r := range reaction.NewReaction {
|
||||||
|
if r.Type == models.ReactionTypeTypeEmoji {
|
||||||
|
switch r.ReactionTypeEmoji.Emoji {
|
||||||
|
case "🩷", "👍":
|
||||||
|
has_like_now = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !had_like_before && has_like_now {
|
||||||
|
log.Println("Now have like")
|
||||||
|
} else if had_like_before && !has_like_now {
|
||||||
|
log.Println("Have no likes")
|
||||||
|
} else {
|
||||||
|
log.Println("likes didn't changed")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,205 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
_ "github.com/glebarez/go-sqlite"
|
||||||
|
"github.com/go-telegram/bot"
|
||||||
|
"github.com/go-telegram/bot/models"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var BUILD_TIME string
|
||||||
|
|
||||||
|
var db *sqlx.DB
|
||||||
|
|
||||||
|
type Godette struct {
|
||||||
|
*bot.Bot
|
||||||
|
ctx context.Context
|
||||||
|
update *models.Update
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeHandler(handler func(g Godette)) func(ctx context.Context, b *bot.Bot, update *models.Update) {
|
||||||
|
return func(ctx context.Context, b *bot.Bot, update *models.Update) {
|
||||||
|
if update.Message.Caption != "" && update.Message.Text == "" {
|
||||||
|
update.Message.Text = update.Message.Caption
|
||||||
|
}
|
||||||
|
handler(Godette{b, ctx, update})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Godette) ReplyWithText(text string) (*models.Message, error) {
|
||||||
|
return g.Bot.SendMessage(g.ctx, &bot.SendMessageParams{
|
||||||
|
Text: text,
|
||||||
|
ChatID: g.update.Message.Chat.ID,
|
||||||
|
MessageThreadID: g.update.Message.MessageThreadID,
|
||||||
|
ReplyParameters: &models.ReplyParameters{
|
||||||
|
ChatID: g.update.Message.Chat.ID,
|
||||||
|
MessageID: g.update.Message.ID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Godette) AnswerWithMarkdown(text string) (*models.Message, error) {
|
||||||
|
return g.Bot.SendMessage(g.ctx, &bot.SendMessageParams{
|
||||||
|
Text: text,
|
||||||
|
ChatID: g.update.Message.Chat.ID,
|
||||||
|
MessageThreadID: g.update.Message.MessageThreadID,
|
||||||
|
ParseMode: models.ParseModeMarkdown,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Godette) ReplyWithMarkdown(text string) (*models.Message, error) {
|
||||||
|
return g.Bot.SendMessage(g.ctx, &bot.SendMessageParams{
|
||||||
|
Text: text,
|
||||||
|
ChatID: g.update.Message.Chat.ID,
|
||||||
|
MessageThreadID: g.update.Message.MessageThreadID,
|
||||||
|
ParseMode: models.ParseModeMarkdown,
|
||||||
|
ReplyParameters: &models.ReplyParameters{
|
||||||
|
ChatID: g.update.Message.Chat.ID,
|
||||||
|
MessageID: g.update.Message.ID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
token := os.Getenv("TOKEN")
|
||||||
|
if token == "" {
|
||||||
|
panic("No token was provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := InitDb(); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := []bot.Option{
|
||||||
|
bot.WithDefaultHandler(defHandler),
|
||||||
|
bot.WithMiddlewares(writeUsersToDB),
|
||||||
|
bot.WithAllowedUpdates(bot.AllowedUpdates{
|
||||||
|
"message",
|
||||||
|
"callback_query",
|
||||||
|
"message_reaction",
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
b, _ := bot.New(token, opts...)
|
||||||
|
b.RegisterHandler(bot.HandlerTypeMessageText, "/me ", bot.MatchTypePrefix, HandleMe)
|
||||||
|
b.RegisterHandler(bot.HandlerTypeMessageText, "/help", bot.MatchTypePrefix, MakeHandler(HandleHelp))
|
||||||
|
b.RegisterHandler(bot.HandlerTypeMessageText, "/docs ", bot.MatchTypePrefix, MakeHandler(HandleDocRequest))
|
||||||
|
b.RegisterHandler(bot.HandlerTypeMessageText, "/docs_ru ", bot.MatchTypePrefix, MakeHandler(HandleDocRequestRu))
|
||||||
|
b.RegisterHandler(bot.HandlerTypeMessageText, "/top", bot.MatchTypePrefix, MakeHandler(HandleUsersTop))
|
||||||
|
b.RegisterHandler(bot.HandlerTypeMessageText, "/bottom", bot.MatchTypePrefix, MakeHandler(HandleUsersBottom))
|
||||||
|
b.RegisterHandler(bot.HandlerTypeMessageText, "/stats", bot.MatchTypePrefix, MakeHandler(HandleStats))
|
||||||
|
b.RegisterHandler(bot.HandlerTypeMessageText, "+", bot.MatchTypePrefix, KarmaGoUp)
|
||||||
|
b.RegisterHandler(bot.HandlerTypeMessageText, "Спасибо", bot.MatchTypePrefix, KarmaGoUp)
|
||||||
|
b.RegisterHandler(bot.HandlerTypeMessageText, "спасибо", bot.MatchTypePrefix, KarmaGoUp)
|
||||||
|
b.RegisterHandler(bot.HandlerTypeMessageText, "СПАСИБО", bot.MatchTypePrefix, KarmaGoUp)
|
||||||
|
b.RegisterHandler(bot.HandlerTypeMessageText, "-", bot.MatchTypePrefix, KarmaGoDown)
|
||||||
|
b.RegisterHandlerMatchFunc(func(update *models.Update) bool {
|
||||||
|
return update.MessageReaction != nil
|
||||||
|
}, HandleReactions)
|
||||||
|
|
||||||
|
b.SetMyCommands(context.Background(), &bot.SetMyCommandsParams{
|
||||||
|
Commands: commands,
|
||||||
|
Scope: &models.BotCommandScopeDefault{},
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Start(ctx)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitDb() error {
|
||||||
|
newdb, err := sqlx.Connect("sqlite", "./bot.db?_time_format=sqlite")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
db = newdb
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func defHandler(ctx context.Context, b *bot.Bot, update *models.Update) {
|
||||||
|
|
||||||
|
}
|
||||||
|
func HandleMe(ctx context.Context, b *bot.Bot, update *models.Update) {
|
||||||
|
g := Godette{b, ctx, update}
|
||||||
|
g.AnswerWithMarkdown(fmt.Sprintf(
|
||||||
|
"_*%s* %s_",
|
||||||
|
bot.EscapeMarkdown(update.Message.From.FirstName),
|
||||||
|
bot.EscapeMarkdown(strings.TrimPrefix(update.Message.Text, "/me ")),
|
||||||
|
))
|
||||||
|
b.DeleteMessage(ctx, &bot.DeleteMessageParams{
|
||||||
|
ChatID: update.Message.Chat.ID,
|
||||||
|
MessageID: update.Message.ID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var commands = []models.BotCommand{
|
||||||
|
{Command: "/help", Description: "Показать данное сообщение"},
|
||||||
|
{Command: "/me", Description: "Написать своё сообщение от третьего лица"},
|
||||||
|
{Command: "/top", Description: "Вывод ТОП-10 пользователей"},
|
||||||
|
{Command: "/bottom", Description: "Отрицательный ТОП-10"},
|
||||||
|
{Command: "/stats", Description: "Информация о пользователе"},
|
||||||
|
{Command: "/docs", Description: "Поиск по документации"},
|
||||||
|
{Command: "/docs_ru", Description: "Поиск по русскоязычной документации"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleHelp(g Godette) {
|
||||||
|
commands_text := ""
|
||||||
|
for _, command := range commands {
|
||||||
|
commands_text += fmt.Sprintf("`%s` \\- %s\n", bot.EscapeMarkdown(command.Command), bot.EscapeMarkdown(command.Description))
|
||||||
|
}
|
||||||
|
log.Println(g.ReplyWithMarkdown(fmt.Sprintf(
|
||||||
|
`
|
||||||
|
Мои команды:
|
||||||
|
|
||||||
|
%s
|
||||||
|
|
||||||
|
Дата сборки бота: %s
|
||||||
|
`,
|
||||||
|
commands_text,
|
||||||
|
bot.EscapeMarkdown(BUILD_TIME),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRandomTemplateByTag(tag string) (string, error) {
|
||||||
|
var msg string
|
||||||
|
err := db.Get(
|
||||||
|
&msg,
|
||||||
|
`SELECT text FROM message_templates WHERE tag=$1 ORDER BY RANDOM() LIMIT 1;`,
|
||||||
|
tag,
|
||||||
|
)
|
||||||
|
return msg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeUsersToDB(next bot.HandlerFunc) bot.HandlerFunc {
|
||||||
|
return func(ctx context.Context, bot *bot.Bot, update *models.Update) {
|
||||||
|
if update.Message != nil {
|
||||||
|
user := update.Message.From
|
||||||
|
chat := update.Message.Chat
|
||||||
|
_, err := db.Exec(
|
||||||
|
`
|
||||||
|
INSERT INTO users(id, name, username) VALUES($1, $2, $3) ON CONFLICT(id) DO UPDATE SET name=$2, username=$3, message_count=message_count+1;
|
||||||
|
INSERT INTO chats(id, name, username) VALUES($4, $5, $6) ON CONFLICT(id) DO UPDATE SET name=$5, username=$6;
|
||||||
|
`,
|
||||||
|
user.ID,
|
||||||
|
strings.TrimSpace(fmt.Sprintf("%s %s", user.FirstName, user.LastName)),
|
||||||
|
user.Username,
|
||||||
|
chat.ID,
|
||||||
|
chat.FirstName,
|
||||||
|
chat.Username,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next(ctx, bot, update)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
SELECT 'up SQL query';
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
username TEXT DEFAULT "",
|
||||||
|
created DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS chats (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name TEXT,
|
||||||
|
username TEXT DEFAULT "",
|
||||||
|
created DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS admins (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
chat_id INTEGER NOT NULL,
|
||||||
|
created DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users (id),
|
||||||
|
FOREIGN KEY (chat_id) REFERENCES chats (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS karma (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
from_user INTEGER NOT NULL,
|
||||||
|
to_user INTEGER NOT NULL,
|
||||||
|
change INTEGER,
|
||||||
|
message TEXT DEFAULT "",
|
||||||
|
created DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CHECK(from_user != to_user),
|
||||||
|
|
||||||
|
FOREIGN KEY (from_user) REFERENCES users(id),
|
||||||
|
FOREIGN KEY (to_user) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS warnings (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user INTEGER NOT NULL,
|
||||||
|
admin INTEGER NOT NULL,
|
||||||
|
message_id INTEGER DEFAULT 0,
|
||||||
|
topic_id INTEGER DEFAULT 0,
|
||||||
|
chat_id INTEGER NOT NULL,
|
||||||
|
active INTEGER DEFAULT 1,
|
||||||
|
reason TEXT DEFAULT "",
|
||||||
|
|
||||||
|
created DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
FOREIGN KEY (user) REFERENCES users (id),
|
||||||
|
FOREIGN KEY (admin) REFERENCES users (id),
|
||||||
|
FOREIGN KEY (chat_id) REFERENCES chats (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS admin_topics (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
chat_id INTEGER NOT NULL,
|
||||||
|
topic_id INTEGER NOT NULL,
|
||||||
|
|
||||||
|
created DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
FOREIGN KEY (chat_id) REFERENCES chats (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS message_templates (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
tag TEXT NOT NULL,
|
||||||
|
text TEXT NOT NULL,
|
||||||
|
triggers TEXT,
|
||||||
|
|
||||||
|
created DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
SELECT 'down SQL query';
|
||||||
|
DROP TABLE users ;
|
||||||
|
DROP TABLE chats ;
|
||||||
|
DROP TABLE admins ;
|
||||||
|
DROP TABLE karma ;
|
||||||
|
DROP TABLE warnings ;
|
||||||
|
DROP TABLE admin_topics ;
|
||||||
|
DROP TABLE message_templates ;
|
||||||
|
|
||||||
|
-- +goose StatementEnd
|
|
@ -0,0 +1,15 @@
|
||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
SELECT 'up SQL query';
|
||||||
|
CREATE VIEW IF NOT EXISTS total_karma AS SELECT u.id, u.name, sum(change) as total from karma
|
||||||
|
INNER JOIN users u ON u.id=karma.to_user
|
||||||
|
WHERE 1
|
||||||
|
GROUP BY u.id
|
||||||
|
ORDER BY total DESC;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
SELECT 'down SQL query';
|
||||||
|
DROP VIEW total_karma
|
||||||
|
-- +goose StatementEnd
|
|
@ -0,0 +1,17 @@
|
||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
SELECT 'up SQL query';
|
||||||
|
ALTER TABLE users ADD COLUMN message_count INTEGER DEFAULT 0;
|
||||||
|
ALTER TABLE users ADD COLUMN karma_history INTEGER DEFAULT 0;
|
||||||
|
ALTER TABLE users ADD COLUMN karma_sent_history INTEGER DEFAULT 0;
|
||||||
|
ALTER TABLE users ADD COLUMN karma_received_history INTEGER DEFAULT 0;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
SELECT 'down SQL query';
|
||||||
|
|
||||||
|
ALTER TABLE users DROP COLUMN message_count;
|
||||||
|
ALTER TABLE users DROP COLUMN karma_sent_history;
|
||||||
|
ALTER TABLE users DROP COLUMN karma_received_history;
|
||||||
|
-- +goose StatementEnd
|
Loading…
Reference in New Issue