kunt

golang IRC bot
git clone git://git.2f30.org/kunt
Log | Files | Refs | LICENSE

kunt.go (19526B)


      1 // Copyright 2013 TLH and dsp. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package main
      6 
      7 import (
      8 	"flag"
      9 	"fmt"
     10 	"games"
     11 	"io"
     12 	"io/ioutil"
     13 	"irc"
     14 	"log"
     15 	"mapfs"
     16 	"math/rand"
     17 	"net/http"
     18 	"os"
     19 	"os/exec"
     20 	"regexp"
     21 	"strings"
     22 	"syscall"
     23 	"time"
     24 )
     25 
     26 type kuntCtx struct {
     27 	stime  time.Time
     28 	ircCtx *irc.Context
     29 }
     30 
     31 func cmdKunt(msg irc.Message) {
     32 	replies := []string{
     33 		"dn niotho maaaan",
     34 		"eimai kokalo telios... gama ta",
     35 		"pwpw gamise ta, den vlepo brosta mou",
     36 		"yoyoyoyo duuuuude",
     37 		"die",
     38 		"ok",
     39 		"maybe",
     40 		"yes",
     41 	}
     42 	if msg.Nick == "dsp" || msg.Nick == "TLH" || msg.Nick == "sovarofanis" {
     43 		replies = []string{
     44 			"You are my Mastah!",
     45 			"I will always obey you my Mastah!",
     46 			"vv maaaaan",
     47 			"etimazo na stripso ena ro... ade vives",
     48 			"420 dawwwwwwg",
     49 			"You own all ma base niggah, chiiilll",
     50 			"spliff it up bro!",
     51 		}
     52 	}
     53 	if strings.Index(msg.Args[1], "vv") != -1 ||
     54 		strings.Index(msg.Args[1], "vives") != -1 {
     55 		replies = []string{
     56 			"vives alani",
     57 			"vv maaaan!!",
     58 			"skastoooo",
     59 			"vv http://www.youtube.com/watch?v=wHzxh9fneJ8 <-- varaeeiiiiii",
     60 			"vives!",
     61 			"vv!",
     62 			"vv dawg",
     63 		}
     64 	}
     65 	if strings.Index(msg.Args[1], "vi ") != -1 ||
     66 		strings.Index(msg.Args[1], " vi") != -1 ||
     67 		strings.Index(msg.Args[1], "vim ") != -1 ||
     68 		strings.Index(msg.Args[1], " vim") != -1 {
     69 		replies = []string{
     70 			"emax ftw",
     71 			"emacs ftw!",
     72 			"EMACS!",
     73 			"ed is the standard editor.",
     74 			"emax or die",
     75 			"mg or die",
     76 			"vi sux",
     77 			"vim sux ass",
     78 		}
     79 	}
     80 	idx := rand.Intn(len(replies))
     81 	kunt.ircCtx.Privmsg(msg.Args[0], string(replies[idx]))
     82 }
     83 
     84 func cmdWisdom(msg irc.Message) {
     85 	out, err := exec.Command("./fortune").Output()
     86 	if err != nil {
     87 		log.Fatal(err)
     88 	}
     89 	lines := strings.Split(string(out), "\n")
     90 	for _, i := range lines {
     91 		if i == "" {
     92 			continue
     93 		}
     94 		kunt.ircCtx.Privmsg(msg.Args[0], i)
     95 		time.Sleep(512 * time.Millisecond)
     96 	}
     97 }
     98 
     99 func cmdHelp(msg irc.Message) {
    100 	help := []string{
    101 		"!addquote <quote> -> Add a quote to the db",
    102 		"!randquote        -> Print a random quote from the db",
    103 		"!countquotes      -> Print the number of entries in the quote db",
    104 		"!addurl <url>     -> Add a url to the db",
    105 		"!randurl          -> Print a random url from the db",
    106 		"!counturls        -> Print the number of entries in the url db",
    107 		"!uptime           -> Show uptime",
    108 		"!wisdom           -> Invoke fortune(6)",
    109 		"!radio            -> Print now playing song",
    110 		"!game             -> List or Play games",
    111 		"!src              -> Fetch kunt's source code",
    112 		"!TODO             -> Print the TODO list",
    113 		"!version          -> Show version",
    114 		"!copyright        -> Show copyright info",
    115 	}
    116 	for _, i := range help {
    117 		kunt.ircCtx.Privmsg(msg.Args[0], i)
    118 		time.Sleep(512 * time.Millisecond)
    119 	}
    120 }
    121 
    122 func cmdVersion(msg irc.Message) {
    123 	ver := "v0.2.9"
    124 	kunt.ircCtx.Privmsg(msg.Args[0], ver)
    125 }
    126 
    127 func cmdRandQuote(msg irc.Message) {
    128 	if quoteDb.Empty() {
    129 		kunt.ircCtx.Privmsg(msg.Args[0], "Empty quote database")
    130 		return
    131 	}
    132 	quote, idx := quoteDb.Rand()
    133 	quote = []byte(strings.TrimSpace(string(quote)))
    134 	// Print idx in red
    135 	text := fmt.Sprintf("%s%02d[%d]%s %s",
    136 		"\003", '5', idx, "\003", string(quote))
    137 	kunt.ircCtx.Privmsg(msg.Args[0], text)
    138 }
    139 
    140 func cmdAddQuote(msg irc.Message) {
    141 	text := msg.Args[1]
    142 	if len(strings.Fields(text)) < 2 {
    143 		kunt.ircCtx.Privmsg(msg.Args[0], "Missing parameter for !addquote")
    144 		return
    145 	}
    146 	text = text[10:]
    147 	text = strings.TrimSpace(text)
    148 	text += "\n"
    149 	buf := []byte(text)
    150 	if quoteDb.CountMatches(buf) > 0 {
    151 		kunt.ircCtx.Privmsg(msg.Args[0],
    152 			fmt.Errorf("Duplicate entry: %s", buf).Error())
    153 		return
    154 	}
    155 	_, err := quoteDb.Append(buf)
    156 	if err != nil {
    157 		kunt.ircCtx.Privmsg(msg.Args[0], err.Error())
    158 		return
    159 	}
    160 	err = quoteDb.Sync()
    161 	if err != nil {
    162 		log.Fatal(err)
    163 	}
    164 }
    165 
    166 func cmdCountQuotes(msg irc.Message) {
    167 	text := fmt.Sprintf("The quote DB has %d quotes", quoteDb.Size())
    168 	kunt.ircCtx.Privmsg(msg.Args[0], text)
    169 }
    170 
    171 func cmdTheoQuote(msg irc.Message) {
    172 	quotes := []string{
    173 	"Write more code.",
    174 	"Make more commits.",
    175 	"That's because you have been slacking.",
    176 	"slacker!",
    177 	"That's what happens when you're lazy.",
    178 	"idler!",
    179 	"slackass!",
    180 	"lazy bum!",
    181 	"Stop slacking you lazy bum!",
    182 	"slacker slacker lazy bum bum bum slacker!",
    183 	"I could search... but I'm a lazy bum ;)",
    184 	"sshutup sshithead, ssharpsshooting susshi sshplats ssharking assholes.",
    185 	"Lazy bums slacking on your asses.",
    186 	"35 commits an hour? That's pathetic!",
    187 	"Fine software takes time to prepare.  Give a little slack.",
    188 	"I am just stating a fact",
    189 	"you bring new meaning to the terms slackass. I will have to invent a new term.",
    190 	"if they cut you out, muddy their back yards",
    191 	"Make them want to start over, and play nice the next time.",
    192 	"It is clear that this has not been thought through.",
    193 	"avoid using abort().  it is not nice.",
    194 	"That's the most ridiculous thing I've heard in the last two or three minutes!",
    195 	"I'm not just doing this for crowd response. I need to be right.",
    196 	"I'd put a fan on my bomb.. And blinking lights...",
    197 	"I love to fight",
    198 	"No sane people allowed here.  Go home.",
    199 	"you have to stop peeing on your breakfast",
    200 	"feature requests come from idiots",
    201 	"henning and darren / sitting in a tree / t o k i n g / a joint or three",
    202 	"KICK ASS. TIME FOR A JASON LOVE IN!  WE CAN ALL GET LOST IN HIS HAIR!",
    203 	"shame on you for following my rules.",
    204 	"altq's parser sucks dead whale farts through the finest chemistry pipette's",
    205 	"screw this operating system shit, i just want to drive!",
    206 	"Search for fuck.  Anytime you see that word, you have a paragraph to write.",
    207 	"Yes, but the ports people are into S&M.",
    208 	"Buttons are for idiots.",
    209 	"We are not hackers. We are turd polishing craftsmen.",
    210 	"who cares.  style(9) can bite my ass",
    211 	"It'd be one fucking happy planet if it wasn't for what's under this fucking sticker.",
    212 	"I would explain, but I am too drunk.",
    213 	"you slackers don't deserve pictures yet",
    214 	"Vegetarian my ass",
    215 	"Wait a minute, that's a McNally's!",
    216 	"don't they recognize their moral responsibility to entertain me?",
    217 	"#ifdef is for emacs developers.",
    218 	"Many well known people become net-kooks in their later life, because they lose touch with reality.",
    219 	"You're not allowed to have an opinion.",
    220 	"tweep tweep tweep",
    221 	"Quite frankly, SSE's alignment requirement is the most utterly retarded idea since eating your own shit.",
    222 	"Holy verbose prom startup Batman.",
    223 	"Any day now, when we sell out.",
    224 	"optimism in man kind does not belong here",
    225 	"First user who tries to push this button, he pounds into the ground with a rant of death.",
    226 	"we did farts.  now we do sperm.  we are cutting edge.",
    227 	"the default configuration is a mixture of piss, puke, shit, and bloody entrails.",
    228 	"Stop wasting your time reading people's licenses.",
    229 	"doing it with environment variables is OH SO SYSTEM FIVE LIKE OH MY GOD PASS ME THE SPOON",
    230 	"Linux is fucking POO, not just bad, bad REALLY REALLY BAD",
    231 	"penguins are not much more than chickens that swim.",
    232 	"i am a packet sniffing fool, let me wipe my face with my own poo",
    233 	"Whiners.  They scale really well.",
    234 	"in your world, you would have a checklist of 50 fucking workarounds just to make a coffee.",
    235 	"for once, I have nothing to say.",
    236 	"You have no idea how fucked we are",
    237 	"You can call it fart if you want to.",
    238 	"wavelan is a battle field",
    239 	"You are in a maze of gpio pins, all alike, all undocumented, and a few are wired to bombs.",
    240 	"And that is why humppa sucks... cause it has no cause.",
    241 	"cache aliasing is a problem that would have stopped in 1992 if someone had killed about 5 people who worked at Sun.",
    242 	"Don't spread rumours about me being gentle.",
    243 	"If municipal water filtering equipment was built by the gcc developers, the western world would be dead by now.",
    244 	"kettenis supported a new machine in my basement and all I got to do was fix a 1 character typo in his html page commit.",
    245 	"industry told us a lesson: when you're an asshole, they mail you hardware",
    246 	"I was joking, really.  I think I am funny :-)",
    247 	"the kernel is a harsh mistress",
    248 	"Have I ever been subtle? If my approach ever becomes subtle, shoot me.",
    249 	"the acpi stabs you in the back.  the acpi stabs you in the back. you die ...",
    250 	"My cats are more observant than you.",
    251 	"our kernels have no bugs",
    252 	"style(9) has all these fascist rules, and i have a problem with some of them because i didn't come up with them",
    253 	"I'm not very reliable",
    254 	"I don't like control",
    255 	"You aren't being conservative -- you are trying to be a caveman.",
    256 	"nfs loves everyone",
    257 	"basically, dung beetles fucking.  that's what kerberosV + openssl is like",
    258 	"I would rather run Windows than use vi.",
    259 	"if you assign that responsibility to non-hikers I will walk over and cripple you now.",
    260 	"i ojbect two yoru splelng of achlhlocis.",
    261 	"We have two kinds of developers - those that deal with their own shit and those that deal with other people's shit.",
    262 	"If people keep adding such huge stuff, soon mg will be bigger than emacs.",
    263 	"this change comes down to: This year, next year, 5 years from now, 10 years from now, or Oh fuck.",
    264 	"backwards compatibility is king, and will remain king, until 2038.",
    265 	"I don't know if the Internet's safe yet.",
    266 	"Those who don't understand Unix are condemned to reinvent Multics in a browser",
    267 	"Don't tell anybody I said that.",
    268 	"Complaint forms are handled in another department.",
    269 	"You'd be safer using Windows than the code which was just deleted.",
    270 	"Shit should not be shared.",
    271 	"the randomization in this entire codebase is a grand experiment in stupid",
    272 	"My mailbox is full of shock.",
    273 	"my integer overflow spidey senses are tingling.",
    274 	"I'm just trying to improve the code...",
    275 	"It's a pleasure to work on code you can't make worse.",
    276 	"It's largely bad style to do (int)sizeof",
    277 	"When I see Makefile.in, I know that \"in\" is short for \"insane\".",
    278 	"This is the beer. And that's why we need a hackathon.",
    279 	}
    280 
    281 	idx := rand.Intn(len(quotes))
    282 	kunt.ircCtx.Privmsg(msg.Args[0], quotes[idx])
    283 }
    284 
    285 func cmdRandUrl(msg irc.Message) {
    286 	if urlDb.Empty() {
    287 		kunt.ircCtx.Privmsg(msg.Args[0], "Empty url database")
    288 		return
    289 	}
    290 	url, idx := urlDb.Rand()
    291 	url = []byte(strings.TrimSpace(string(url)))
    292 	// Print idx in red
    293 	r := fmt.Sprintf("%s%02d[%d]%s %s",
    294 		"\003", '5', idx, "\003", string(url))
    295 	kunt.ircCtx.Privmsg(msg.Args[0], r)
    296 }
    297 
    298 func resolveYoutubeTitle(url string) (title string, ok bool) {
    299 	title = ""
    300 	ok = false
    301 	if len(ytregex.FindAllString(url, -1)) > 0 {
    302 		title = "Unresolved title"
    303 		ok = true
    304 		resp, err := http.Get(url)
    305 		if err == nil {
    306 			defer resp.Body.Close()
    307 			contents, err := ioutil.ReadAll(resp.Body)
    308 			if err == nil {
    309 				titles := titleregex.FindAllString(string(contents), 1)
    310 				if len(titles) > 0 {
    311 					//this relates to the way titleregex is
    312 					title = string(titles[0][35 : len(titles[0])-2])
    313 				} else {
    314 					log.Printf(" [resolveYoutubeTitle] could not find the title regexp for url: %s \n", url)
    315 				}
    316 			} else {
    317 				log.Printf(" [resolveYoutubeTitle] could not read response body with ioutil\n")
    318 			}
    319 		} else {
    320 			log.Printf(" [resolveYoutubeTitle] could not connect to youtube url for title fetching \n")
    321 		}
    322 		//print [Youtube] tag in purple
    323 		title = fmt.Sprintf("        %s%02d[%s]%s %s",
    324 			"\003", '6', "Youtube", "\003", string(title))
    325 	}
    326 	return
    327 }
    328 
    329 func cmdAddUrl(msg irc.Message) {
    330 	text := msg.Args[1]
    331 	title := ""
    332 	if len(strings.Fields(text)) < 2 {
    333 		kunt.ircCtx.Privmsg(msg.Args[0], "Missing parameter for !addurl")
    334 		return
    335 	}
    336 	text = text[8:]
    337 	text = strings.TrimSpace(text)
    338 	// Check for yt link
    339 	title, ok := resolveYoutubeTitle(text)
    340 	if ok {
    341 		text += title + "\n"
    342 	} else {
    343 		text += "\n"
    344 	}
    345 	buf := []byte(text)
    346 	if urlDb.CountMatches(buf) > 0 {
    347 		kunt.ircCtx.Privmsg(msg.Args[0],
    348 			fmt.Errorf("Duplicate entry: %s", buf).Error())
    349 		return
    350 	}
    351 	_, err := urlDb.Append(buf)
    352 	if err != nil {
    353 		kunt.ircCtx.Privmsg(msg.Args[0], err.Error())
    354 		return
    355 	}
    356 	err = urlDb.Sync()
    357 	if err != nil {
    358 		log.Fatal(err)
    359 	}
    360 }
    361 
    362 func cmdCountUrls(msg irc.Message) {
    363 	r := fmt.Sprintf("The url DB has %d urls", urlDb.Size())
    364 	kunt.ircCtx.Privmsg(msg.Args[0], r)
    365 }
    366 
    367 func cmdRadio(msg irc.Message) {
    368 	np, e := http.Get("http://radio.2f30.org:8000/")
    369 	if e != nil {
    370 		kunt.ircCtx.Privmsg(msg.Args[0], "Is radio broken?")
    371 	} else {
    372 		defer np.Body.Close()
    373 		info, _ := ioutil.ReadAll(np.Body)
    374 		lines := strings.Split(string(info), "\n")
    375 		for _, i := range lines {
    376 			if i == "" {
    377 				continue
    378 			}
    379 			kunt.ircCtx.Privmsg(msg.Args[0], i)
    380 			time.Sleep(512 * time.Millisecond)
    381 		}
    382 	}
    383 }
    384 
    385 func cmdUptime(msg irc.Message) {
    386 	etime := time.Now()
    387 	r := fmt.Sprintf("%v", etime.Sub(kunt.stime))
    388 	kunt.ircCtx.Privmsg(msg.Args[0], r)
    389 }
    390 
    391 func cmdSrc(msg irc.Message) {
    392 	src := "http://git.2f30.org/kunt"
    393 	kunt.ircCtx.Privmsg(msg.Args[0], src)
    394 }
    395 
    396 func cmdTodo(msg irc.Message) {
    397 	todo, err := ioutil.ReadFile("TODO")
    398 	if err != nil {
    399 		kunt.ircCtx.Privmsg(msg.Args[0], "No TODO list available")
    400 		return
    401 	}
    402 	sf := strings.FieldsFunc(string(todo), func(r rune) bool {
    403 		switch r {
    404 		case '\n':
    405 			return true
    406 		}
    407 		return false
    408 	})
    409 	for _, v := range sf {
    410 		kunt.ircCtx.Privmsg(msg.Args[0], v)
    411 		time.Sleep(512 * time.Millisecond)
    412 	}
    413 }
    414 
    415 func cmdGame(msg irc.Message) {
    416 	text := msg.Args[1]
    417 	if len(strings.Fields(text)) < 2 {
    418 		for _, str := range geng.List() {
    419 			kunt.ircCtx.Privmsg(msg.Args[0], "[game] "+str)
    420 		}
    421 		return
    422 	}
    423 	text = strings.TrimSpace(text[5:])
    424 	found := false
    425 	for _, str := range geng.List() {
    426 		if str == text {
    427 			found = true
    428 			break
    429 		}
    430 	}
    431 	if !found {
    432 		kunt.ircCtx.Privmsg(msg.Args[0], "[game] game not found")
    433 		return
    434 	}
    435 	gchan := make(chan irc.Message)
    436 	geng.New(text, kunt.ircCtx, &gchan)
    437 	time.Sleep(512 * time.Millisecond)
    438 	gchan <- irc.Message{Command: "SETNICK", Args: []string{botname}} //Let the game engine know.
    439 	gchan <- irc.Message{Command: "SETCHAN", Args: []string{msg.Args[0]}}
    440 }
    441 
    442 func cmdCopyright(msg irc.Message) {
    443 	copyright := []string{
    444 		"Copyright 2013 TLH and dsp. All rights reserved.",
    445 		"Use of this source code is governed by a BSD-style",
    446 		"license that can be found in the LICENSE file.",
    447 	}
    448 	for _, i := range copyright {
    449 		kunt.ircCtx.Privmsg(msg.Args[0], i)
    450 		time.Sleep(512 * time.Millisecond)
    451 	}
    452 }
    453 
    454 func fifoCmdParse() {
    455 	fi, err := os.Open(".kfifo")
    456 	if err != nil {
    457 		log.Fatal(err)
    458 	}
    459 	defer func() {
    460 		if err := fi.Close(); err != nil {
    461 			log.Fatal(err)
    462 		}
    463 	}()
    464 	for {
    465 		buf := make([]byte, 1024)
    466 		n, err := fi.Read(buf)
    467 		if err != nil && err != io.EOF {
    468 			log.Fatal(err)
    469 		}
    470 		if n > 0 {
    471 			buf = buf[0 : n-1]
    472 			switch string(buf) {
    473 			case "netjoin":
    474 				kunt.ircCtx.JoinChannels()
    475 			case "urlupdate":
    476 				updateUrlDbTitles("db/urls-new")
    477 			case "quit":
    478 				donechan <- true
    479 			default:
    480 				log.Printf("Unknown fifo command:%s\n", string(buf))
    481 			}
    482 		}
    483 	}
    484 }
    485 
    486 func updateUrlDbTitles(path string) {
    487 	log.Printf("*** starting urlDb update under path %s ***\n", path)
    488 	newurlPlain := mapfs.NewPlainMap()
    489 	_, err := os.Stat(path)
    490 	if err == nil {
    491 		log.Printf(" [updateUrlDbTitles] for the db to be updated, please provide a non-existant directory to be populated. %s exists\n", path)
    492 		return
    493 	}
    494 	newurlDb := mapfs.NewMapfs(newurlPlain, "Urls", path, "url")
    495 	err = newurlDb.Load()
    496 	if err != nil {
    497 		log.Printf("error on loading new urlDb: %s\n", err.Error())
    498 		return
    499 	}
    500 	iter := mapfs.MakeMapIter(urlDb)
    501 	whitespacefunc := func(a rune) bool {
    502 		return a == '\t' || a == ' '
    503 	}
    504 	for {
    505 		_, v, ok := iter()
    506 		if !ok {
    507 			break
    508 		}
    509 		stringv := strings.TrimSpace(string(v))
    510 		strs := strings.FieldsFunc(stringv, whitespacefunc)
    511 		title, ok := resolveYoutubeTitle(strs[0])
    512 		if ok {
    513 			stringv = strs[0] + title + "\n"
    514 		} else {
    515 			stringv = strs[0] + "\n"
    516 		}
    517 		buf := []byte(stringv)
    518 		_, err := newurlDb.Append(buf)
    519 		if err != nil {
    520 			log.Printf(" [updateUrlDbTitles] %s\n", err.Error())
    521 			return
    522 		}
    523 		err = newurlDb.Sync()
    524 		if err != nil {
    525 			log.Fatal(err)
    526 		}
    527 	}
    528 	log.Printf("*** urlDb update finished ***\n")
    529 }
    530 
    531 
    532 var quoteDb *mapfs.Mapfs
    533 var urlDb *mapfs.Mapfs
    534 var kunt kuntCtx
    535 var geng games.GameEnger
    536 var botname string
    537 var ytregex *regexp.Regexp
    538 var titleregex *regexp.Regexp
    539 var configfile string
    540 var sslon = flag.Bool("s", false, "SSL support")
    541 var cfg *irc.Config
    542 var donechan chan bool
    543 
    544 func init() {
    545 	flag.StringVar(&configfile,"c","","JSON configuration file")
    546 }
    547 
    548 func main() {
    549 	kunt.stime = time.Now()
    550 	botname = "kunt"
    551 	geng = games.NewGameEng()
    552 	log.SetPrefix("kunt: ")
    553 
    554 	flag.Parse()
    555 	if flag.NArg() < 1 && configfile == "" {
    556 		fmt.Fprintf(os.Stderr, "usage: %s [-s] [-c configfile] host:port\n",
    557 			os.Args[0])
    558 		os.Exit(2)
    559 	}
    560 
    561 
    562 	quotePlain := mapfs.NewPlainMap()
    563 	quoteDb = mapfs.NewMapfs(quotePlain, "Quotes", "db/quotes", "quote")
    564 	urlPlain := mapfs.NewPlainMap()
    565 	urlDb = mapfs.NewMapfs(urlPlain, "Urls", "db/urls", "url")
    566 
    567 	err := quoteDb.Load()
    568 	if err != nil {
    569 		log.Fatal(err)
    570 	}
    571 	err = urlDb.Load()
    572 	if err != nil {
    573 		log.Fatal(err)
    574 	}
    575 
    576 	// Compile the yt regexp
    577 	ytregex = regexp.MustCompile("(((www.)?youtube.com/.*(.v=|&v=).+)|(youtu.be/.+))")
    578 	titleregex = regexp.MustCompile("<meta property=\"og:title\" content=\".*\">")
    579 
    580 	// Check for duplicate entries
    581 	iter := mapfs.MakeMapIter(quoteDb)
    582 	for {
    583 		k, v, ok := iter()
    584 		if !ok {
    585 			break
    586 		}
    587 		if quoteDb.CountMatches(v) > 1 {
    588 			log.Fatal(fmt.Errorf("Duplicate entry in quote DB with key: %d - val: %s", k, v))
    589 		}
    590 	}
    591 
    592 	iter = mapfs.MakeMapIter(urlDb)
    593 	for {
    594 		k, v, ok := iter()
    595 		if !ok {
    596 			break
    597 		}
    598 		if urlDb.CountMatches(v) > 1 {
    599 			log.Fatal(fmt.Errorf("Duplicate entry in url DB with key: %d - val: %s", k, v))
    600 		}
    601 	}
    602 
    603 	rand.Seed(time.Now().UnixNano())
    604 
    605 	dispatch := map[string]func(irc.Message){
    606 		botname:        cmdKunt,
    607 		"!theo":	cmdTheoQuote,
    608 		"!wisdom":      cmdWisdom,
    609 		"!help":        cmdHelp,
    610 		"!version":     cmdVersion,
    611 		"!randquote":   cmdRandQuote,
    612 		"!addquote":    cmdAddQuote,
    613 		"!countquotes": cmdCountQuotes,
    614 		"!randurl":     cmdRandUrl,
    615 		"!addurl":      cmdAddUrl,
    616 		"!counturls":   cmdCountUrls,
    617 		"!radio":       cmdRadio,
    618 		"!uptime":      cmdUptime,
    619 		"!src":         cmdSrc,
    620 		"!TODO":        cmdTodo,
    621 		"!game":        cmdGame,
    622 		"!copyright":   cmdCopyright,
    623 	}
    624 	if configfile == "" {
    625 		hostport := strings.Split(flag.Arg(0), ":")
    626 
    627 		cfg = &irc.Config{
    628 			NetworkName: "freenode",
    629 			Nick:        botname,
    630 			User:        "z0mg",
    631 			RealName:    botname,
    632 			Serv:        hostport[0],
    633 			Port:        hostport[1],
    634 			Ssl:         *sslon,
    635 			Channels:    []string{"#2f30"},
    636 		}
    637 	} else {
    638 		cfg,err = irc.LoadConfig(configfile)
    639 		if err != nil {
    640 			log.Fatal(err)
    641 		}
    642 	}
    643 
    644 	kunt.ircCtx = irc.NewIrcContext(*cfg)
    645 	kunt.ircCtx.AddEventHandler(irc.Event{Command: "PRIVMSG", Fn: func(msg irc.Message) {
    646 		cmd := msg.Args[1]
    647 		for i, v := range dispatch {
    648 			if strings.HasPrefix(cmd, i) {
    649 				go v(msg)
    650 			}
    651 		}
    652 	}})
    653 
    654 	kunt.ircCtx.AddEventHandler(irc.Event{Command: "PING", Fn: func(msg irc.Message) {
    655 		kunt.ircCtx.Pong(msg.Args[0])
    656 	}})
    657 
    658 	kunt.ircCtx.AddEventHandler(irc.Event{Command: "ERROR", Fn: func(msg irc.Message) {
    659 		os.Exit(0)
    660 	}})
    661 
    662 	kunt.ircCtx.AddEventHandler(irc.Event{Command: "KICK", Fn: func(msg irc.Message) {
    663 		time.Sleep(750 * time.Millisecond)
    664 		kunt.ircCtx.Join("#2f30", "")
    665 	}})
    666 
    667 	err = kunt.ircCtx.Connect()
    668 	if err != nil {
    669 		log.Fatal(err)
    670 	}
    671 	kunt.ircCtx.Login()
    672 
    673 	syscall.Mkfifo(".kfifo", 0600)
    674 	go fifoCmdParse()
    675 
    676 	donechan = make(chan bool)
    677 	select {
    678 	case stat := <-donechan:
    679 		if stat {
    680 			break
    681 		}
    682 	}
    683 
    684 	kunt.ircCtx.Quit("releasing entropy")
    685 	log.Println(" [Exiting] ")
    686 }