Merged branch master into master

This commit is contained in:
toksikk
2016-11-17 19:41:29 +01:00
16 changed files with 597 additions and 52 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2015,2016 Andreas Neue
Copyright (c) 2015,2016 Andreas Neue, Daniel Aberger
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
+17 -2
View File
@@ -29,7 +29,9 @@ var (
channels = flag.String("chan", "#test", "Channels to join")
nsname = flag.String("nsname", "NickServ", "NickServ name")
nspass = flag.String("nspass", "", "NickServ password")
mods = flag.String("mods", "", "Modules to load")
params = flag.String("params", "", "Module params")
autocmd = flag.String("autocmd", "", "Autosend IRC command")
)
func init() {
@@ -43,7 +45,7 @@ var (
func main() {
sayCh = make(chan string, 1024)
xlog.Init(xlog.INFO)
xlog.Init(xlog.DEBUG)
//bot := ircx.Classic(*server, *name)
cfg := ircx.Config{User: *name, MaxRetries: 1000}
@@ -57,7 +59,8 @@ func main() {
//mods := strings.Split(*modules, ",")
//TODO: implement more robust list parsing
modules.Init(sayCh, *params)
modules.Init(sayCh, *mods, *params)
modules.ModParams["_nick"] = *name
go func() {
for {
@@ -82,6 +85,11 @@ func main() {
}
}()
go Ping(bot)
if *autocmd != "" {
println(*autocmd)
bot.Sender.Send(&irc.Message{Command: *autocmd})
}
RegisterHandlers(bot)
bot.HandleLoop()
xlog.Info("Exiting")
@@ -94,6 +102,13 @@ func RegisterHandlers(bot *ircx.Bot) {
bot.HandleFunc(irc.PRIVMSG, PrivmsgHandler)
}
func Ping(bot *ircx.Bot) {
for {
time.Sleep(1 * time.Minute)
bot.Sender.Send(&irc.Message{Command: irc.PING})
}
}
func ConnectHandler(s ircx.Sender, m *irc.Message) {
if *nspass != "" {
xlog.Info("Authenticating with NickServ: %v, %v", *name, *nspass)
+1 -4
View File
@@ -5,16 +5,13 @@ package modules
import (
"strings"
"code.dnix.de/an/xlog"
"github.com/sorcix/irc"
)
var ()
func init() {
MsgHandlers["announcements"] = anncouncementsHandleMessage
xlog.Info("Announcements module initialized")
MsgFuncs["announcements"] = anncouncementsHandleMessage
}
func anncouncementsHandleMessage(m *irc.Message) {
+1 -5
View File
@@ -12,8 +12,6 @@ import (
"sync"
"time"
"code.dnix.de/an/xlog"
"github.com/sorcix/irc"
"github.com/sorcix/irc/ctcp"
)
@@ -29,9 +27,8 @@ var (
)
func init() {
MsgHandlers["coffee"] = coffeeHandleMessage
MsgFuncs["coffee"] = coffeeHandleMessage
r.names = make(map[string]bool)
xlog.Info("Coffee module initialized")
}
func coffeeHandleMessage(m *irc.Message) {
@@ -108,4 +105,3 @@ func printAction(s string) {
msg := ctcp.Encode(ctcp.ACTION, fmt.Sprintf(s))
SayCh <- fmt.Sprintf("%s\n%s", "*", msg)
}
+1 -4
View File
@@ -8,14 +8,11 @@ import (
"os/exec"
"strings"
"code.dnix.de/an/xlog"
"github.com/sorcix/irc"
)
func init() {
MsgHandlers["fortune"] = fortuneHandleMessage
xlog.Info("Fortune module initialized")
MsgFuncs["fortune"] = fortuneHandleMessage
}
func fortuneHandleMessage(m *irc.Message) {
+1 -4
View File
@@ -7,14 +7,11 @@ import (
"strings"
"time"
"code.dnix.de/an/xlog"
"github.com/sorcix/irc"
)
func init() {
MsgHandlers["fuzzytime"] = fuzzytimeHandleMessage
xlog.Info("Fuzzytime module initialized")
MsgFuncs["fuzzytime"] = fuzzytimeHandleMessage
}
func fuzzytimeHandleMessage(m *irc.Message) {
+2 -4
View File
@@ -8,8 +8,6 @@ import (
"strings"
"time"
"code.dnix.de/an/xlog"
"github.com/sorcix/irc"
)
@@ -18,8 +16,8 @@ var (
)
func init() {
MsgHandlers["gogs"] = gogsHandleMessage
xlog.Info("Gogs module initialized")
MsgFuncs["gogs"] = gogsHandleMessage
RunFuncs["gogs"] = gogsConfig
}
func gogsConfig() {
+273
View File
@@ -0,0 +1,273 @@
// vi:ts=4:sts=4:sw=4:noet:tw=72
package modules
// This Markov chain code is taken from the "Generating arbitrary text"
// codewalk: http://golang.org/doc/codewalk/markov/
//
// Minor modifications have been made to make it easier to integrate
// with a webserver and to save/load state
import (
"bufio"
"encoding/gob"
"fmt"
"math/rand"
"os"
"regexp"
"strconv"
"strings"
"sync"
"time"
"code.dnix.de/an/xlog"
"github.com/sorcix/irc"
)
var markovChain *MarkovChain
func init() {
MsgFuncs["markov"] = markovHandleMessage
RunFuncs["markov"] = markovRun
}
func markovHandleMessage(m *irc.Message) {
text := m.Trailing
if text == "" {
return
}
text = markovParseText(text)
answerLen, _ := strconv.Atoi(ModParams["markov-answer-len"])
respChance, _ := strconv.Atoi(ModParams["markov-response-chance"])
if rand.Intn(100) <= respChance || strings.Index(text, strings.ToLower(ModParams["_nick"])) != -1 {
responseText := markovChain.Generate(answerLen, text)
if responseText != "" {
go func() {
time.Sleep(time.Duration(rand.Intn(8)+2) * time.Second)
SayCh <- "*\n" + responseText
}()
}
}
markovChain.Write(text)
}
func markovRun() {
prefixLen, _ := strconv.Atoi(ModParams["markov-prefix-len"])
markovChain = markovNewChain(prefixLen)
err := markovChain.Load(ModParams["markov-state-file"])
if err != nil {
xlog.Error(err.Error())
}
filepath := ModParams["markov-import-file"]
if filepath != "-" {
file, _ := os.Open(filepath)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
text := scanner.Text()
text = markovParseText(text)
if text != "" {
markovChain.Write(text)
}
}
}
go func() {
for {
time.Sleep(60 * time.Second)
markovChain.Save(ModParams["markov-state-file"])
}
}()
}
func markovParseText(text string) string {
messageRegex := regexp.MustCompile(`<([^>]+)>`)
matches := messageRegex.FindAllStringSubmatch(text, -1)
for _, matches2 := range matches {
if strings.HasPrefix(matches2[1], "http") || strings.HasPrefix(matches2[1], "mailto") {
text = strings.Replace(text, matches2[0], "", -1)
} else if strings.HasPrefix(matches2[1], "@U") {
parts := strings.SplitN(matches2[1], "|", 2)
if len(parts) == 2 {
text = strings.Replace(text, matches2[0], "@"+parts[1], -1)
} else {
text = strings.Replace(text, matches2[0], "", -1)
}
} else if strings.HasPrefix(matches2[1], "@") {
text = strings.Replace(text, matches2[0], matches2[1], -1)
} else if strings.HasPrefix(matches2[1], "#") {
parts := strings.SplitN(matches2[1], "|", 2)
if len(parts) == 2 {
text = strings.Replace(text, matches2[0], "#"+parts[1], -1)
} else {
text = strings.Replace(text, matches2[0], "", -1)
}
}
}
text = strings.TrimSpace(text)
text = strings.Replace(text, "&lt;", "<", -1)
text = strings.Replace(text, "&gt;", ">", -1)
text = strings.Replace(text, "&amp;", "&", -1)
return strings.ToLower(text)
}
// Prefix is a Markov chain prefix of one or more words.
type MarkovPrefix []string
// String returns the Prefix as a string (for use as a map key).
func (p MarkovPrefix) String() string {
return strings.Trim(strings.Join(p, " "), " ")
}
// Shift removes the first word from the Prefix and appends the given word.
func (p MarkovPrefix) Shift(word string) {
copy(p, p[1:])
p[len(p)-1] = word
}
// MarkovChain contains a map ("chain") of prefixes to a list of suffixes.
// A prefix is a string of prefixLen words joined with spaces.
// A suffix is a single word. A prefix can have multiple suffixes.
type MarkovChain struct {
MarkovChain map[string][]string
prefixLen int
mu sync.Mutex
}
// NewMarkovChain returns a new MarkovChain with prefixes of prefixLen words.
func markovNewChain(prefixLen int) *MarkovChain {
return &MarkovChain{
MarkovChain: make(map[string][]string),
prefixLen: prefixLen,
}
}
// Write parses the bytes into prefixes and suffixes that are stored in MarkovChain.
func (c *MarkovChain) Write(in string) (int, error) {
in = strings.ToLower(in)
if strings.HasPrefix(in, strings.ToLower(ModParams["_nick"])) {
tok := strings.Split(in, " ")
in = strings.Replace(in, tok[0]+" ", "", 1)
}
sr := strings.NewReader(in)
p := make(MarkovPrefix, c.prefixLen)
for {
var s string
if _, err := fmt.Fscan(sr, &s); err != nil {
break
}
key := p.String()
c.mu.Lock()
c.MarkovChain[key] = append(c.MarkovChain[key], s)
c.mu.Unlock()
xlog.Debug("Chain len: %d, learned [%s] [%s]", len(c.MarkovChain), key, s)
p.Shift(s)
}
return len(in), nil
}
// Generate returns a string of at most n words generated from MarkovChain.
func (c *MarkovChain) Generate(n int, in string) string {
in = strings.ToLower(in)
if strings.HasPrefix(in, strings.ToLower(ModParams["_nick"])) {
tok := strings.Split(in, " ")
in = strings.Replace(in, tok[0]+" ", "", 1)
}
c.mu.Lock()
defer c.mu.Unlock()
p := make(MarkovPrefix, c.prefixLen)
p = strings.Split(in, " ")
if len(p) > c.prefixLen {
i := rand.Intn(len(p) - 1)
p = p[i : i+c.prefixLen]
}
prefix := p.String()
xlog.Debug("Looking for answer on [%s]", prefix)
var words []string
for i := 0; i < n; i++ {
choices := c.MarkovChain[p.String()]
if len(choices) == 0 {
break
}
next := choices[rand.Intn(len(choices))]
words = append(words, next)
if strings.HasSuffix(next, ".") || strings.HasSuffix(next, "!") || strings.HasSuffix(next, "?") {
break
}
p.Shift(next)
}
prefix = strings.Trim(prefix, " ")
if len(words) == 0 {
xlog.Debug("No answer found")
return prefix + " ... pfrrrz"
} else {
xlog.Debug("Found words: [%s]", strings.Join(words, " "))
return prefix + " " + strings.Join(words, " ")
}
}
// Save the chain to a file
func (c *MarkovChain) Save(fileName string) error {
// Open the file for writing
fo, err := os.Create(fileName)
if err != nil {
return err
}
// close fo on exit and check for its returned error
defer func() {
if err := fo.Close(); err != nil {
panic(err)
}
}()
// Create an encoder and dump to it
c.mu.Lock()
defer c.mu.Unlock()
enc := gob.NewEncoder(fo)
err = enc.Encode(c)
if err != nil {
return err
}
return nil
}
// Load the chain from a file
func (c *MarkovChain) Load(fileName string) error {
// Open the file for reading
fi, err := os.Open(fileName)
if err != nil {
return err
}
// close fi on exit and check for its returned error
defer func() {
if err := fi.Close(); err != nil {
panic(err)
}
}()
// Create a decoder and read from it
c.mu.Lock()
defer c.mu.Unlock()
dec := gob.NewDecoder(fi)
err = dec.Decode(c)
if err != nil {
return err
}
return nil
}
+28 -3
View File
@@ -8,26 +8,51 @@ package modules
import (
"strings"
"time"
"github.com/sorcix/irc"
)
var (
SayCh chan string
MsgHandlers = make(map[string]func(*irc.Message))
MsgFuncs = make(map[string]func(*irc.Message))
RunFuncs = make(map[string]func())
ModParams = make(map[string]string)
)
func Init(ch chan string, params string) {
func Init(ch chan string, mods, params string) {
time.Sleep(5 * time.Second)
SayCh = ch
for mod, _ := range MsgFuncs {
if !contains(strings.Split(mods, ","), mod) {
delete(MsgFuncs, mod)
}
}
for mod, _ := range RunFuncs {
if !contains(strings.Split(mods, ","), mod) {
delete(RunFuncs, mod)
}
}
for _, param := range strings.Split(params, "!") {
kv := strings.Split(param, ":")
ModParams[kv[0]] = kv[1]
}
for _, fn := range RunFuncs {
go fn()
}
}
func HandleMessage(m *irc.Message) {
for _, fn := range MsgHandlers {
for _, fn := range MsgFuncs {
fn(m)
}
}
func contains(sa []string, s string) bool {
for _, a := range sa {
if a == s {
return true
}
}
return false
}
+247
View File
@@ -0,0 +1,247 @@
// vi:ts=4:sts=4:sw=4:noet:tw=72
package modules
import (
"bufio"
"flokatirc/util"
"fmt"
"math/rand"
"os"
"regexp"
"strings"
"time"
"github.com/sorcix/irc"
"code.dnix.de/an/xlog"
)
type quizQuestion struct {
category, question, answer, regexp, level string
}
var (
quizCtrlCh = make(chan string, 1024)
quizAnswerCh = make(chan *irc.Message, 1024)
quizQuestions []quizQuestion
quizQuestionValues = map[string]int{"extreme": 5, "hard": 4, "normal": 3, "easy": 2, "baby": 1}
)
func init() {
MsgFuncs["quiz"] = quizHandleMessage
RunFuncs["quiz"] = quiz
rand.Seed(time.Now().Unix())
}
func quizHandleMessage(m *irc.Message) {
tok := strings.Split(m.Trailing, " ")
if len(tok) < 1 {
return
}
switch tok[0] {
case "!quiz", "!quizstart":
quizCtrlCh <- "start"
break
case "!quizstop":
quizCtrlCh <- "stop"
break
default:
quizAnswerCh <- m
break
}
}
func quiz() {
time.Sleep(5 * time.Second)
SayCh <- fmt.Sprintf("%s\nquiz mod test. !quizstart to start, !quizstop to end.", "*")
for {
time.Sleep(1 * time.Millisecond)
select {
case s := <-quizCtrlCh:
if s == "start" {
quizRun()
}
default:
break
}
}
}
func quizRun() {
quizQuestions = quizLoadQuestions("questions.txt")
ranklist := make(map[string]int)
SayCh <- fmt.Sprintf("%s\nQuiz gestartet.", "*")
for {
solved, cont, solver, gain := quizAskQuestion()
if !cont {
SayCh <- fmt.Sprintf("%s\nQuiz beendet.", "*")
return
} else {
if solved {
if _, exists := ranklist[solver]; exists {
ranklist[solver] += gain
} else {
ranklist[solver] = gain
}
if ranklist[solver] > 1000 {
bold := byte(0x02)
solver = string(bold) + solver + string(bold)
SayCh <- fmt.Sprintf("%s\n%s gewinnt!", "*", solver)
quizPrintRanklist(ranklist)
SayCh <- fmt.Sprintf("%s\nQuiz beendet.", "*")
return
}
}
}
quizPrintRanklist(ranklist)
}
}
func quizPrintRanklist(ranklist map[string]int) {
SayCh <- fmt.Sprintf("%s\nAktueller Punktestand:", "*")
for k, v := range ranklist {
SayCh <- fmt.Sprintf("%s\n%s: %d", "*", k, v)
}
}
func quizAskQuestion() (bool, bool, string, int) {
q := quizQuestions[rand.Intn(len(quizQuestions))]
SayCh <- fmt.Sprintf("%s\nEine Frage aus der Kategorie \"%s\" (%s):", "*", q.category, q.level)
SayCh <- fmt.Sprintf("%s\n>>> %s <<<", "*", q.question)
solved, cont, solver, gain := quizWaitForAnswer(q)
if !solved {
SayCh <- fmt.Sprintf("%s\nDie richtige Antwort wäre gewesen:", "*")
SayCh <- fmt.Sprintf("%s\n%s [%s]", "*", q.answer, q.regexp)
}
time.Sleep(5 * time.Second)
return solved, cont, solver, gain
}
func quizWaitForAnswer(q quizQuestion) (bool, bool, string, int) {
i := 0
haveAnswer := false
timer := time.Now().Unix()
for {
select {
case m := <-quizAnswerCh:
haveAnswer = true
points := 10 - ((time.Now().Unix() - timer) / 10)
if points < 1 {
points = 1
}
re, err := regexp.Compile(q.regexp)
if err != nil {
xlog.Error(err.Error())
return false, false, "", 0
}
if re.MatchString(strings.ToLower(util.ReplaceUmlauts(m.Trailing))) {
bold := byte(0x02)
solver := strings.Split(m.Prefix.String(), "!")[0]
solver = string(bold) + solver + string(bold)
value := quizQuestionValues[q.level]
gain := int(points) * value
SayCh <- fmt.Sprintf("%s\n%s hat die Frage korrekt beantwortet und erhält dafür %d (%d * %d) Punkte.", "*", solver, gain, points, value)
return true, true, solver, gain
}
break
case s := <-quizCtrlCh:
if s == "stop" {
return false, false, "", 0
}
break
case <-time.After(15 * time.Second):
i++
if i > 3 {
if haveAnswer {
SayCh <- fmt.Sprintf("%s\nNiemand konnte die Frage korrekt beantworten.", "*")
return false, true, "", 0
} else {
SayCh <- fmt.Sprintf("%s\nNiemand hat auf die Frage geantwortet.", "*")
return false, false, "", 0
}
} else {
quizGiveHint(q, i)
}
}
}
}
func quizGiveHint(q quizQuestion, n int) {
var hint string
haveHint := false
for {
hint = ""
for i := 0; i < len(q.answer); i++ {
if string(q.answer[i]) == " " {
hint += " "
} else {
if rand.Intn(10) < 2 {
haveHint = true
hint += string(q.answer[i])
} else {
hint += "-"
}
}
}
if haveHint {
break
}
}
SayCh <- fmt.Sprintf("%s\nTipp: %s", "*", hint)
}
func quizLoadQuestions(path string) []quizQuestion {
file, err := os.Open(path)
if err != nil {
xlog.Fatal(err.Error())
}
defer file.Close()
questions := make([]quizQuestion, 0)
q := quizQuestion{"", "", "", "", ""}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if len(line) == 0 || line[0] == '#' || line[0] == '\n' {
if q.category != "" {
questions = append(questions, q)
q = quizQuestion{"", "", "", "", "normal"}
}
} else {
tok := strings.Split(line, ":: ")
switch tok[0] {
case "Category":
q.category = tok[1]
break
case "Question":
q.question = tok[1]
break
case "Answer":
q.answer = strings.Replace(util.ReplaceUmlauts(tok[1]), "#", "", -1)
if q.regexp == "" {
regexp := strings.Split(strings.ToLower(util.ReplaceUmlauts(tok[1])), "#")
if len(regexp) > 1 {
q.regexp = regexp[1]
} else {
q.regexp = regexp[0]
}
}
break
case "Regexp":
q.regexp = util.ReplaceUmlauts(strings.ToLower(tok[1]))
break
case "Level":
q.level = tok[1]
break
}
}
}
if err := scanner.Err(); err != nil {
xlog.Fatal(err.Error())
}
return questions
}
+2 -3
View File
@@ -24,9 +24,8 @@ import (
var hideOutput = true
func init() {
MsgHandlers["rss"] = rssHandleMessage
go rssRun()
xlog.Info("RSS module initialized")
MsgFuncs["rss"] = rssHandleMessage
RunFuncs["rss"] = rssRun
}
func rssRun() {
+2 -3
View File
@@ -48,9 +48,8 @@ var (
)
func init() {
MsgHandlers["sc"] = scHandleMessage
go scScrapeLoop()
xlog.Info("SC module initialized")
MsgFuncs["sc"] = scHandleMessage
RunFuncs["sc"] = scScrapeLoop
}
func scHandleMessage(m *irc.Message) {
+5 -6
View File
@@ -3,11 +3,10 @@
package modules
import (
"flokatirc/util"
"fmt"
"math/rand"
"strings"
"code.dnix.de/an/xlog"
"time"
"github.com/sorcix/irc"
)
@@ -522,8 +521,8 @@ var quotes = [][]string{
}
func init() {
MsgHandlers["stoll"] = stollHandleMessage
xlog.Info("Stoll module initialized")
MsgFuncs["stoll"] = stollHandleMessage
rand.Seed(time.Now().Unix())
}
func stollHandleMessage(m *irc.Message) {
@@ -534,7 +533,7 @@ func stollHandleMessage(m *irc.Message) {
if tok[0] == "!stoll" {
line := ""
for i := 0; i < 3; i++ {
line += quotes[i][util.Random(0, len(quotes[i]))]
line += quotes[i][rand.Intn(len(quotes[i]))]
line += " "
}
line += "[Dr. Axel Stoll, promovierter Naturwissenschaftler]"
+2 -3
View File
@@ -137,9 +137,8 @@ var (
)
func init() {
MsgHandlers["twitch"] = twitchHandleMessage
go pollStreamData()
xlog.Info("Twitch module initialized")
MsgFuncs["twitch"] = twitchHandleMessage
RunFuncs["twitch"] = pollStreamData
}
func twitchHandleMessage(m *irc.Message) {
+2 -3
View File
@@ -71,9 +71,8 @@ type WeatherObject struct {
}
func init() {
MsgHandlers["weather"] = weatherHandleMessage
go weatherConfig()
xlog.Info("Weather module initialized")
MsgFuncs["weather"] = weatherHandleMessage
RunFuncs["weather"] = weatherConfig
}
func weatherConfig() {
+10 -5
View File
@@ -4,9 +4,8 @@ package util
import (
"bytes"
"math/rand"
"strconv"
"time"
"strings"
)
func ToInt(v interface{}) int {
@@ -48,7 +47,13 @@ func NumberToString(n int, sep rune) string {
return buf.String()
}
func Random(min, max int) int {
rand.Seed(time.Now().Unix())
return rand.Intn(max-min) + min
func ReplaceUmlauts(s string) string {
ret := strings.Replace(s, "Ä", "Ae", -1)
ret = strings.Replace(ret, "Ö", "Oe", -1)
ret = strings.Replace(ret, "Ü", "Ue", -1)
ret = strings.Replace(ret, "ä", "ae", -1)
ret = strings.Replace(ret, "ö", "oe", -1)
ret = strings.Replace(ret, "ü", "ue", -1)
ret = strings.Replace(ret, "ß", "ss", -1)
return ret
}