123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- // This file is subject to a 1-clause BSD license.
- // Its contents can be found in the enclosed LICENSE file.
- // Package admin defines administrative bot commands. This package is
- // also used for the initial IRC login and channel joins.
- package admin
- import (
- "encoding/json"
- "log"
- "math"
- "os"
- "strconv"
- "strings"
- "syscall"
- "time"
- "notabug.org/mouz/bot/app"
- "notabug.org/mouz/bot/app/util"
- "notabug.org/mouz/bot/irc"
- "notabug.org/mouz/bot/irc/cmd"
- "notabug.org/mouz/bot/irc/proto"
- "notabug.org/mouz/bot/plugins"
- )
- // lastRestart defines the timestamp at which the bot was last restarted.
- var lastRestart = time.Now()
- func init() { plugins.Register(&plugin{}) }
- type plugin struct {
- cmd *cmd.Set
- // This will store the bot's profile, but only as a subset of
- // the full interface. We only need access to some parts.
- profile interface {
- WhitelistAdd(string)
- WhitelistRemove(string)
- Whitelist() []string
- Nickname() string
- SetNickname(string)
- NickservPassword() string
- SetNickservPassword(string)
- Channels() []irc.Channel
- }
- }
- // Load initializes the module and loads any internal resources
- // which may be required.
- func (p *plugin) Load(prof irc.Profile) error {
- p.profile = prof
- p.cmd = cmd.New(
- prof.CommandPrefix(),
- prof.IsWhitelisted,
- )
- p.cmd.Bind(TextFloodName, false, p.cmdFlood).
- Add(TextFloodCount, true, cmd.RegAny)
- // Two aliases for the same command. Can be invoked through
- // !help or !<bot nickname>
- p.cmd.Bind(TextHelpName, false, p.cmdHelp)
- p.cmd.Bind(prof.Nickname(), false, p.cmdHelp)
- p.cmd.Bind(TextNickName, true, p.cmdNick).
- Add(TextNickNickName, true, cmd.RegAny).
- Add(TextNickPassName, false, cmd.RegAny)
- p.cmd.Bind(TextJoinName, true, p.cmdJoin).
- Add(TextJoinChannelName, true, cmd.RegChannel).
- Add(TextJoinKeyName, false, cmd.RegAny)
- p.cmd.Bind(TextPartName, true, p.cmdPart).
- Add(TextPartChannelName, true, cmd.RegChannel)
- p.cmd.Bind(TextNoopName, true, p.cmdNoop).
- Add(TextNoopChannelName, false, cmd.RegChannel)
- p.cmd.Bind(TextAuthListName, false, p.cmdAuthList)
- p.cmd.Bind(TextAuthorizeName, true, p.cmdAuthorize).
- Add(TextAuthorizeMaskName, true, cmd.RegAny)
- p.cmd.Bind(TextDeauthorizeName, true, p.cmdDeauthorize).
- Add(TextDeauthorizeMaskName, true, cmd.RegAny)
- p.cmd.Bind(TextReloadName, true, p.cmdReload)
- p.cmd.Bind(TextVersionName, false, p.cmdVersion)
- return nil
- }
- // Unload cleans the module up and unloads any internal resources.
- func (p *plugin) Unload(prof irc.Profile) error {
- p.profile = nil
- return nil
- }
- // Dispatch sends the given, incoming IRC message to the plugin for
- // processing as it sees fit.
- func (p *plugin) Dispatch(w irc.ResponseWriter, r *irc.Request) {
- switch r.Type {
- case "375", "422": // received START_MOTD or NO_MOTD
- p.onFinalizeLogin(w, r)
- case "433":
- p.onNickInUse(w, r)
- case "PRIVMSG":
- p.cmd.Dispatch(w, r)
- }
- }
- // onFinalizeLogin is called to complete the login sequence. It is
- // triggered when we receive either the STARTMOTD or NOMOTD messages.
- // It identifies to nickserv and joins channels defined in the
- // profile.
- func (p *plugin) onFinalizeLogin(w irc.ResponseWriter, r *irc.Request) {
- if len(p.profile.NickservPassword()) > 0 {
- proto.PrivMsg(w, "nickserv", "IDENTIFY %s", p.profile.NickservPassword())
- }
- proto.Join(w, p.profile.Channels()...)
- }
- // onNickInUse signals that our nick is in use. If we can regain it, do so.
- // Otherwise, change ours.
- func (p *plugin) onNickInUse(w irc.ResponseWriter, r *irc.Request) {
- pr := p.profile
- if len(pr.NickservPassword()) > 0 {
- log.Println("[admin] Nick in use: trying to recover")
- proto.Recover(w, pr.Nickname(), pr.NickservPassword())
- return
- }
- pr.SetNickname(pr.Nickname() + "_")
- log.Println("[admin] Nick in use: changing nick to:", pr.Nickname())
- proto.Nick(w, pr.Nickname())
- }
- // cmdFlood
- func (p *plugin) cmdFlood(w irc.ResponseWriter, r *irc.Request, params cmd.ParamList) {
- j, _ := json.MarshalIndent(r, "", " ")
- log.Println("[admin] cmdFlood() was called. Request:\n" + string(j))
- count := params.Uint(0)
- var i uint64
- for i = 0; i < count; i++ {
- proto.PrivMsg(w, r.Target, TextFloodDisplay, r.SenderName, i)
- }
- }
- // cmdHelp presents the user with a list of commands, also pointing them to
- // a resource where the full bot help can be read.
- func (p *plugin) cmdHelp(w irc.ResponseWriter, r *irc.Request, params cmd.ParamList) {
- proto.PrivMsg(w, r.SenderName, TextHelpAll)
- for i := 1; i <= len(CommandsHelp); i++ {
- proto.PrivMsg(w, r.SenderName, "%s", CommandsHelp[i])
- }
- proto.PrivMsg(w, r.SenderName, TextHelpAdmins)
- }
- // cmdNick allows the bot to change its name.
- func (p *plugin) cmdNick(w irc.ResponseWriter, r *irc.Request, params cmd.ParamList) {
- p.profile.SetNickname(params.String(0))
- if params.Len() > 1 {
- proto.Nick(w, params.String(0), params.String(1))
- p.profile.SetNickservPassword(params.String(1))
- } else {
- proto.Nick(w, params.String(0))
- }
- }
- // cmdJoin makes the bot join a new channel.
- func (p *plugin) cmdJoin(w irc.ResponseWriter, r *irc.Request, params cmd.ParamList) {
- var channel irc.Channel
- channel.Name = params.String(0)
- if params.Len() > 1 {
- channel.Key = params.String(1)
- }
- proto.Join(w, channel)
- }
- // cmdPart makes the bot leave a given channel.
- func (p *plugin) cmdPart(w irc.ResponseWriter, r *irc.Request, params cmd.ParamList) {
- proto.Part(w, irc.Channel{
- Name: params.String(0),
- })
- }
- // cmdNoop makes the bot de-op itself.
- func (p *plugin) cmdNoop(w irc.ResponseWriter, r *irc.Request, params cmd.ParamList) {
- var channel_name string
- if params.Len() > 0 {
- channel_name = params.String(0)
- } else {
- if r.FromChannel() {
- channel_name = r.Target
- } else {
- // ugly?
- proto.PrivMsg(w, r.SenderName, cmd.TextMissingParameters, r.Data[1:])
- return
- }
- }
- proto.Mode(w, channel_name, "-o", p.profile.Nickname())
- }
- // cmdAuthList lists all whitelisted users.
- func (p *plugin) cmdAuthList(w irc.ResponseWriter, r *irc.Request, params cmd.ParamList) {
- list := p.profile.Whitelist()
- out := strings.Join(list, ", ")
- proto.PrivMsg(w, r.SenderName, TextAuthListDisplay, out)
- }
- // cmdAuthorize adds a new whitelisted user.
- func (p *plugin) cmdAuthorize(w irc.ResponseWriter, r *irc.Request, params cmd.ParamList) {
- p.profile.WhitelistAdd(params.String(0))
- proto.PrivMsg(w, r.SenderName, TextAuthorizeDisplay, params.String(0))
- }
- // cmdDeauthorize removes a user from the whitelist.
- func (p *plugin) cmdDeauthorize(w irc.ResponseWriter, r *irc.Request, params cmd.ParamList) {
- p.profile.WhitelistRemove(params.String(0))
- proto.PrivMsg(w, r.SenderName, TextDeauthorizeDisplay, params.String(0))
- }
- // cmdReload forces the bot to fork itself. This is achieved by
- // sending SIGUSR1 to the current process.
- func (p *plugin) cmdReload(w irc.ResponseWriter, r *irc.Request, params cmd.ParamList) {
- syscall.Kill(os.Getpid(), syscall.SIGUSR1)
- }
- // cmdVersion prints version information.
- func (p *plugin) cmdVersion(w irc.ResponseWriter, r *irc.Request, params cmd.ParamList) {
- rev, _ := strconv.ParseInt(app.VersionRevision, 10, 64)
- stamp := time.Unix(rev, 0)
- hours := math.Abs(time.Since(lastRestart).Hours())
- days := hours / 24
- udays := math.Floor(days)
- uhours := 24 * (days - udays)
- uminutes := 60 * (uhours - math.Floor(uhours))
- useconds := 60 * (uminutes - math.Floor(uminutes))
- proto.PrivMsg(
- w, r.Target,
- TextVersionDisplay,
- r.SenderName,
- util.Bold(p.profile.Nickname()),
- util.Bold("%d.%d", app.VersionMajor, app.VersionMinor),
- stamp.Format(TextDateFormat),
- stamp.Format(TextTimeFormat),
- udays,
- uhours,
- uminutes,
- useconds,
- )
- }
|