2021-01-16 00:20:50 +03:00
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
2021-01-16 17:55:13 +03:00
|
|
|
|
"encoding/json"
|
2021-01-16 00:20:50 +03:00
|
|
|
|
"fmt"
|
2021-01-16 17:55:13 +03:00
|
|
|
|
"io/ioutil"
|
2021-01-16 13:33:30 +03:00
|
|
|
|
"math/rand"
|
|
|
|
|
"net/http"
|
2021-01-16 00:20:50 +03:00
|
|
|
|
"os"
|
|
|
|
|
"regexp"
|
2021-01-19 11:50:20 +03:00
|
|
|
|
"strings"
|
2021-01-16 17:55:13 +03:00
|
|
|
|
"sync"
|
2021-01-16 13:33:30 +03:00
|
|
|
|
"time"
|
2021-01-16 00:20:50 +03:00
|
|
|
|
|
2021-01-16 17:55:13 +03:00
|
|
|
|
"log"
|
|
|
|
|
|
2021-01-19 11:50:20 +03:00
|
|
|
|
"strconv"
|
|
|
|
|
|
2021-01-16 00:20:50 +03:00
|
|
|
|
irc "github.com/thoj/go-ircevent"
|
|
|
|
|
)
|
|
|
|
|
|
2021-01-16 18:23:56 +03:00
|
|
|
|
var channel = "#ctf"
|
2021-01-17 12:50:12 +03:00
|
|
|
|
var sendHi = ""
|
2021-01-27 12:32:04 +03:00
|
|
|
|
var writeHistory = false
|
2021-01-16 00:20:50 +03:00
|
|
|
|
|
|
|
|
|
const (
|
2021-01-19 11:50:20 +03:00
|
|
|
|
pattern = "(?i)\\b(cat|gato|miau|meow|garfield|lolcat)[s|z]{0,1}\\b"
|
2021-01-16 17:55:13 +03:00
|
|
|
|
nyastat = "nyastat"
|
2021-01-16 13:33:30 +03:00
|
|
|
|
msgPrefix = "I love cats! Here's a fact: %s ^_^"
|
|
|
|
|
gifPrefix = "Meow! Here's a gif: %s ^_^"
|
2021-01-16 00:20:50 +03:00
|
|
|
|
)
|
|
|
|
|
|
2021-01-17 12:50:12 +03:00
|
|
|
|
// environment variables
|
|
|
|
|
const (
|
2021-01-27 12:32:04 +03:00
|
|
|
|
userEnv = "IRC_USER"
|
|
|
|
|
nickEnv = "IRC_NICK"
|
|
|
|
|
himsgEnv = "IRC_SAY_HI"
|
|
|
|
|
passEnv = "IRC_PASS"
|
|
|
|
|
chanEnv = "IRC_CHAN"
|
|
|
|
|
urlEnv = "IRC_URL"
|
|
|
|
|
invitionsEnv = "IRC_ENABLE_INVITIONS"
|
|
|
|
|
chanHistoryEnv = "IRC_CHAN_HISTORY"
|
|
|
|
|
dbPathEnv = "IRC_DB_PATH"
|
2021-01-17 12:50:12 +03:00
|
|
|
|
)
|
|
|
|
|
|
2021-01-16 00:20:50 +03:00
|
|
|
|
type catFact struct {
|
|
|
|
|
Fact string `json:"fact"`
|
|
|
|
|
Length int `json:"length"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
re = regexp.MustCompile(pattern)
|
|
|
|
|
catFactsURL = "http://catfact.ninja/fact"
|
|
|
|
|
)
|
|
|
|
|
|
2021-01-16 17:55:13 +03:00
|
|
|
|
type stats struct {
|
|
|
|
|
userMessages map[string]uint
|
2021-01-19 11:50:20 +03:00
|
|
|
|
invites map[string]bool
|
2021-01-27 12:32:04 +03:00
|
|
|
|
history *history
|
2021-01-16 17:55:13 +03:00
|
|
|
|
mux *sync.Mutex
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newStats() *stats {
|
|
|
|
|
return &stats{
|
|
|
|
|
userMessages: make(map[string]uint),
|
2021-01-19 11:50:20 +03:00
|
|
|
|
invites: make(map[string]bool),
|
2021-01-16 17:55:13 +03:00
|
|
|
|
mux: &sync.Mutex{},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *stats) addUser(user string) {
|
|
|
|
|
s.mux.Lock()
|
|
|
|
|
defer s.mux.Unlock()
|
|
|
|
|
|
|
|
|
|
if v, ok := s.userMessages[user]; ok {
|
|
|
|
|
log.Printf("User %s writes %d message", user, v+1)
|
|
|
|
|
s.userMessages[user]++
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
log.Printf("First message from user %s", user)
|
|
|
|
|
s.userMessages[user] = 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *stats) online(e *irc.Event) int {
|
|
|
|
|
e.Connection.Mutex.Lock()
|
|
|
|
|
defer e.Connection.Mutex.Unlock()
|
|
|
|
|
|
|
|
|
|
return -1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *stats) realStats(e *irc.Event) {
|
2021-01-19 11:50:20 +03:00
|
|
|
|
e.Connection.Privmsg(e.Nick, "🙀🙀🙀 WoW 🙀🙀🙀 NYAStat 🙀🙀🙀")
|
2021-01-16 17:55:13 +03:00
|
|
|
|
t := "User %s sent %d messages"
|
|
|
|
|
for u, v := range s.userMessages {
|
2021-01-19 11:50:20 +03:00
|
|
|
|
e.Connection.Privmsg(e.Nick, fmt.Sprintf(t, u, v))
|
2021-01-16 17:55:13 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *stats) printStats(e *irc.Event) {
|
|
|
|
|
s.online(e)
|
|
|
|
|
s.realStats(e)
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-19 11:50:20 +03:00
|
|
|
|
func (s *stats) invitionRequest(e *irc.Event) {
|
|
|
|
|
s.mux.Lock()
|
|
|
|
|
defer s.mux.Unlock()
|
|
|
|
|
|
|
|
|
|
if !isInvitionsEnabled() {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if sended, ok := s.invites[e.Nick]; !ok || !sended {
|
|
|
|
|
log.Printf("Sending invite request to %s", e.Nick)
|
|
|
|
|
|
|
|
|
|
s.invites[e.Nick] = true
|
|
|
|
|
e.Connection.Privmsg(e.Nick, fmt.Sprintf("Привет, %s! В тиму 0х0 нужны два опытных человека. + в приват @difrex", e.Nick))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-17 12:50:12 +03:00
|
|
|
|
func addCallbacks(irccon *irc.Connection, s *stats) {
|
2021-01-16 00:20:50 +03:00
|
|
|
|
irccon.AddCallback("001", func(e *irc.Event) {
|
|
|
|
|
log.Println("Welcome message:", e.Message())
|
|
|
|
|
|
2021-01-17 12:50:12 +03:00
|
|
|
|
// Login
|
|
|
|
|
e.Connection.Privmsg("nickserv", "identify "+os.Getenv(passEnv))
|
2021-01-16 00:20:50 +03:00
|
|
|
|
|
2021-01-17 12:50:12 +03:00
|
|
|
|
// Join to channel
|
|
|
|
|
e.Connection.Join(channel)
|
|
|
|
|
|
2021-01-19 11:50:20 +03:00
|
|
|
|
e.Connection.Action(channel, "=^_^= https://gitea.difrex.ru/CTF2021/irc_bot")
|
2021-01-27 12:32:04 +03:00
|
|
|
|
e.Connection.Action(channel, "😸🦄😸 Now with HiStOrY!!! 😸🦄😸")
|
2021-01-19 11:50:20 +03:00
|
|
|
|
|
2021-01-17 12:50:12 +03:00
|
|
|
|
// Greetings
|
|
|
|
|
if sendHi != "" {
|
|
|
|
|
e.Connection.Privmsg(channel, `Hi!`)
|
|
|
|
|
e.Connection.Privmsg(channel, `I'm a cat facts bot!`)
|
2021-01-16 18:34:10 +03:00
|
|
|
|
e.Connection.Privmsg(channel, `Source code and issue tracker: https://gitea.difrex.ru/CTF2021/irc_bot`)
|
|
|
|
|
e.Connection.Privmsg(channel, `Now with gifs! ^_^`)
|
|
|
|
|
}
|
2021-01-16 00:20:50 +03:00
|
|
|
|
})
|
|
|
|
|
|
2021-01-19 11:50:20 +03:00
|
|
|
|
irccon.AddCallback("JOIN", s.invitionRequest)
|
|
|
|
|
|
2021-01-16 00:20:50 +03:00
|
|
|
|
irccon.AddCallback("PRIVMSG", func(e *irc.Event) {
|
|
|
|
|
log.Printf("Message from %s received: %s", e.Nick, e.Message())
|
2021-01-16 17:55:13 +03:00
|
|
|
|
s.addUser(e.Nick)
|
|
|
|
|
doCommand(e.Message(), e, s)
|
2021-01-16 00:20:50 +03:00
|
|
|
|
})
|
2021-01-17 12:50:12 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
nick := os.Getenv(nickEnv)
|
|
|
|
|
irccon := irc.IRC(nick, os.Getenv(userEnv))
|
2021-01-27 12:32:04 +03:00
|
|
|
|
if irccon == nil {
|
|
|
|
|
log.Fatal("Connection is nil")
|
|
|
|
|
}
|
2021-01-17 12:50:12 +03:00
|
|
|
|
|
|
|
|
|
s := newStats()
|
2021-01-27 12:32:04 +03:00
|
|
|
|
history, err := Open(os.Getenv(dbPathEnv))
|
|
|
|
|
if err != nil {
|
|
|
|
|
writeHistory = false
|
|
|
|
|
}
|
|
|
|
|
s.history = history
|
2021-01-17 12:50:12 +03:00
|
|
|
|
|
2021-01-27 12:50:34 +03:00
|
|
|
|
if history != nil {
|
|
|
|
|
defer history.db.Close()
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-17 12:50:12 +03:00
|
|
|
|
irccon.VerboseCallbackHandler = false
|
|
|
|
|
irccon.Debug = false
|
|
|
|
|
irccon.UseTLS = false
|
|
|
|
|
|
|
|
|
|
addCallbacks(irccon, s)
|
2021-01-16 00:20:50 +03:00
|
|
|
|
|
2021-01-27 12:32:04 +03:00
|
|
|
|
if err := irccon.Connect(os.Getenv(urlEnv)); err != nil {
|
2021-01-16 00:20:50 +03:00
|
|
|
|
fmt.Printf("Err %s", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
irccon.Loop()
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-19 11:50:20 +03:00
|
|
|
|
func isHelpCommand(command string, e *irc.Event) bool {
|
|
|
|
|
if e.Nick != channel && strings.ToLower(command) == "help" {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-27 12:32:04 +03:00
|
|
|
|
func isHistoryCommand(command string, e *irc.Event) bool {
|
|
|
|
|
if e.Nick != channel && strings.HasPrefix(e.Message(), "history") {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func parseHistoryCommandArgs(e *irc.Event) [2]string {
|
|
|
|
|
var args [2]string
|
|
|
|
|
|
|
|
|
|
message := strings.Split(e.Message(), " ")
|
|
|
|
|
if len(message) == 3 {
|
|
|
|
|
args[0] = message[1]
|
|
|
|
|
args[1] = message[2]
|
|
|
|
|
}
|
|
|
|
|
if len(message) == 2 {
|
|
|
|
|
args[0] = message[1]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return args
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func showLogs(e *irc.Event, s *stats) {
|
|
|
|
|
var logs []LogLine
|
|
|
|
|
args := parseHistoryCommandArgs(e)
|
|
|
|
|
|
|
|
|
|
if args[0] == "all" {
|
|
|
|
|
l, err := s.history.GetAllLog()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Println("Error:", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
logs = l
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if args[0] != "" && args[1] != "" {
|
|
|
|
|
l, err := s.history.GetRangedLogs(args[0], args[1])
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Println("Error:", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
logs = l
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(logs) == 0 {
|
|
|
|
|
e.Connection.Privmsg(e.Nick, ":( such empty")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.Connection.Privmsg(e.Nick, "WOW!!! HiStOrY!!!")
|
|
|
|
|
for _, l := range logs {
|
|
|
|
|
e.Connection.Privmsg(e.Nick, fmt.Sprintf("%s [%s] %s", l.Time.Format("2006-01-02T15:04:05"), l.Nick, l.Message))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-19 11:50:20 +03:00
|
|
|
|
func showHelp(e *irc.Event) {
|
|
|
|
|
e.Connection.Privmsg(e.Nick, "HELP -- show this help")
|
|
|
|
|
e.Connection.Privmsg(e.Nick, "cat|meow|etc -- show cat fact or gif")
|
|
|
|
|
e.Connection.Privmsg(e.Nick, "nyastat -- show #ctf channel stats")
|
2021-01-27 12:32:04 +03:00
|
|
|
|
e.Connection.Privmsg(e.Nick, "history -- show #ctf channel history logs")
|
2021-01-27 12:35:14 +03:00
|
|
|
|
e.Connection.Privmsg(e.Nick, " -- EXAMPLE")
|
|
|
|
|
e.Connection.Privmsg(e.Nick, "history all -- show all logs")
|
|
|
|
|
e.Connection.Privmsg(e.Nick, "history 2021-01-27T12:19:55 2021-01-27T12:20:00 -- show date ranged logs")
|
2021-01-19 11:50:20 +03:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-16 17:55:13 +03:00
|
|
|
|
func doCommand(command string, e *irc.Event, s *stats) {
|
2021-01-19 11:50:20 +03:00
|
|
|
|
if ok := statCommand(command); ok {
|
|
|
|
|
s.printStats(e)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if isHelpCommand(command, e) {
|
|
|
|
|
showHelp(e)
|
|
|
|
|
return
|
|
|
|
|
}
|
2021-01-16 17:55:13 +03:00
|
|
|
|
|
2021-01-27 12:32:04 +03:00
|
|
|
|
if isHistoryCommand(command, e) {
|
|
|
|
|
showLogs(e, s)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-16 17:55:13 +03:00
|
|
|
|
if ok := checkCommad(command, e.Connection); !ok {
|
2021-01-27 12:32:04 +03:00
|
|
|
|
err := s.history.WriteLog(&LogLine{
|
|
|
|
|
Nick: e.Nick,
|
|
|
|
|
Message: e.Message(),
|
|
|
|
|
Time: time.Now(),
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Println("Error:", err)
|
|
|
|
|
}
|
2021-01-16 13:33:30 +03:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
|
i := rand.Intn(50)
|
|
|
|
|
fmt.Println(i)
|
|
|
|
|
if i > 30 {
|
2021-01-19 11:50:20 +03:00
|
|
|
|
catGif(e)
|
2021-01-16 13:33:30 +03:00
|
|
|
|
return
|
|
|
|
|
}
|
2021-01-19 11:50:20 +03:00
|
|
|
|
|
|
|
|
|
catFacts(e)
|
2021-01-16 17:55:13 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func statCommand(command string) bool {
|
|
|
|
|
if command == nyastat {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
2021-01-16 13:33:30 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func checkCommad(command string, con *irc.Connection) bool {
|
2021-01-19 11:50:20 +03:00
|
|
|
|
return re.MatchString(command)
|
2021-01-16 13:33:30 +03:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-19 11:50:20 +03:00
|
|
|
|
func catGif(e *irc.Event) (string, error) {
|
2021-01-16 13:33:30 +03:00
|
|
|
|
res, err := http.Get("http://thecatapi.com/api/images/get?format=src&type=gif")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
2021-01-19 11:50:20 +03:00
|
|
|
|
log.Println("Send gif")
|
2021-02-12 19:22:28 +03:00
|
|
|
|
e.Connection.Privmsg(e.Nick, fmt.Sprintf(gifPrefix, res.Request.URL.String()))
|
2021-01-16 13:33:30 +03:00
|
|
|
|
return fmt.Sprintf(gifPrefix, res.Request.URL.String()), nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-16 17:55:13 +03:00
|
|
|
|
func getJson(url string, v interface{}) error {
|
|
|
|
|
res, err := http.Get(url)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
|
|
|
|
|
body, err := ioutil.ReadAll(res.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return json.Unmarshal(body, v)
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-19 11:50:20 +03:00
|
|
|
|
func catFacts(e *irc.Event) (string, error) {
|
|
|
|
|
log.Println("Send fact")
|
2021-01-16 00:20:50 +03:00
|
|
|
|
data := &catFact{}
|
2021-01-16 17:55:13 +03:00
|
|
|
|
err := getJson(catFactsURL, data)
|
2021-01-16 00:20:50 +03:00
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(data.Fact) == 0 {
|
|
|
|
|
return "", nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-12 19:22:28 +03:00
|
|
|
|
e.Connection.Privmsg(e.Nick, fmt.Sprintf(msgPrefix, data.Fact))
|
2021-01-16 00:20:50 +03:00
|
|
|
|
return fmt.Sprintf(msgPrefix, data.Fact), nil
|
|
|
|
|
}
|
2021-01-16 18:23:56 +03:00
|
|
|
|
|
2021-01-19 11:50:20 +03:00
|
|
|
|
func getChanName(e *irc.Event) string {
|
|
|
|
|
c := channel
|
|
|
|
|
if _, err := strconv.Atoi(e.Nick); err != nil {
|
|
|
|
|
c = e.Nick
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-27 12:32:04 +03:00
|
|
|
|
func isFetureEnabled(feature string) bool {
|
|
|
|
|
if feature != "" && strings.ToLower(feature) == "yes" {
|
2021-01-19 11:50:20 +03:00
|
|
|
|
return true
|
|
|
|
|
}
|
2021-01-27 12:32:04 +03:00
|
|
|
|
|
2021-01-19 11:50:20 +03:00
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-27 12:32:04 +03:00
|
|
|
|
func isInvitionsEnabled() bool {
|
|
|
|
|
return isFetureEnabled(os.Getenv(invitionsEnv))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func isHistoryEnabled() bool {
|
|
|
|
|
return isFetureEnabled(os.Getenv(chanHistoryEnv))
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-19 11:50:20 +03:00
|
|
|
|
func getNick() string {
|
|
|
|
|
return os.Getenv(nickEnv)
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-16 18:23:56 +03:00
|
|
|
|
func init() {
|
2021-01-17 12:50:12 +03:00
|
|
|
|
ch := os.Getenv(chanEnv)
|
2021-01-16 18:23:56 +03:00
|
|
|
|
if ch != "" {
|
|
|
|
|
channel = ch
|
|
|
|
|
}
|
2021-01-16 18:34:10 +03:00
|
|
|
|
|
2021-01-17 12:50:12 +03:00
|
|
|
|
sendHi = os.Getenv(himsgEnv)
|
2021-01-27 12:32:04 +03:00
|
|
|
|
|
|
|
|
|
writeHistory = isFetureEnabled(os.Getenv(chanHistoryEnv))
|
2021-01-16 18:23:56 +03:00
|
|
|
|
}
|