2016-01-17 18:00:44 +00:00
|
|
|
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
|
2013-12-10 23:37:07 +00:00
|
|
|
|
2016-03-24 17:32:40 +00:00
|
|
|
package downtime
|
2012-08-31 22:55:05 +00:00
|
|
|
|
2016-01-15 18:37:54 +00:00
|
|
|
import (
|
|
|
|
"database/sql"
|
|
|
|
|
2016-03-19 18:02:46 +00:00
|
|
|
"github.com/jmoiron/sqlx"
|
2016-01-17 18:00:44 +00:00
|
|
|
"github.com/velour/catbase/bot"
|
2016-01-15 18:37:54 +00:00
|
|
|
)
|
2013-01-22 19:46:51 +00:00
|
|
|
|
|
|
|
import (
|
2013-01-22 21:14:04 +00:00
|
|
|
"fmt"
|
2013-01-23 00:22:32 +00:00
|
|
|
"log"
|
2013-01-28 18:35:41 +00:00
|
|
|
"sort"
|
2013-01-22 21:14:04 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
2013-01-22 19:46:51 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// This is a downtime plugin to monitor how much our users suck
|
|
|
|
|
|
|
|
type DowntimePlugin struct {
|
2016-01-15 18:37:54 +00:00
|
|
|
Bot *bot.Bot
|
2016-03-19 18:02:46 +00:00
|
|
|
db *sqlx.DB
|
2013-01-22 19:46:51 +00:00
|
|
|
}
|
|
|
|
|
2013-01-22 21:14:04 +00:00
|
|
|
type idleEntry struct {
|
2016-01-15 18:37:54 +00:00
|
|
|
id sql.NullInt64
|
|
|
|
nick string
|
|
|
|
lastSeen time.Time
|
|
|
|
}
|
|
|
|
|
2016-03-19 18:02:46 +00:00
|
|
|
func (entry idleEntry) saveIdleEntry(db *sqlx.DB) error {
|
2016-01-15 18:37:54 +00:00
|
|
|
var err error
|
|
|
|
if entry.id.Valid {
|
|
|
|
log.Println("Updating downtime for: ", entry)
|
|
|
|
_, err = db.Exec(`update downtime set
|
|
|
|
nick=?, lastSeen=?
|
|
|
|
where id=?;`, entry.nick, entry.lastSeen.Unix(), entry.id.Int64)
|
|
|
|
} else {
|
|
|
|
log.Println("Inserting downtime for: ", entry)
|
|
|
|
_, err = db.Exec(`insert into downtime (nick, lastSeen)
|
|
|
|
values (?, ?)`, entry.nick, entry.lastSeen.Unix())
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-03-19 18:02:46 +00:00
|
|
|
func getIdleEntryByNick(db *sqlx.DB, nick string) (idleEntry, error) {
|
2016-01-15 18:37:54 +00:00
|
|
|
var id sql.NullInt64
|
|
|
|
var lastSeen sql.NullInt64
|
|
|
|
err := db.QueryRow(`select id, max(lastSeen) from downtime
|
|
|
|
where nick = ?`, nick).Scan(&id, &lastSeen)
|
|
|
|
if err != nil {
|
|
|
|
log.Println("Error selecting downtime: ", err)
|
|
|
|
return idleEntry{}, err
|
|
|
|
}
|
|
|
|
if !id.Valid {
|
|
|
|
return idleEntry{
|
|
|
|
nick: nick,
|
|
|
|
lastSeen: time.Now(),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
return idleEntry{
|
|
|
|
id: id,
|
|
|
|
nick: nick,
|
|
|
|
lastSeen: time.Unix(lastSeen.Int64, 0),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2016-03-19 18:02:46 +00:00
|
|
|
func getAllIdleEntries(db *sqlx.DB) (idleEntries, error) {
|
2016-01-15 18:37:54 +00:00
|
|
|
rows, err := db.Query(`select id, nick, max(lastSeen) from downtime
|
|
|
|
group by nick`)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
entries := idleEntries{}
|
|
|
|
for rows.Next() {
|
|
|
|
var e idleEntry
|
|
|
|
err := rows.Scan(&e.id, &e.nick, &e.lastSeen)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
entries = append(entries, &e)
|
|
|
|
}
|
|
|
|
return entries, nil
|
2013-01-22 21:14:04 +00:00
|
|
|
}
|
|
|
|
|
2013-01-23 15:53:31 +00:00
|
|
|
type idleEntries []*idleEntry
|
|
|
|
|
|
|
|
func (ie idleEntries) Len() int {
|
|
|
|
return len(ie)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ie idleEntries) Less(i, j int) bool {
|
2016-01-15 18:37:54 +00:00
|
|
|
return ie[i].lastSeen.Before(ie[j].lastSeen)
|
2013-01-23 15:53:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ie idleEntries) Swap(i, j int) {
|
|
|
|
ie[i], ie[j] = ie[j], ie[i]
|
|
|
|
}
|
|
|
|
|
2013-01-22 19:46:51 +00:00
|
|
|
// NewDowntimePlugin creates a new DowntimePlugin with the Plugin interface
|
|
|
|
func NewDowntimePlugin(bot *bot.Bot) *DowntimePlugin {
|
2013-01-22 21:14:04 +00:00
|
|
|
p := DowntimePlugin{
|
2013-01-22 19:46:51 +00:00
|
|
|
Bot: bot,
|
2016-01-15 18:37:54 +00:00
|
|
|
db: bot.DB,
|
2013-01-22 19:46:51 +00:00
|
|
|
}
|
2016-01-15 18:37:54 +00:00
|
|
|
|
|
|
|
if bot.DBVersion == 1 {
|
|
|
|
_, err := p.db.Exec(`create table if not exists downtime (
|
|
|
|
id integer primary key,
|
|
|
|
nick string,
|
|
|
|
lastSeen integer
|
|
|
|
);`)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("Error creating downtime table: ", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-22 21:14:04 +00:00
|
|
|
return &p
|
2013-01-22 19:46:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Message responds to the bot hook on recieving messages.
|
|
|
|
// This function returns true if the plugin responds in a meaningful way to the users message.
|
|
|
|
// Otherwise, the function returns false and the bot continues execution of other plugins.
|
|
|
|
func (p *DowntimePlugin) Message(message bot.Message) bool {
|
2013-01-22 21:14:04 +00:00
|
|
|
// If it's a command and the payload is idle <nick>, give it. Log everything.
|
|
|
|
|
|
|
|
parts := strings.Fields(strings.ToLower(message.Body))
|
|
|
|
channel := message.Channel
|
|
|
|
ret := false
|
|
|
|
|
2016-01-17 15:29:14 +00:00
|
|
|
if len(parts) == 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2013-01-22 21:14:04 +00:00
|
|
|
if parts[0] == "idle" && len(parts) == 2 {
|
|
|
|
nick := parts[1]
|
|
|
|
// parts[1] must be the userid, or we don't know them
|
2016-01-15 18:37:54 +00:00
|
|
|
entry, err := getIdleEntryByNick(p.db, nick)
|
|
|
|
if err != nil {
|
|
|
|
log.Println("Error getting idle entry: ", err)
|
|
|
|
}
|
|
|
|
if !entry.id.Valid {
|
2013-01-22 21:14:04 +00:00
|
|
|
// couldn't find em
|
|
|
|
p.Bot.SendMessage(channel, fmt.Sprintf("Sorry, I don't know %s.", nick))
|
|
|
|
} else {
|
|
|
|
p.Bot.SendMessage(channel, fmt.Sprintf("%s has been idle for: %s",
|
2016-01-15 18:37:54 +00:00
|
|
|
nick, time.Now().Sub(entry.lastSeen)))
|
2013-01-22 21:14:04 +00:00
|
|
|
}
|
|
|
|
ret = true
|
2013-01-23 15:53:31 +00:00
|
|
|
} else if parts[0] == "idle" && len(parts) == 1 {
|
|
|
|
// Find all idle times, report them.
|
2016-01-15 18:37:54 +00:00
|
|
|
entries, err := getAllIdleEntries(p.db)
|
|
|
|
if err != nil {
|
|
|
|
log.Println("Error retrieving idle entries: ", err)
|
|
|
|
}
|
2013-01-28 18:35:41 +00:00
|
|
|
sort.Sort(entries)
|
|
|
|
tops := "The top entries are: "
|
|
|
|
for _, e := range entries {
|
2013-01-29 21:27:38 +00:00
|
|
|
|
2013-01-29 21:29:07 +00:00
|
|
|
// filter out ZNC entries and ourself
|
2016-01-15 18:37:54 +00:00
|
|
|
if strings.HasPrefix(e.nick, "*") || strings.ToLower(p.Bot.Config.Nick) == e.nick {
|
|
|
|
p.remove(e.nick)
|
2013-01-29 21:27:38 +00:00
|
|
|
} else {
|
2016-01-15 18:37:54 +00:00
|
|
|
tops = fmt.Sprintf("%s%s: %s ", tops, e.nick, time.Now().Sub(e.lastSeen))
|
2013-01-28 18:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
p.Bot.SendMessage(channel, tops)
|
2013-01-28 18:48:13 +00:00
|
|
|
ret = true
|
2013-01-28 18:35:41 +00:00
|
|
|
|
2013-01-22 21:14:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
p.record(strings.ToLower(message.User.Name))
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *DowntimePlugin) record(user string) {
|
2016-01-15 18:37:54 +00:00
|
|
|
entry, err := getIdleEntryByNick(p.db, user)
|
|
|
|
if err != nil {
|
|
|
|
log.Println("Error recording downtime: ", err)
|
2013-01-22 21:14:04 +00:00
|
|
|
}
|
2016-01-15 18:37:54 +00:00
|
|
|
entry.lastSeen = time.Now()
|
|
|
|
entry.saveIdleEntry(p.db)
|
|
|
|
log.Println("Inserted downtime for:", user)
|
2013-01-22 19:46:51 +00:00
|
|
|
}
|
|
|
|
|
2016-01-15 18:37:54 +00:00
|
|
|
func (p *DowntimePlugin) remove(user string) error {
|
|
|
|
_, err := p.db.Exec(`delete from downtime where nick = ?`, user)
|
|
|
|
if err != nil {
|
|
|
|
log.Println("Error removing downtime for user: ", user, err)
|
|
|
|
return err
|
|
|
|
}
|
2013-01-23 00:22:32 +00:00
|
|
|
log.Println("Removed downtime for:", user)
|
2016-01-15 18:37:54 +00:00
|
|
|
return nil
|
2013-01-22 19:46:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Help responds to help requests. Every plugin must implement a help function.
|
|
|
|
func (p *DowntimePlugin) Help(channel string, parts []string) {
|
2013-01-22 21:14:04 +00:00
|
|
|
p.Bot.SendMessage(channel, "Ask me how long one of your friends has been idele with, \"idle <nick>\"")
|
2013-01-22 19:46:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Empty event handler because this plugin does not do anything on event recv
|
|
|
|
func (p *DowntimePlugin) Event(kind string, message bot.Message) bool {
|
2013-01-23 00:22:32 +00:00
|
|
|
log.Println(kind, "\t", message)
|
|
|
|
if kind != "PART" && message.User.Name != p.Bot.Config.Nick {
|
2013-01-22 21:14:04 +00:00
|
|
|
// user joined, let's nail them for it
|
2013-01-23 00:22:32 +00:00
|
|
|
if kind == "NICK" {
|
|
|
|
p.record(strings.ToLower(message.Channel))
|
|
|
|
p.remove(strings.ToLower(message.User.Name))
|
|
|
|
} else {
|
|
|
|
p.record(strings.ToLower(message.User.Name))
|
|
|
|
}
|
2013-02-18 19:08:08 +00:00
|
|
|
} else if kind == "PART" || kind == "QUIT" {
|
2013-01-23 00:22:32 +00:00
|
|
|
p.remove(strings.ToLower(message.User.Name))
|
2013-01-23 15:53:31 +00:00
|
|
|
} else {
|
2013-01-29 21:27:38 +00:00
|
|
|
log.Println("Unknown event: ", kind, message.User, message)
|
2013-01-23 15:53:31 +00:00
|
|
|
p.record(strings.ToLower(message.User.Name))
|
2013-01-22 21:14:04 +00:00
|
|
|
}
|
2013-01-22 19:46:51 +00:00
|
|
|
return false
|
|
|
|
}
|
2013-05-08 00:08:18 +00:00
|
|
|
|
|
|
|
// Handler for bot's own messages
|
|
|
|
func (p *DowntimePlugin) BotMessage(message bot.Message) bool {
|
|
|
|
return false
|
|
|
|
}
|
2013-06-01 17:10:15 +00:00
|
|
|
|
|
|
|
// Register any web URLs desired
|
|
|
|
func (p *DowntimePlugin) RegisterWeb() *string {
|
|
|
|
return nil
|
|
|
|
}
|