irc-medialink/main.go

281 lines
7.9 KiB
Go

package main
import (
"log"
"strings"
"time"
"net/url"
"github.com/mvdan/xurls"
"github.com/thoj/go-ircevent"
"gopkg.in/alecthomas/kingpin.v2"
"github.com/icedream/irc-medialink/manager"
"github.com/icedream/irc-medialink/parsers/soundcloud"
"github.com/icedream/irc-medialink/parsers/web"
"github.com/icedream/irc-medialink/parsers/wikipedia"
"github.com/icedream/irc-medialink/parsers/youtube"
)
func must(err error) {
if err == nil {
return
}
log.Fatal(err)
}
func main() {
var youtubeApiKey string
var soundcloudClientId string
var soundcloudClientSecret string
var webEnableImages bool
var debug bool
var useTLS bool
var server string
var password string
var timeout time.Duration
var pingFreq time.Duration
nickname := "YouTubeBot"
ident := "youtube"
var nickservPw string
channels := []string{}
// IRC config
kingpin.Flag("nick", "The nickname.").Short('n').StringVar(&nickname)
kingpin.Flag("ident", "The ident.").Short('i').StringVar(&ident)
kingpin.Flag("debug", "Enables debug mode.").Short('d').BoolVar(&debug)
kingpin.Flag("tls", "Use TLS.").BoolVar(&useTLS)
kingpin.Flag("server", "The server to connect to.").Short('s').StringVar(&server)
kingpin.Flag("password", "The password to use for logging into the IRC server.").Short('p').StringVar(&password)
kingpin.Flag("timeout", "The timeout on the connection.").Short('t').DurationVar(&timeout)
kingpin.Flag("pingfreq", "The ping frequency.").DurationVar(&pingFreq)
kingpin.Flag("nickserv-pw", "NickServ password.").StringVar(&nickservPw)
kingpin.Flag("channels", "Channels to join.").Short('c').StringsVar(&channels)
// Youtube config
kingpin.Flag("youtube-key", "The API key to use to access the YouTube API.").StringVar(&youtubeApiKey)
// SoundCloud config
kingpin.Flag("soundcloud-id", "The SoundCloud ID.").StringVar(&soundcloudClientId)
kingpin.Flag("soundcloud-secret", "The SoundCloud secret.").StringVar(&soundcloudClientSecret)
// Web parser config
kingpin.Flag("images", "Enables parsing links of images. Disabled by default for legal reasons.").BoolVar(&webEnableImages)
kingpin.Parse()
if len(nickname) == 0 {
log.Fatal("Nickname must be longer than 0 chars.")
}
if len(ident) == 0 {
log.Fatal("Ident must be longer than 0 chars.")
}
// Manager
m := manager.NewManager()
// Load youtube parser
if len(youtubeApiKey) > 0 {
youtubeParser := &youtube.Parser{
Config: &youtube.Config{ApiKey: youtubeApiKey},
}
must(m.RegisterParser(youtubeParser))
} else {
log.Println("No YouTube API key provided, YouTube parsing via API is disabled.")
}
// Load soundcloud parser
if len(soundcloudClientId) > 0 && len(soundcloudClientSecret) > 0 {
soundcloudParser := &soundcloud.Parser{
Config: &soundcloud.Config{
ClientId: soundcloudClientId,
ClientSecret: soundcloudClientSecret,
},
}
must(m.RegisterParser(soundcloudParser))
} else {
log.Println("No SoundCloud client ID or secret provided, SoundCloud parsing via API is disabled.")
}
// Load wikipedia parser
must(m.RegisterParser(new(wikipedia.Parser)))
// Load web parser
webParser := &web.Parser{
EnableImages: webEnableImages,
}
must(m.RegisterParser(webParser))
// IRC
conn := m.AntifloodIrcConn(irc.IRC(nickname, ident))
conn.Debug = debug
conn.VerboseCallbackHandler = conn.Debug
conn.UseTLS = useTLS
conn.Password = password
if timeout > time.Duration(0) {
conn.Timeout = timeout
}
if pingFreq > time.Duration(0) {
conn.PingFreq = pingFreq
}
joinChan := make(chan string)
inviteChan := make(chan string)
// register callbacks
conn.AddCallback("001", func(e *irc.Event) { // handle RPL_WELCOME
// nickserv login
if len(nickservPw) > 0 {
conn.Privmsg("NickServ", "IDENTIFY "+nickservPw)
log.Print("Sent NickServ login request.")
}
// I am a bot! (+B user mode)
conn.Mode(conn.GetNick(), "+B-iw")
// Join configured channels
if len(channels) > 0 {
conn.Join(strings.Join(channels, ","))
}
})
conn.AddCallback("JOIN", func(e *irc.Event) {
// Is this JOIN not about us?
if !strings.EqualFold(e.Nick, conn.GetNick()) {
// Save this user's details for a temporary ignore
m.NotifyUserJoined(e.Arguments[0], e.Source)
return
}
// Asynchronous notification
select {
case joinChan <- e.Arguments[0]:
default:
}
})
conn.AddCallback("INVITE", func(e *irc.Event) {
// Is this INVITE not for us?
if !strings.EqualFold(e.Arguments[0], conn.GetNick()) {
return
}
// Asynchronous notification
select {
case inviteChan <- e.Arguments[1]:
default:
}
// We have been invited, autojoin!
go func(sourceNick string, targetChannel string) {
joinWaitLoop:
for {
select {
case channel := <-joinChan:
if strings.EqualFold(channel, targetChannel) {
// TODO - Thanks message
time.Sleep(1 * time.Second)
conn.Privmsgf(targetChannel, "Thanks for inviting me, %s! I am %s, the friendly bot that shows information about links posted in this channel. I hope I can be of great help for everyone here in %s! :)", sourceNick, conn.GetNick(), targetChannel)
time.Sleep(2 * time.Second)
conn.Privmsg(targetChannel, "If you ever run into trouble with me (or find any bugs), please use the channel #MediaLink for contact on this IRC.")
break joinWaitLoop
}
case channel := <-inviteChan:
if strings.EqualFold(channel, targetChannel) {
break joinWaitLoop
}
case <-time.After(time.Minute):
log.Printf("WARNING: Timed out waiting for us to join %s as we got invited", targetChannel)
break joinWaitLoop
}
}
}(e.Nick, e.Arguments[1])
conn.Join(e.Arguments[1])
})
conn.AddCallback("PRIVMSG", func(e *irc.Event) {
go func(event *irc.Event) {
//sender := event.Nick
target := event.Arguments[0]
isChannel := true
if strings.EqualFold(target, conn.GetNick()) {
// Private message to us!
target = event.Nick
isChannel = false
}
if strings.EqualFold(target, conn.GetNick()) {
// Emergency switch to avoid endless loop,
// dropping all messages from the bot to the bot!
log.Printf("BUG - Emergency switch, caught message from bot to bot: %s", event.Arguments)
return
}
msg := stripIrcFormatting(event.Message())
log.Printf("<%s @ %s> %s", event.Nick, target, msg)
// Ignore user if they just joined
if shouldIgnore := m.TrackUser(target, event.Source); shouldIgnore {
log.Print("This message will be ignored since the user just joined.")
return
}
urlStr := xurls.Relaxed.FindString(msg)
switch {
case !isChannel:
// Explain who we are and what we do
conn.Privmsgf(target, "Hi, I parse links people post to chat rooms to give some information about them. I also allow people to search for YouTube videos and SoundCloud sounds straight from IRC. If you have questions or got any bug reports, please direct them to Icedream in #MediaLink, thank you!")
case len(urlStr) > 0: // URL?
// Parse URL!
u, err := url.ParseRequestURI(urlStr)
if err != nil {
u, err = url.ParseRequestURI("http://" + urlStr)
}
if err != nil {
log.Print(err)
break
}
// Check if this URL has been recently parsed before (antiflood)
shouldIgnore := m.TrackUrl(target, u)
if shouldIgnore {
log.Printf("WARNING: URL antiflood triggered, dropping URL for %s: %s", target, u)
break
}
_, result := m.Parse(u)
if result.Error != nil {
log.Print(result.Error)
}
if result.UserError != nil {
if s, err := tplString("error", result.UserError); err != nil {
log.Print(err)
} else {
conn.Privmsg(target, s)
}
}
if result.Error == nil && result.UserError == nil && result.Information != nil {
for _, i := range result.Information {
if s, err := tplString("link-info", i); err != nil {
log.Print(err)
} else {
conn.Privmsg(target, s)
}
}
}
}
}(e)
})
// connect
must(conn.Connect(server))
// listen for errors
log.Print("Now looping.")
conn.Loop()
}