commit ffeb3cf38c94e2f3c362b3b2343ac60cfede89fa
Author: sin <sin@2f30.org>
Date: Sat, 13 Apr 2013 20:41:57 +0100
Initial commit
Diffstat:
10 files changed, 650 insertions(+), 0 deletions(-)
diff --git a/build b/build
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+export GOPATH=$PWD
+go install fsdb
+go build -o src/kunt/kunt -v kunt
diff --git a/package b/package
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+mkdir -p kunt-latest
+cp build package kunt-latest
+cp -r src kunt-latest
+rm -rf kunt-latest/pkg
+rm -f kunt-latest/src/kunt/kunt
+tar cfz kunt-latest.tgz kunt-latest
+rm -rf kunt-latest
diff --git a/src/fsdb/fsdb.go b/src/fsdb/fsdb.go
@@ -0,0 +1,154 @@
+package fsdb
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "os"
+ "path"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "sync"
+)
+
+type Fsdb struct {
+ name string
+ path string
+ prefix string
+ dbMap map[int][]byte
+ lastKey int
+ lock sync.Mutex
+}
+
+func NewFsdb(name string, path string, prefix string) *Fsdb {
+ d := new(Fsdb)
+ d.dbMap = make(map[int][]byte)
+ d.name = name
+ d.path = path
+ d.prefix = prefix
+ d.lastKey = -1
+ return d
+}
+
+func (d *Fsdb) FsRead(quote int) ([]byte, error) {
+ path := fmt.Sprintf("%s/%s%d.txt", d.path, d.prefix, quote)
+ b, err := ioutil.ReadFile(path)
+ if err != nil {
+ return b, err
+ }
+ return b, err
+}
+
+func (d *Fsdb) FsWrite(quote int, buf []byte) error {
+ path := fmt.Sprintf("%s/%s%d.txt", d.path, d.prefix, quote)
+ _, err := os.Stat(path)
+ if err == nil {
+ return fmt.Errorf("Entry %s already exists in fsdb\n",
+ path)
+ }
+ s := string(buf)
+ last := strings.Index(s, "\n")
+ err = ioutil.WriteFile(path, buf[0:last+1], 0644)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (d *Fsdb) Load() {
+ err := filepath.Walk(d.path, func(p string, f os.FileInfo, err error) error {
+ fi, err := os.Stat(p)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if fi.IsDir() {
+ return nil
+ }
+ base, ext := path.Base(p), path.Ext(p)
+ entry := base[len(d.prefix) : len(base)-len(ext)]
+ i, err := strconv.Atoi(entry)
+ if err != nil {
+ log.Fatal(err)
+ }
+ b, err := d.FsRead(i)
+ if err != nil {
+ log.Fatal(err)
+ }
+ _, err = d.Put(i, b)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return nil
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func (d *Fsdb) Put(key int, buf []byte) (int, error) {
+ d.lock.Lock()
+ defer d.lock.Unlock()
+ for _, v := range d.dbMap {
+ if string(v) == string(buf) {
+ return -1, errors.New("Duplicate entry: " + string(buf))
+ }
+ }
+ if key == -1 {
+ d.lastKey++
+ key = d.lastKey
+ } else {
+ if key > d.lastKey {
+ d.lastKey = key
+ }
+ }
+ if d.dbMap[key] != nil {
+ log.Fatal("Duplicate entry in fsdb")
+ }
+ d.dbMap[key] = buf
+ return key, nil
+}
+
+func (d *Fsdb) Get(key int) ([]byte, error) {
+ d.lock.Lock()
+ defer d.lock.Unlock()
+ if len(d.dbMap) == 0 {
+ return nil, errors.New("Empty DB, can't fetch entry")
+ }
+ buf := d.dbMap[key]
+ if buf == nil {
+ log.Fatal("Fetching nil entry from fsdb")
+ }
+ return buf, nil
+}
+
+func (d *Fsdb) Rand() ([]byte, int) {
+ d.lock.Lock()
+ defer d.lock.Unlock()
+
+ idx := rand.Intn(len(d.dbMap))
+ i := 0
+ for k, _ := range d.dbMap {
+ if i == idx {
+ buf := d.dbMap[k]
+ return buf, k
+ }
+ i++
+ }
+ return nil, -1
+}
+
+func (d *Fsdb) Empty() bool {
+ d.lock.Lock()
+ defer d.lock.Unlock()
+ if len(d.dbMap) == 0 {
+ return true
+ }
+ return false
+}
+
+func (d *Fsdb) Len() int {
+ return len(d.dbMap)
+}
diff --git a/src/kunt/TODO b/src/kunt/TODO
@@ -0,0 +1,5 @@
+Add multichannel/multiserver support
+Optionally cache youtube titles locally
+Write a sensible parser for the irc protocol
+Add a !mode command for setting up various options/modes
+Add a !flush command to write out the entire db to the fs
diff --git a/src/kunt/db/vagina b/src/kunt/db/vagina
@@ -0,0 +1,51 @@
+#!/bin/bash
+# thx tsakos! :)
+
+TMP_FILE=tmp.$$ # a temporary file for storing the URLs
+URL_DB=urls # directory that contains the URLs
+CNT=0 # link counter
+
+tar cfz $URL_DB.tgz $URL_DB
+
+check_avail() {
+STATUS=`curl -Is $1 | grep HTTP | cut -d ' ' -f 2`
+
+case $STATUS in
+200)
+ return 0
+ ;;
+404)
+ return 1
+ ;;
+esac
+}
+
+for i in `ls $URL_DB | grep -o '[0-9]*' | sort -g`
+do
+ cat $URL_DB/url$i.txt | tr -d '\r' >> $TMP_FILE
+done
+
+rm -rf urls/*
+
+while read URL
+do
+ if [ "`echo $URL | grep -Eo '([a-z0-9]*\.)?[a-z]*\.[a-z]*'`" ]
+ then
+ check_avail $URL
+ RETURN_CODE=$?
+
+ if [ $RETURN_CODE -ne 0 ]
+ then
+ echo "url$CNT.txt: BROKEN"
+ else
+ echo "url$CNT.txt: OK"
+ echo "$URL" >> $URL_DB/url$CNT.txt
+ CNT=$((CNT+1))
+ fi
+ else
+ echo "Invalid URL format: url$CNT.txt"
+ fi
+
+done < $TMP_FILE
+
+rm $TMP_FILE
diff --git a/src/kunt/fortune b/src/kunt/fortune
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+fortune -s -o | expand
diff --git a/src/kunt/init b/src/kunt/init
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+mkdir -p db/quotes
+mkdir -p db/urls
+mkdir -p certs
+openssl req -new -nodes -x509 -out certs/client.pem -keyout certs/client.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=kunt@2f30.org"
diff --git a/src/kunt/kunt.go b/src/kunt/kunt.go
@@ -0,0 +1,396 @@
+package main
+
+import (
+ "bytes"
+ "crypto/tls"
+ "crypto/x509"
+ "flag"
+ "fmt"
+ "fsdb"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "net"
+ "os"
+ "os/exec"
+ "strings"
+ "time"
+)
+
+/* Kunt context */
+type kuntCtx struct {
+ channel string
+ nick string
+ user string
+ srv string
+ port string
+ ssl bool
+ stime time.Time
+}
+
+func newKunt(channel string, nick string,
+ user string, srv string, port string, ssl bool) *kuntCtx {
+ return &kuntCtx{channel, nick, user, srv, port, ssl, time.Now()}
+}
+
+/* Helper routines */
+func botWrite(conn net.Conn, buf []byte) {
+ _, err := conn.Write(buf[0:])
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func resolveYoutubeTitle(link string) (title string, ret bool) {
+ var out bytes.Buffer
+ cmd := exec.Command("./resolve", link)
+ cmd.Stdout = &out
+ err := cmd.Run()
+ if err != nil {
+ if _, ok := err.(*exec.ExitError); ok {
+ title = ""
+ ret = false
+ return
+ }
+ }
+ title = out.String()
+ ret = true
+ return
+}
+
+/* Kunt command handling routines */
+func cmdKunt(conn net.Conn, s string) {
+ actions := []string{
+ "die",
+ "die bitch",
+ "are you talking to me?",
+ "ok",
+ "maybe",
+ "yes",
+ "no",
+ "no fucking way",
+ }
+ idx := rand.Intn(len(actions))
+ r := fmt.Sprintf("PRIVMSG %s :%s\r\n", kunt.channel, actions[idx])
+ botWrite(conn, []byte(r))
+}
+
+func cmdWisdom(conn net.Conn, s string) {
+ out, err := exec.Command("./fortune").Output()
+ if err != nil {
+ log.Fatal(err)
+ }
+ lines := strings.Split(string(out), "\n")
+ for _, i := range lines {
+ if i == "" {
+ continue
+ }
+ r := fmt.Sprintf("PRIVMSG %s :%s\r\n", kunt.channel, i)
+ botWrite(conn, []byte(r))
+ time.Sleep(512 * time.Millisecond)
+ }
+}
+
+func cmdHelp(conn net.Conn, s string) {
+ help := []string{
+ "!addquote <quote> -> Add a quote to the db",
+ "!randquote -> Print a random quote from the db",
+ "!countquotes -> Print the number of entries in the quote db",
+ "!addurl <url> -> Add a url to the db",
+ "!randurl -> Print a random url from the db",
+ "!counturls -> Print the number of entries in the url db",
+ "!uptime -> Show uptime",
+ "!wisdom -> Invoke fortune(6)",
+ "!src -> Fetch kunt's source code",
+ "!TODO -> Print the TODO list",
+ "!version -> Show version",
+ }
+ for _, i := range help {
+ r := fmt.Sprintf("PRIVMSG %s :%s\r\n", kunt.channel, i)
+ botWrite(conn, []byte(r))
+ time.Sleep(512 * time.Millisecond)
+ }
+}
+
+func cmdVersion(conn net.Conn, s string) {
+ ver := "v0.2.7"
+ r := fmt.Sprintf("PRIVMSG %s :%s\r\n", kunt.channel, ver)
+ botWrite(conn, []byte(r))
+}
+
+func cmdRandQuote(conn net.Conn, s string) {
+ if quoteDb.Empty() {
+ r := fmt.Sprintf("PRIVMSG %s :Empty quote database\r\n",
+ kunt.channel)
+ botWrite(conn, []byte(r))
+ return
+ }
+ quote, idx := quoteDb.Rand()
+ /* Print idx in red */
+ r := fmt.Sprintf("PRIVMSG %s :%s%02d[%d]%s %s\r\n", kunt.channel,
+ "\003", '5', idx, "\003", string(quote))
+ botWrite(conn, []byte(r))
+}
+
+func cmdAddQuote(conn net.Conn, s string) {
+ idx := len("!addquote")
+ if s[idx:idx+1] != " " {
+ r := fmt.Sprintf("PRIVMSG %s :Missing parameter for !addquote\r\n",
+ kunt.channel)
+ botWrite(conn, []byte(r))
+ return
+ }
+ s = s[idx+1:]
+ s = strings.TrimSpace(s)
+ s += "\n"
+ buf := []byte(s)
+ quote, err := quoteDb.Put(-1, buf)
+ if err != nil {
+ r := fmt.Sprintf("PRIVMSG %s :%s\r\n",
+ kunt.channel, err.Error())
+ botWrite(conn, []byte(r))
+ return
+ }
+ err = quoteDb.FsWrite(quote, buf)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func cmdCountQuotes(conn net.Conn, s string) {
+ r := fmt.Sprintf("PRIVMSG %s :The quote DB has %d quotes\r\n",
+ kunt.channel, quoteDb.Len())
+ botWrite(conn, []byte(r))
+}
+
+func cmdRandUrl(conn net.Conn, s string) {
+ if urlDb.Empty() {
+ r := fmt.Sprintf("PRIVMSG %s :Empty url database\r\n",
+ kunt.channel)
+ botWrite(conn, []byte(r))
+ return
+ }
+ url, idx := urlDb.Rand()
+ /* Print idx in red */
+ r := fmt.Sprintf("PRIVMSG %s :%s%02d[%d]%s %s\r\n", kunt.channel,
+ "\003", '5', idx, "\003", string(url))
+ botWrite(conn, []byte(r))
+ title, ok := resolveYoutubeTitle(string(url))
+ if ok {
+ r = fmt.Sprintf("PRIVMSG %s :[YouTube] %s\r\n",
+ kunt.channel, title)
+ botWrite(conn, []byte(r))
+ }
+}
+
+func cmdAddUrl(conn net.Conn, s string) {
+ idx := len("!addurl")
+ if s[idx:idx+1] != " " {
+ r := fmt.Sprintf("PRIVMSG %s :Missing parameter for !addurl\r\n",
+ kunt.channel)
+ botWrite(conn, []byte(r))
+ return
+ }
+ s = s[idx+1:]
+ s = strings.TrimSpace(s)
+ s += "\n"
+ buf := []byte(s)
+ url, err := urlDb.Put(-1, buf)
+ if err != nil {
+ r := fmt.Sprintf("PRIVMSG %s :%s\r\n",
+ kunt.channel, err.Error())
+ botWrite(conn, []byte(r))
+ return
+ }
+ err = urlDb.FsWrite(url, buf)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func cmdCountUrls(conn net.Conn, s string) {
+ r := fmt.Sprintf("PRIVMSG %s :The url DB has %d urls\r\n",
+ kunt.channel, urlDb.Len())
+ botWrite(conn, []byte(r))
+}
+
+func cmdUptime(conn net.Conn, s string) {
+ etime := time.Now()
+ r := fmt.Sprintf("PRIVMSG %s :%v\r\n", kunt.channel,
+ etime.Sub(kunt.stime))
+ botWrite(conn, []byte(r))
+}
+
+func cmdSrc(conn net.Conn, s string) {
+ src := "http://amnezia.2f30.org/tmp/kunt-latest.tgz"
+ r := fmt.Sprintf("PRIVMSG %s :%s\r\n", kunt.channel, src)
+ botWrite(conn, []byte(r))
+}
+
+func cmdTodo(conn net.Conn, s string) {
+ todo, err := ioutil.ReadFile("TODO")
+ if err != nil {
+ r := fmt.Sprintf("PRIVMSG %s :No TODO list available\r\n", kunt.channel)
+ botWrite(conn, []byte(r))
+ return
+ }
+ sf := strings.FieldsFunc(string(todo), func(r rune) bool {
+ switch r {
+ case '\n':
+ return true
+ }
+ return false
+ })
+ for _, i := range sf {
+ r := fmt.Sprintf("PRIVMSG %s :%s\r\n", kunt.channel, i)
+ botWrite(conn, []byte(r))
+ time.Sleep(512 * time.Millisecond)
+ }
+}
+
+var sslon = flag.Bool("s", false, "SSL support")
+
+var quoteDb *fsdb.Fsdb
+var urlDb *fsdb.Fsdb
+var kunt *kuntCtx
+
+func main() {
+ flag.Parse()
+
+ if flag.NArg() < 1 {
+ fmt.Fprintf(os.Stderr, "usage: %s [-s] host:port\n",
+ os.Args[0])
+ os.Exit(1)
+ }
+ service := flag.Arg(0)
+
+ log.SetPrefix("kunt: ")
+
+ hostport := strings.Split(flag.Arg(0), ":")
+ kunt = newKunt("#2f30", "kunt", "z0mg", hostport[0], hostport[1], *sslon)
+
+ if *sslon {
+ fmt.Println("SSL on")
+ }
+
+ tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if *sslon {
+ cert, err := tls.LoadX509KeyPair("certs/client.pem",
+ "certs/client.key")
+ if err != nil {
+ log.Fatal(err)
+ }
+ conf := tls.Config{Certificates: []tls.Certificate{cert},
+ InsecureSkipVerify: true}
+
+ conn, err := tls.Dial("tcp", service, &conf)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer conn.Close()
+
+ fmt.Println("Client: connected to: ", conn.RemoteAddr())
+
+ state := conn.ConnectionState()
+
+ for _, v := range state.PeerCertificates {
+ fmt.Println(x509.MarshalPKIXPublicKey(v.PublicKey))
+ fmt.Println(v.Subject)
+ }
+
+ fmt.Println("Client: handshake: ", state.HandshakeComplete)
+ fmt.Println("Client: mutual: ", state.NegotiatedProtocolIsMutual)
+ loop(conn)
+ } else {
+ conn, err := net.DialTCP("tcp", nil, tcpAddr)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer conn.Close()
+ loop(conn)
+ }
+}
+
+func loop(conn net.Conn) {
+ quoteDb = fsdb.NewFsdb("Quote DB", "db/quotes", "quote")
+ urlDb = fsdb.NewFsdb("Url DB", "db/urls", "url")
+
+ quoteDb.Load()
+ urlDb.Load()
+
+ go InitiateConnection(conn)
+
+ rand.Seed(time.Now().UnixNano())
+
+ dispatch := map[string]func(net.Conn, string){
+ "kunt": cmdKunt,
+ "!wisdom": cmdWisdom,
+ "!help": cmdHelp,
+ "!version": cmdVersion,
+ "!randquote": cmdRandQuote,
+ "!addquote": cmdAddQuote,
+ "!countquotes": cmdCountQuotes,
+ "!randurl": cmdRandUrl,
+ "!addurl": cmdAddUrl,
+ "!counturls": cmdCountUrls,
+ "!uptime": cmdUptime,
+ "!src": cmdSrc,
+ "!TODO": cmdTodo,
+ }
+
+ for {
+ var buf [512]byte
+ n, err := conn.Read(buf[0:])
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println(string(buf[0 : n-1]))
+ s := string(buf[0:n])
+ for i := range buf {
+ buf[i] = 0
+ }
+
+ if strings.HasPrefix(s, "PING :") {
+ r := strings.Replace(s, "PING :", "PONG :", 6)
+ r = r + "\r\n"
+ _, err := conn.Write([]byte(r))
+ if err != nil {
+ log.Fatal(err)
+ }
+ continue
+ }
+
+ q := fmt.Sprintf("PRIVMSG %s :", kunt.channel)
+ msg := strings.Index(s, q)
+ if msg == -1 {
+ continue
+ }
+ msg += len(q)
+ s = s[msg:]
+ s = strings.TrimSpace(s)
+ s += "\n"
+
+ for i, v := range dispatch {
+ if strings.HasPrefix(s, i) {
+ go v(conn, s)
+ break
+ }
+ }
+ }
+
+ os.Exit(0)
+}
+
+func InitiateConnection(conn net.Conn) {
+ fmt.Println("initcon")
+ query := fmt.Sprintf("NICK %s\r\n", kunt.nick)
+ botWrite(conn, []byte(query))
+ query = fmt.Sprintf("USER %s * 8 :%s\r\n", kunt.user, kunt.nick)
+ botWrite(conn, []byte(query))
+ query = fmt.Sprintf("JOIN %s\r\n", kunt.channel)
+ botWrite(conn, []byte(query))
+}
diff --git a/src/kunt/resolve b/src/kunt/resolve
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+export LINK=$@
+
+if [ -z ${LINK} ]; then
+ echo "usage: $0 <link>"
+ exit 1
+fi
+
+echo ${LINK} | grep -qie "\(\(\(www.\)\?youtube.com/.*\(?v=\|&v=\).\+\)\|\(youtu.be/.\+\)\)"
+if [ $? -eq 0 ]; then
+ TITLE=$(echo ${LINK} | awk -F'=' '{print $2}')
+ QUERY="-qO - https://gdata.youtube.com/feeds/api/videos/${TITLE}"
+ wget ${QUERY} | grep -oe "<title type='text'>.*</title>" | awk -F'>' '{print $2}' | awk -F'<' '{print $1}' | tr -d '\n'
+ exit 0
+else
+ exit 1
+fi
diff --git a/src/kunt/run b/src/kunt/run
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+while :; do ./kunt -s otrere.irc.gr:9667; sleep 20; done