152 lines
4.0 KiB
Go
152 lines
4.0 KiB
Go
package hbot
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// Action sends an action to 'who' (user or channel)
|
|
func (bot *Bot) Action(who, text string) {
|
|
msg := fmt.Sprintf("\u0001ACTION %s\u0001", text)
|
|
bot.Msg(who, msg)
|
|
}
|
|
|
|
// ChMode is used to change users modes in a channel
|
|
// operator = "+o" deop = "-o"
|
|
// ban = "+b"
|
|
func (bot *Bot) ChMode(user, channel, mode string) {
|
|
bot.Send("MODE " + channel + " " + mode + " " + user)
|
|
}
|
|
|
|
// Join a channel
|
|
func (bot *Bot) Join(ch string) {
|
|
bot.Send("JOIN " + ch)
|
|
}
|
|
|
|
// Msg sends a message to 'who' (user or channel)
|
|
func (bot *Bot) Msg(who, text string) {
|
|
const command = "PRIVMSG"
|
|
for _, line := range bot.splitText(text, command, who) {
|
|
bot.Send(command + " " + who + " :" + line)
|
|
}
|
|
}
|
|
|
|
// MsgMaxSize returns maximum number of bytes that fit into one message.
|
|
// Useful, for example, if you want to generate a wall of emojis that fit into one message,
|
|
// or you want to cap some output to one message
|
|
func (bot *Bot) MsgMaxSize(who string) int {
|
|
const command = "PRIVMSG"
|
|
maxSize := bot.maxMsgSize(command, who)
|
|
return maxSize
|
|
}
|
|
|
|
// Notice sends a NOTICE message to 'who' (user or channel)
|
|
func (bot *Bot) Notice(who, text string) {
|
|
const command = "NOTICE"
|
|
for _, line := range bot.splitText(text, command, who) {
|
|
bot.Send(command + " " + who + " :" + line)
|
|
}
|
|
}
|
|
|
|
// NoticeMaxSize returns maximum number of bytes that fit into one message.
|
|
// Useful, for example, if you want to generate a wall of emojis that fit into one message,
|
|
// or you want to cap some output to one message
|
|
func (bot *Bot) NoticeMaxSize(who string) int {
|
|
const command = "NOTICE"
|
|
maxSize := bot.maxMsgSize(command, who)
|
|
return maxSize
|
|
}
|
|
|
|
// Part a channel
|
|
func (bot *Bot) Part(ch, msg string) {
|
|
bot.Send("PART " + ch + " " + msg)
|
|
}
|
|
|
|
// Reply sends a message to where the message came from (user or channel)
|
|
func (bot *Bot) Reply(m *Message, text string) {
|
|
bot.Msg(replyTarget(m), text)
|
|
}
|
|
|
|
// ReplyMaxSize is just like MsgMaxSize
|
|
// but calculates message size for the reply target
|
|
func (bot *Bot) ReplyMaxSize(m *Message) int {
|
|
const command = "PRIVMSG"
|
|
maxSize := bot.maxMsgSize(command, replyTarget(m))
|
|
return maxSize
|
|
}
|
|
|
|
// Send any command to the server
|
|
func (bot *Bot) Send(command string) {
|
|
bot.outgoing <- command
|
|
}
|
|
|
|
// SetNick sets the bots nick on the irc server.
|
|
// This does not alter *Bot.Nick, so be vary of that
|
|
func (bot *Bot) SetNick(nick string) {
|
|
bot.Send(fmt.Sprintf("NICK %s", nick))
|
|
}
|
|
|
|
// Topic sets the channel 'c' topic (requires bot has proper permissions)
|
|
func (bot *Bot) Topic(c, topic string) {
|
|
str := fmt.Sprintf("TOPIC %s :%s", c, topic)
|
|
bot.Send(str)
|
|
}
|
|
|
|
func (bot *Bot) maxMsgSize(command, who string) int {
|
|
// Maximum message size that fits into 512 bytes.
|
|
// Carriage return and linefeed are not counted here as they
|
|
// are added by handleOutgoingMessages()
|
|
maxSize := 510 - len(fmt.Sprintf(":%s %s %s :", bot.Prefix(), command, who))
|
|
if _, present := bot.CapStatus(CapIdentifyMsg); present {
|
|
maxSize--
|
|
}
|
|
// https://ircv3.net/specs/extensions/multiline
|
|
if bot.config.MsgSafetyBuffer {
|
|
maxSize -= 10
|
|
}
|
|
return maxSize
|
|
}
|
|
|
|
func replyTarget(m *Message) string {
|
|
if to := m.Param(0); strings.Contains(to, "#") {
|
|
return to
|
|
}
|
|
return m.Prefix.Name
|
|
}
|
|
|
|
// Splits a given string into a string slice, in chunks ending
|
|
// either with \n, or with \r\n, or splitting text to maximally allowed size.
|
|
func (bot *Bot) splitText(text, command, who string) []string {
|
|
var ret []string
|
|
|
|
// Sanitize input
|
|
text = strings.ToValidUTF8(text, "")
|
|
|
|
maxSize := bot.maxMsgSize(command, who)
|
|
|
|
scanner := bufio.NewScanner(strings.NewReader(text))
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
for len(line) > maxSize {
|
|
totalSize := 0
|
|
runeSize := 0
|
|
// utf-8 aware splitting
|
|
for _, v := range line {
|
|
runeSize = utf8.RuneLen(v)
|
|
if totalSize+runeSize > maxSize {
|
|
ret = append(ret, line[:totalSize])
|
|
line = line[totalSize:]
|
|
totalSize = runeSize
|
|
continue
|
|
}
|
|
totalSize += runeSize
|
|
}
|
|
|
|
}
|
|
ret = append(ret, line)
|
|
}
|
|
return ret
|
|
}
|