// Init initializes the plugin. it loads configuration data and binds // commands and protocol handlers. func (p *Plugin) Load(c *proto.Client) (err error) { err = p.Base.Load(c) if err != nil { return } c.Bind(proto.CmdPrivMsg, func(c *proto.Client, m *proto.Message) { p.parseSexpr(c, m) }) ini := p.LoadConfig() if ini == nil { return } s := ini.Section("exclude") list := s.List("url") p.exclude = make([]*regexp.Regexp, len(list)) for i := range list { p.exclude[i], err = regexp.Compile(list[i]) if err != nil { return } } return }
func fetchGithubRepo(c *proto.Client, m *proto.Message, url string) { matches := githubUrlRegex.FindStringSubmatch(url) if len(matches) != 4 { log.Printf("Expected %d Github matches and found %d", 4, len(matches)) return } apiUrl := fmt.Sprintf("https://api.github.com/repos/%s/%s", matches[2], matches[3]) resp, err := http.Get(apiUrl) if err != nil { log.Printf("Error querying: %s, %s", apiUrl, err) return } bts, err := ioutil.ReadAll(resp.Body) if err != nil { log.Printf("ERROR reading response to %s: %s", apiUrl, err) return } var result Repository err = json.Unmarshal(bts, &result) if err != nil { log.Printf("ERROR unmarshalling response to %s: %s", apiUrl, err) return } c.PrivMsg(m.Receiver, "%s's respository is: %s", m.SenderName, result.Description) }
// fetchTitle attempts to retrieve the title element for a given url. func fetchTitle(c *proto.Client, m *proto.Message, url string) { resp, err := http.Get(url) if err != nil { return } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { return } body = bytes.ToLower(body) s := bytes.Index(body, []byte("<title>")) if s == -1 { return } body = body[s+7:] e := bytes.Index(body, []byte("</title>")) if e == -1 { e = len(body) - 1 } body = bytes.TrimSpace(body[:e]) c.PrivMsg(m.Receiver, "%s's link shows: %s", m.SenderName, html.UnescapeString(string(body))) }
// bind binds protocol message handlers. func bind(c *proto.Client) { c.Bind(proto.Unknown, onAny) c.Bind(proto.CmdPing, onPing) c.Bind(proto.EndOfMOTD, onJoinChannels) c.Bind(proto.ErrNoMOTD, onJoinChannels) c.Bind(proto.ErrNicknameInUse, onNickInUse) c.Bind(proto.CmdPrivMsg, onPrivMsg) }
// shutdown cleans up our mess. func shutdown(conn *net.Conn, client *proto.Client) { plugin.Unload(client) log.Printf("Shutting down.") client.Quit(config.QuitMessage) client.Close() conn.Close() }
// ctcpVersion handles a CTCP version request. func ctcpVersion(c *proto.Client, m *proto.Message) bool { if m.Data != "\x01VERSION\x01" { return false } c.PrivMsg(m.SenderName, "%s %d.%d", AppName, AppVersionMajor, AppVersionMinor) return true }
// ctcpPing handles a CTCP ping request. func ctcpPing(c *proto.Client, m *proto.Message) bool { if !strings.HasPrefix(m.Data, "\x01PING ") { return false } c.PrivMsg(m.SenderName, "\x01PONG %s\x01", m.Data[6:]) return true }
// onNickInUse is called whenever we receive a notification that our // nickname is already in use. We will attempt to re-acquire it by // identifying with our password. Otherwise we will pick a new name. func onNickInUse(c *proto.Client, m *proto.Message) { if len(config.NickservPassword) > 0 { c.Recover(config.Nickname, config.NickservPassword) return } config.SetNickname(config.Nickname + "_") c.Nick(config.Nickname, "") }
func listBotReputation(c *proto.Client, m *proto.Message) { resp, err := red.Do("ZRANGE", "reputation", "0", "4", "WITHSCORES") if err != nil { log.Print(err) return } c.PrivMsg(m.Receiver, "Bottom Reputations:") printResponseReputationList(c, m, resp) }
// parseURL looks for descriptions in incoming messages. func (p *Plugin) parseDescription(c *proto.Client, m *proto.Message) { list := p.description.FindStringSubmatch(m.Data) if len(list) == 0 { return } description := list[len(list)-1] log.Printf("Found description: %s", description) c.PrivMsg(m.Receiver, fmt.Sprintf("%s %s is %s", otherBotUsername, m.SenderName, description)) }
func decrementReputation(c *proto.Client, m *proto.Message, entity string) { log.Printf("decrementing %s", entity) rep, err := red.Do("ZINCRBY", "reputation", "-1", entity) if err != nil { log.Print(err) return } c.PrivMsg(m.Receiver, "%s lost 1 rep! rep: %s", entity, string(rep.([]byte))) }
func checkReputation(c *proto.Client, m *proto.Message, entity string) { log.Printf("checking %s", entity) rep, err := red.Do("ZSCORE", "reputation", entity) if err != nil { log.Print(err) return } if rep == nil { c.PrivMsg(m.Receiver, "never heard of %s", entity) return } c.PrivMsg(m.Receiver, "%s has rep: %s", entity, string(rep.([]byte))) }
func printResponseReputationList(c *proto.Client, m *proto.Message, resp interface{}) { values, err := redis.Values(resp, nil) if err != nil { log.Print(err) return } var reps []struct { Name string Score int } err = redis.ScanSlice(values, &reps) for i, rep := range reps { c.PrivMsg(m.Receiver, "(%d) %-10s: %3d", i, rep.Name, rep.Score) } }
// fetchTweet attempts to retrieve the tweet associated with a given url. func fetchTweet(c *proto.Client, m *proto.Message, url string) { id, err := strconv.ParseInt(twitterUrlRegex.FindStringSubmatch(url)[2], 10, 64) if err != nil { c.PrivMsg(m.Receiver, "error parsing tweet :(") log.Print("error parsing tweet for %s: %v", url, err) fetchTitle(c, m, url) return } tweet, err := api.GetTweet(id, nil) if err != nil { log.Print("error parsing tweet for %s: %v", url, err) fetchTitle(c, m, url) return } c.PrivMsg(m.Receiver, "%s's tweet shows: %s", m.SenderName, html.UnescapeString(tweet.Text)) }
func (p *Plugin) Load(c *proto.Client) (err error) { err = p.Base.Load(c) if err != nil { return } w := new(cmd.Command) w.Name = "define" w.Description = "Fetch the definition for the given term" w.Restricted = false w.Params = []cmd.Param{ {Name: "term", Description: "Word to find definition for", Pattern: cmd.RegAny}, } w.Execute = func(cmd *cmd.Command, c *proto.Client, m *proto.Message) { dict, err := Dial("tcp", "dict.org:2628") if err != nil { log.Printf("[dict] %s", err) c.PrivMsg(m.Receiver, "%s, No definition found for '%s'", m.SenderName, cmd.Params[0].Value) return } def, err := dict.Define("wn", cmd.Params[0].Value) if err != nil { log.Printf("[dict] %s", err) c.PrivMsg(m.Receiver, "%s, No definition found for '%s'", m.SenderName, cmd.Params[0].Value) return } if len(def) == 0 { c.PrivMsg(m.Receiver, "%s, No definition found for '%s'", m.SenderName, cmd.Params[0].Value) return } space := []byte{' '} mspace := []byte{' ', ' '} line := bytes.Replace(def[0].Text, []byte{'\n'}, space, -1) // Strip all multi-space indents. for bytes.Index(line, mspace) > -1 { line = bytes.Replace(line, mspace, space, -1) } c.PrivMsg(m.Receiver, "%s: %s", m.SenderName, line) } cmd.Register(w) return }
// fetchTitle attempts to retrieve the title element for a given url. func whoIs(c *proto.Client, m *proto.Message, match []string) { log.Printf("Whois: %+v", match) entity := strings.ToLower(match[3]) action := match[2] response := "no idea who or what that is" switch action { case "whois": descriptor_b, err := red.Do("GET", entity+WHOIS_SUFFIX) if err != nil { log.Print(err) break } // Will be nil if not yet in the database if descriptor_b == nil { break } descriptor, ok := descriptor_b.([]byte) if !ok { fmt.Printf("ERROR: not a byte slice type: %+v", descriptor_b) break } response = fmt.Sprintf("%s is %s", entity, string(descriptor)+"...") default: if match[4] != "is" { log.Printf("action %s not supported", match[3]) return } entity = match[5] descriptor := match[6] _, err := red.Do("APPEND", entity+WHOIS_SUFFIX, descriptor+", ") if err != nil { log.Print(err) return } response = fmt.Sprintf("%s is %s", entity, descriptor) } c.PrivMsg(m.Receiver, response) }
// Parse reads incoming message data and tries to parse it into // a command structure and then execute it. func Parse(prefix string, c *proto.Client, m *proto.Message) bool { prefixlen := len(prefix) if prefixlen == 0 || !strings.HasPrefix(m.Data, prefix) { return false } // Split the data into a name and list of parameters. name, params := parseCommand(m.Data[prefixlen:]) if len(name) == 0 { return false } // Ensure the given command exists. cmd := findCommand(name) if cmd == nil { return false } cmd.Data = strings.TrimSpace(m.Data[prefixlen+len(name):]) // Ensure the current user us allowed to execute the command. if cmd.Restricted && !isWhitelisted(m.SenderMask) { c.PrivMsg(m.SenderName, "Access to %q denied.", name) return false } // Make sure we received enough parameters. pc := cmd.RequiredParamCount() lp := len(params) if pc > lp { c.PrivMsg(m.SenderName, "Missing parameters for command %q", name) return false } // Copy over parameter values and ensure they are of the right format. for i := 0; i < lp && i < len(cmd.Params); i++ { cmd.Params[i].Value = params[i] if !cmd.Params[i].Valid() { c.PrivMsg(m.SenderName, "Invalid parameter value %q for command %q", params[i], name) return false } } // Execute the command. if cmd.Execute != nil { go cmd.Execute(cmd, c, m) } return true }
func (p *Plugin) Load(c *proto.Client) (err error) { err = p.Base.Load(c) if err != nil { return } ini := p.LoadConfig() if ini == nil { log.Fatalf("[ipintel] No configuration found.") return } key := ini.Section("api").S("key", "") if len(key) == 0 { log.Fatalf("[ipintel] No API key found.") return } shared := ini.Section("api").S("shared", "") if len(shared) == 0 { log.Fatalf("[ipintel] No API shared secret found.") return } drift := ini.Section("api").I64("drift", 0) if len(shared) == 0 { log.Fatalf("[ipintel] No API shared secret found.") return } w := new(cmd.Command) w.Name = "loc" w.Description = "Fetch geo-location data for the given IP address." w.Restricted = false w.Params = []cmd.Param{ {Name: "ip", Description: "IPv4 address to look up", Pattern: cmd.RegIPv4}, } w.Execute = func(cmd *cmd.Command, c *proto.Client, m *proto.Message) { hash := md5.New() stamp := fmt.Sprintf("%d", time.Now().UTC().Unix()+drift) io.WriteString(hash, key+shared+stamp) sig := fmt.Sprintf("%x", hash.Sum(nil)) target := fmt.Sprintf(url, cmd.Params[0].Value, key, sig) resp, err := http.Get(target) if err != nil { log.Printf("[ipintel]: %v", err) return } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { log.Printf("[ipintel]: %v", err) return } var data Response err = json.Unmarshal(body, &data) if err != nil { log.Printf("[ipintel]: %v", err) return } inf := data.IPInfo mapsURL := fmt.Sprintf("https://maps.google.com/maps?q=%f,%f", inf.Location.Latitude, inf.Location.Longitude) c.PrivMsg(m.Receiver, "%s: %s (%s), Network org.: %s, Carrier: %s, TLD: %s, SLD: %s. "+ "Location: %s/%s/%s/%s (%f, %f). Postalcode: %s, Timezone: %d, %s", m.SenderName, inf.IPAddress, inf.IPType, inf.Network.Organization, inf.Network.Carrier, inf.Network.Domain.TLD, inf.Network.Domain.SLD, inf.Location.Continent, inf.Location.CountryData.Name, inf.Location.StateData.Name, inf.Location.CityData.Name, inf.Location.Latitude, inf.Location.Longitude, inf.Location.CityData.PostalCode, inf.Location.CityData.TimeZone, mapsURL, ) } cmd.Register(w) w = new(cmd.Command) w.Name = "mibbit" w.Description = "Resolve a mibbit address to a real IP address." w.Restricted = false w.Params = []cmd.Param{ {Name: "hex", Description: "Mibbit hex string", Pattern: regMibbit}, } w.Execute = func(cmd *cmd.Command, c *proto.Client, m *proto.Message) { hex := cmd.Params[0].Value var ip [4]uint64 var err error ip[0], err = strconv.ParseUint(hex[:2], 16, 8) if err != nil { c.PrivMsg(m.Receiver, "%s: invalid mibbit address.", m.SenderName) return } ip[1], err = strconv.ParseUint(hex[2:4], 16, 8) if err != nil { c.PrivMsg(m.Receiver, "%s: invalid mibbit address.", m.SenderName) return } ip[2], err = strconv.ParseUint(hex[4:6], 16, 8) if err != nil { c.PrivMsg(m.Receiver, "%s: invalid mibbit address.", m.SenderName) return } ip[3], err = strconv.ParseUint(hex[6:], 16, 8) if err != nil { c.PrivMsg(m.Receiver, "%s: invalid mibbit address.", m.SenderName) return } address := fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) names, err := net.LookupAddr(address) if err != nil || len(names) == 0 { c.PrivMsg(m.Receiver, "%s: %s is %s", m.SenderName, hex, address) } else { c.PrivMsg(m.Receiver, "%s: %s is %s / %s", m.SenderName, hex, address, names[0]) } } cmd.Register(w) return }
// onJoinChannels is used to complete the login procedure. // We have just received the server's MOTD and now is a good time to // start joining channels. func onJoinChannels(c *proto.Client, m *proto.Message) { c.Join(config.Channels...) }
// onPing handles PING messages. func onPing(c *proto.Client, m *proto.Message) { c.Pong(m.Data) }
func (p *Plugin) Load(c *proto.Client) (err error) { err = p.Base.Load(c) if err != nil { return } comm := new(cmd.Command) comm.Name = "quit" comm.Description = "Unconditionally quit the bot program" comm.Restricted = true comm.Execute = func(cmd *cmd.Command, c *proto.Client, m *proto.Message) { c.Quit("") } cmd.Register(comm) comm = new(cmd.Command) comm.Name = "join" comm.Description = "Join the given channel" comm.Restricted = true comm.Params = []cmd.Param{ {Name: "channel", Optional: false, Pattern: cmd.RegChannel}, {Name: "key", Optional: true, Pattern: cmd.RegAny}, {Name: "chanservpass", Optional: true, Pattern: cmd.RegAny}, } comm.Execute = func(cmd *cmd.Command, c *proto.Client, m *proto.Message) { var ch irc.Channel ch.Name = cmd.Params[0].Value if len(cmd.Params) > 1 { ch.Key = cmd.Params[1].Value } if len(cmd.Params) > 2 { ch.ChanservPassword = cmd.Params[2].Value } c.Join(&ch) } cmd.Register(comm) comm = new(cmd.Command) comm.Name = "leave" comm.Description = "Leave the given channel" comm.Restricted = true comm.Params = []cmd.Param{ {Name: "channel", Optional: true, Pattern: cmd.RegChannel}, } comm.Execute = func(cmd *cmd.Command, c *proto.Client, m *proto.Message) { var ch irc.Channel if len(cmd.Params) > 0 { ch.Name = cmd.Params[0].Value } else { if !m.FromChannel() { return } ch.Name = m.Receiver } c.Part(&ch) } cmd.Register(comm) return }
func (p *Plugin) Load(c *proto.Client) (err error) { err = p.Base.Load(c) if err != nil { return } ini := p.LoadConfig() if ini == nil { log.Fatalf("[weather] No API key found.") return } key := ini.Section("api").S("key", "") if len(key) == 0 { log.Fatalf("[weather] No API key found.") return } w := new(cmd.Command) w.Name = "weather" w.Description = "Fetch the current weather for a given location" w.Restricted = false w.Params = []cmd.Param{ {Name: "location", Description: "Name of the city/town for the forecast", Pattern: cmd.RegAny}, } w.Execute = func(cmd *cmd.Command, c *proto.Client, m *proto.Message) { location := url.QueryEscape(cmd.Params[0].Value) resp, err := http.Get(fmt.Sprintf(_url, location, key)) if err != nil { return } data, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { return } var wd WeatherData err = json.Unmarshal(data, &wd) if err != nil { return } if len(wd.Data.Request) == 0 || len(wd.Data.Conditions) == 0 { c.PrivMsg(m.Receiver, "%s: No weather data for %q", m.SenderName, cmd.Params[0].Value) return } wr := wd.Data.Request[0] wc := wd.Data.Conditions[0] c.PrivMsg(m.Receiver, "%s, weather in %s: %s°C/%s°F/%.2f°K, %s, cloud cover: %s%%, humidity: %s%%, wind: %skph/%smph from %s, pressure: %s mb, visibility: %s km", m.SenderName, wr.Query, wc.TempC, wc.TempF, wd.TempK(), codeName(wc.WeatherCode), wc.CloudCover, wc.Humidity, wc.WindSpeedKmph, wc.WindSpeedMiles, wc.WindDir16Point, wc.Pressure, wc.Visibility, ) } cmd.Register(w) return }