forked from External/ergo
import changes
This commit is contained in:
parent
30f47a9b22
commit
ca62b268b0
16 changed files with 272 additions and 34 deletions
|
|
@ -369,7 +369,7 @@ server:
|
|||
# in a "closed-loop" system where you control the server and all the clients,
|
||||
# you may want to increase the maximum (non-tag) length of an IRC line from
|
||||
# the default value of 512. DO NOT change this on a public server:
|
||||
#max-line-len: 512
|
||||
max-line-len: 2048
|
||||
|
||||
# send all 0's as the LUSERS (user counts) output to non-operators; potentially useful
|
||||
# if you don't want to publicize how popular the server is
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ package caps
|
|||
|
||||
const (
|
||||
// number of recognized capabilities:
|
||||
numCapabs = 34
|
||||
numCapabs = 35
|
||||
// length of the uint32 array that represents the bitset:
|
||||
bitsetLen = 2
|
||||
)
|
||||
|
|
@ -148,6 +148,8 @@ const (
|
|||
// ZNCSelfMessage is the ZNC vendor capability named "znc.in/self-message":
|
||||
// https://wiki.znc.in/Query_buffers
|
||||
ZNCSelfMessage Capability = iota
|
||||
|
||||
ExtendedNames Capability = iota
|
||||
)
|
||||
|
||||
// `capabilityNames[capab]` is the string name of the capability `capab`
|
||||
|
|
@ -187,5 +189,6 @@ var (
|
|||
"userhost-in-names",
|
||||
"znc.in/playback",
|
||||
"znc.in/self-message",
|
||||
"cef/extended-names",
|
||||
}
|
||||
)
|
||||
|
|
|
|||
124
irc/cef.go
Normal file
124
irc/cef.go
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
package irc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/ergochat/ergo/irc/caps"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const PORT = "127.0.0.1:22843"
|
||||
|
||||
type CefConnection struct {
|
||||
connections map[string]net.Conn
|
||||
server *Server
|
||||
}
|
||||
|
||||
func (instance *CefConnection) CEFMessage(action string, extra ...string) {
|
||||
if instance == nil {
|
||||
return
|
||||
}
|
||||
str := fmt.Sprintf("%s %s\n", action, strings.Join(extra, " "))
|
||||
for _, conn := range instance.connections {
|
||||
conn.Write([]byte(str))
|
||||
}
|
||||
}
|
||||
|
||||
func (instance *CefConnection) KickBroadcast(channel string, user string) {
|
||||
instance.CEFMessage("KICK", channel, user)
|
||||
}
|
||||
|
||||
func cefConnection(server *Server) *CefConnection {
|
||||
// create a tcp listener on the given port
|
||||
listener, err := net.Listen("tcp", PORT)
|
||||
if err != nil {
|
||||
fmt.Println("Unable to open CefConnection listener:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("CefConnection listener on %s active\n", PORT)
|
||||
instance := CefConnection{connections: make(map[string]net.Conn), server: server}
|
||||
go cefListener(listener, &instance)
|
||||
|
||||
return &instance
|
||||
}
|
||||
|
||||
func cefListener(listener net.Listener, instance *CefConnection) {
|
||||
// listen for new connections
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
fmt.Println("failed to accept cef connection, err:", err)
|
||||
continue
|
||||
}
|
||||
instance.connections[conn.RemoteAddr().String()] = conn
|
||||
|
||||
// pass an accepted connection to a handler goroutine
|
||||
go handleConnection(conn, instance)
|
||||
}
|
||||
}
|
||||
|
||||
func handleConnection(conn net.Conn, instance *CefConnection) {
|
||||
defer delete(instance.connections, conn.RemoteAddr().String())
|
||||
reader := bufio.NewReader(conn)
|
||||
println("Connection with CEF service established")
|
||||
for {
|
||||
// read client request data
|
||||
bytes, err := reader.ReadBytes(byte('\n'))
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
fmt.Println("failed to read data, err:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
convertedLine := string(bytes[:len(bytes)-1])
|
||||
line := strings.Split(strings.Trim(convertedLine, " \n"), " ")
|
||||
fmt.Printf("cef: %+q\n", line)
|
||||
switch line[0] {
|
||||
case "PART":
|
||||
if len(line) == 1 || len(line[1]) == 0 {
|
||||
println("skipping malformed line")
|
||||
continue
|
||||
}
|
||||
channel := instance.server.channels.Get(line[1])
|
||||
for _, member := range channel.Members() {
|
||||
for _, session := range member.Sessions() {
|
||||
session.Send(nil, member.server.name, "VOICEPART", line[1], line[2])
|
||||
}
|
||||
}
|
||||
break
|
||||
case "VOICESTATE":
|
||||
channel := instance.server.channels.Get(line[1])
|
||||
for _, member := range channel.Members() {
|
||||
for _, session := range member.Sessions() {
|
||||
session.Send(nil, member.server.name, "VOICESTATE", line[1], line[2], line[3], line[4])
|
||||
}
|
||||
}
|
||||
break
|
||||
case "BROADCASTAS":
|
||||
// TODO: global broadcast
|
||||
user := instance.server.clients.Get(line[1])
|
||||
if user != nil {
|
||||
// I'm not too sure what the capability bit is, I think it's just ones that match
|
||||
for friend := range user.Friends(caps.ExtendedNames) {
|
||||
friend.Send(nil, user.NickMaskString(), line[2], line[3:]...)
|
||||
}
|
||||
}
|
||||
break
|
||||
case "BROADCASTTO":
|
||||
channel := instance.server.channels.Get(line[1])
|
||||
if channel != nil {
|
||||
// I'm not too sure what the capability bit is, I think it's just ones that match
|
||||
for _, person := range channel.Members() {
|
||||
person.Send(nil, instance.server.name, line[2], line[3:]...)
|
||||
}
|
||||
}
|
||||
break
|
||||
default:
|
||||
println("Unknown cef message: ", line[0])
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -477,6 +477,12 @@ func (channel *Channel) Names(client *Client, rb *ResponseBuffer) {
|
|||
if respectAuditorium && memberData.modes.HighestChannelUserMode() == modes.Mode(0) {
|
||||
continue
|
||||
}
|
||||
if rb.session.capabilities.Has(caps.ExtendedNames) {
|
||||
away, _ := target.Away()
|
||||
if away {
|
||||
nick = nick + "*"
|
||||
}
|
||||
}
|
||||
tl.AddParts(memberData.modes.Prefixes(isMultiPrefix), nick)
|
||||
}
|
||||
}
|
||||
|
|
@ -795,6 +801,8 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
|
|||
}
|
||||
|
||||
client.server.logger.Debug("channels", fmt.Sprintf("%s joined channel %s", details.nick, chname))
|
||||
// I think this is assured to always be a good join point
|
||||
client.server.cefManager.CEFMessage("POLL", channel.NameCasefolded())
|
||||
|
||||
givenMode := func() (givenMode modes.Mode) {
|
||||
channel.joinPartMutex.Lock()
|
||||
|
|
@ -991,6 +999,7 @@ func (channel *Channel) playJoinForSession(session *Session) {
|
|||
channel.Names(client, sessionRb)
|
||||
}
|
||||
sessionRb.Send(false)
|
||||
client.server.cefManager.CEFMessage("POLL", channel.NameCasefolded())
|
||||
}
|
||||
|
||||
// Part parts the given client from this channel, with the given message.
|
||||
|
|
@ -1450,6 +1459,7 @@ func (channel *Channel) Quit(client *Client) {
|
|||
client.server.channels.Cleanup(channel)
|
||||
}
|
||||
client.removeChannel(channel)
|
||||
client.server.cefManager.KickBroadcast(channel.name, client.Username())
|
||||
}
|
||||
|
||||
func (channel *Channel) Kick(client *Client, target *Client, comment string, rb *ResponseBuffer, hasPrivs bool) {
|
||||
|
|
@ -1513,7 +1523,7 @@ func (channel *Channel) Purge(source string) {
|
|||
now := time.Now().UTC()
|
||||
for _, member := range members {
|
||||
tnick := member.Nick()
|
||||
msgid := utils.GenerateSecretToken()
|
||||
msgid := utils.GenerateMessageIdStr()
|
||||
for _, session := range member.Sessions() {
|
||||
session.sendFromClientInternal(false, now, msgid, source, "*", false, nil, "KICK", chname, tnick, member.t("This channel has been purged by the server administrators and cannot be used"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -753,6 +753,7 @@ func csListHandler(service *ircService, server *Server, client *Client, command
|
|||
}
|
||||
|
||||
func csInfoHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||
|
||||
if len(params) == 0 {
|
||||
// #765
|
||||
listRegisteredChannels(service, client.Account(), rb)
|
||||
|
|
@ -765,37 +766,41 @@ func csInfoHandler(service *ircService, server *Server, client *Client, command
|
|||
return
|
||||
}
|
||||
|
||||
// purge status
|
||||
if client.HasRoleCapabs("chanreg") {
|
||||
purgeRecord, err := server.channels.LoadPurgeRecord(chname)
|
||||
if err == nil {
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Channel %s was purged by the server operators and cannot be used"), chname))
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Purged by operator: %s"), purgeRecord.Oper))
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Purged at: %s"), purgeRecord.PurgedAt.Format(time.RFC1123)))
|
||||
if purgeRecord.Reason != "" {
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Purge reason: %s"), purgeRecord.Reason))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if server.channels.IsPurged(chname) {
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Channel %s was purged by the server operators and cannot be used"), chname))
|
||||
}
|
||||
}
|
||||
|
||||
var chinfo RegisteredChannel
|
||||
channel := server.channels.Get(params[0])
|
||||
if channel != nil {
|
||||
chinfo = channel.exportSummary()
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"target": chinfo.Name,
|
||||
}
|
||||
|
||||
// purge status
|
||||
if client.HasRoleCapabs("chanreg") {
|
||||
purgeRecord, err := server.channels.LoadPurgeRecord(chname)
|
||||
if err == nil {
|
||||
service.TaggedNotice(rb, fmt.Sprintf(client.t("Channel %s was purged by the server operators and cannot be used"), chname), tags)
|
||||
service.TaggedNotice(rb, fmt.Sprintf(client.t("Purged by operator: %s"), purgeRecord.Oper), tags)
|
||||
service.TaggedNotice(rb, fmt.Sprintf(client.t("Purged at: %s"), purgeRecord.PurgedAt.Format(time.RFC1123)), tags)
|
||||
if purgeRecord.Reason != "" {
|
||||
service.TaggedNotice(rb, fmt.Sprintf(client.t("Purge reason: %s"), purgeRecord.Reason), tags)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if server.channels.IsPurged(chname) {
|
||||
service.TaggedNotice(rb, fmt.Sprintf(client.t("Channel %s was purged by the server operators and cannot be used"), chname), tags)
|
||||
}
|
||||
}
|
||||
|
||||
// channel exists but is unregistered, or doesn't exist:
|
||||
if chinfo.Founder == "" {
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Channel %s is not registered"), chname))
|
||||
service.TaggedNotice(rb, fmt.Sprintf(client.t("Channel %s is not registered"), chname), tags)
|
||||
return
|
||||
}
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Channel %s is registered"), chinfo.Name))
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Founder: %s"), chinfo.Founder))
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Registered at: %s"), chinfo.RegisteredAt.Format(time.RFC1123)))
|
||||
service.TaggedNotice(rb, fmt.Sprintf(client.t("Channel %s is registered"), chinfo.Name), tags)
|
||||
service.TaggedNotice(rb, fmt.Sprintf(client.t("Founder: %s"), chinfo.Founder), tags)
|
||||
service.TaggedNotice(rb, fmt.Sprintf(client.t("Registered at: %s"), chinfo.RegisteredAt.Format(time.RFC1123)), tags)
|
||||
}
|
||||
|
||||
func displayChannelSetting(service *ircService, settingName string, settings ChannelSettings, client *Client, rb *ResponseBuffer) {
|
||||
|
|
|
|||
|
|
@ -1436,6 +1436,10 @@ func composeMultilineBatch(batchID, fromNickMask, fromAccount string, isBot bool
|
|||
for _, msg := range message.Split {
|
||||
message := ircmsg.MakeMessage(nil, fromNickMask, command, target, msg.Message)
|
||||
message.SetTag("batch", batchID)
|
||||
for k, v := range msg.Tags {
|
||||
message.SetTag(k, v)
|
||||
}
|
||||
|
||||
if msg.Concat {
|
||||
message.SetTag(caps.MultilineConcatTag, "")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ package irc
|
|||
const (
|
||||
// maxLastArgLength is used to simply cap off the final argument when creating general messages where we need to select a limit.
|
||||
// for instance, in MONITOR lists, RPL_ISUPPORT lists, etc.
|
||||
maxLastArgLength = 400
|
||||
maxLastArgLength = 1024
|
||||
// maxTargets is the maximum number of targets for PRIVMSG and NOTICE.
|
||||
maxTargets = 4
|
||||
)
|
||||
|
|
|
|||
|
|
@ -410,7 +410,11 @@ func (client *Client) SetMode(mode modes.Mode, on bool) bool {
|
|||
|
||||
func (client *Client) SetRealname(realname string) {
|
||||
client.stateMutex.Lock()
|
||||
// TODO: make this configurable
|
||||
client.realname = realname
|
||||
if len(realname) > 64 {
|
||||
client.realname = client.realname[:64]
|
||||
}
|
||||
alwaysOn := client.registered && client.alwaysOn
|
||||
client.stateMutex.Unlock()
|
||||
if alwaysOn {
|
||||
|
|
|
|||
|
|
@ -1128,6 +1128,12 @@ func extjwtHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respo
|
|||
}
|
||||
|
||||
claims["channel"] = channel.Name()
|
||||
|
||||
var channelModeStrings []string
|
||||
for _, mode := range channel.flags.AllModes() {
|
||||
channelModeStrings = append(channelModeStrings, mode.String())
|
||||
}
|
||||
claims["chanModes"] = channelModeStrings
|
||||
claims["joined"] = 0
|
||||
claims["cmodes"] = []string{}
|
||||
if present, joinTimeSecs, cModes := channel.ClientStatus(client); present {
|
||||
|
|
@ -2234,7 +2240,7 @@ func absorbBatchedMessage(server *Server, client *Client, msg ircmsg.Message, ba
|
|||
if !isConcat && len(rb.session.batch.message.Split) != 0 {
|
||||
rb.session.batch.lenBytes++ // bill for the newline
|
||||
}
|
||||
rb.session.batch.message.Append(msg.Params[1], isConcat)
|
||||
rb.session.batch.message.Append(msg.Params[1], isConcat, msg.ClientOnlyTags())
|
||||
rb.session.batch.lenBytes += len(msg.Params[1])
|
||||
config := server.Config()
|
||||
if config.Limits.Multiline.MaxBytes < rb.session.batch.lenBytes {
|
||||
|
|
@ -2317,6 +2323,41 @@ func messageHandler(server *Server, client *Client, msg ircmsg.Message, rb *Resp
|
|||
return false
|
||||
}
|
||||
|
||||
// Not really sure how to do this in Go
|
||||
var endChars = map[int32]bool{
|
||||
' ': true,
|
||||
'@': true,
|
||||
':': true,
|
||||
'!': true,
|
||||
'?': true,
|
||||
}
|
||||
|
||||
func detectMentions(message string) (mentions []string) {
|
||||
buf := ""
|
||||
mentions = []string{}
|
||||
working := false
|
||||
for _, char := range message {
|
||||
if char == '@' {
|
||||
working = true
|
||||
continue
|
||||
}
|
||||
if !working {
|
||||
continue
|
||||
}
|
||||
if _, stop := endChars[char]; stop {
|
||||
working = false
|
||||
mentions = append(mentions, buf)
|
||||
buf = ""
|
||||
} else {
|
||||
buf += string(char)
|
||||
}
|
||||
}
|
||||
if len(buf) != 0 {
|
||||
mentions = append(mentions, buf)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func dispatchMessageToTarget(client *Client, tags map[string]string, histType history.ItemType, command, target string, message utils.SplitMessage, rb *ResponseBuffer) {
|
||||
server := client.server
|
||||
|
||||
|
|
@ -2333,7 +2374,15 @@ func dispatchMessageToTarget(client *Client, tags map[string]string, histType hi
|
|||
}
|
||||
return
|
||||
}
|
||||
// This likely isn't that great for performance. Should figure out some way to deal with this at some point
|
||||
mentions := detectMentions(message.Message)
|
||||
channel.SendSplitMessage(command, lowestPrefix, tags, client, message, rb)
|
||||
for _, mention := range mentions {
|
||||
user := client.server.clients.Get(mention)
|
||||
if user != nil {
|
||||
client.server.cefManager.CEFMessage("MENTION", user.nickCasefolded, channel.Name(), message.Msgid)
|
||||
}
|
||||
}
|
||||
} else if target[0] == '$' && len(target) > 2 && client.Oper().HasRoleCapab("massmessage") {
|
||||
details := client.Details()
|
||||
matcher, err := utils.CompileGlob(target[2:], false)
|
||||
|
|
@ -2456,7 +2505,9 @@ func dispatchMessageToTarget(client *Client, tags map[string]string, histType hi
|
|||
Message: message,
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
client.addHistoryItem(user, item, &details, &tDetails, config)
|
||||
client.server.cefManager.CEFMessage("MENTION", user.nickCasefolded, client.nick, message.Msgid)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2745,7 +2796,7 @@ func redactHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respo
|
|||
var members []*Client // members of a channel, or both parties of a PM
|
||||
var canDelete CanDelete
|
||||
|
||||
msgid := utils.GenerateSecretToken()
|
||||
msgid := utils.GenerateMessageIdStr()
|
||||
time := time.Now().UTC().Round(0)
|
||||
details := client.Details()
|
||||
isBot := client.HasMode(modes.Bot)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func (t *JwtServiceConfig) Enabled() bool {
|
|||
|
||||
func (t *JwtServiceConfig) Sign(claims MapClaims) (result string, err error) {
|
||||
claims["exp"] = time.Now().Unix() + int64(t.Expiration/time.Second)
|
||||
|
||||
claims["now"] = time.Now().Unix()
|
||||
if t.rsaPrivateKey != nil {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims(claims))
|
||||
return token.SignedString(t.rsaPrivateKey)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"encoding/json"
|
||||
|
||||
"github.com/ergochat/ergo/irc/history"
|
||||
"github.com/ergochat/ergo/irc/utils"
|
||||
)
|
||||
|
||||
// 123 / '{' is the magic number that means JSON;
|
||||
|
|
@ -18,6 +17,7 @@ func unmarshalItem(data []byte, result *history.Item) (err error) {
|
|||
return json.Unmarshal(data, result)
|
||||
}
|
||||
|
||||
// TODO: probably should convert the internal mysql column to uint
|
||||
func decodeMsgid(msgid string) ([]byte, error) {
|
||||
return utils.B32Encoder.DecodeString(msgid)
|
||||
return []byte(msgid), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,7 +142,14 @@ func (rb *ResponseBuffer) AddSplitMessageFromClient(fromNickMask string, fromAcc
|
|||
if i == 0 {
|
||||
msgid = message.Msgid
|
||||
}
|
||||
rb.AddFromClient(message.Time, msgid, fromNickMask, fromAccount, isBot, tags, command, target, messagePair.Message)
|
||||
mergedTags := make(map[string]string)
|
||||
for k, v := range tags {
|
||||
mergedTags[k] = v
|
||||
}
|
||||
for k, v := range messagePair.Tags {
|
||||
mergedTags[k] = v
|
||||
}
|
||||
rb.AddFromClient(message.Time, msgid, fromNickMask, fromAccount, isBot, mergedTags, command, target, messagePair.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,9 @@ type Server struct {
|
|||
semaphores ServerSemaphores
|
||||
flock flock.Flocker
|
||||
defcon atomic.Uint32
|
||||
|
||||
// CEF
|
||||
cefManager *CefConnection
|
||||
}
|
||||
|
||||
// NewServer returns a new Oragono server.
|
||||
|
|
@ -163,6 +166,8 @@ func (server *Server) Shutdown() {
|
|||
func (server *Server) Run() {
|
||||
defer server.Shutdown()
|
||||
|
||||
server.cefManager = cefConnection(server)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-server.exitSignals:
|
||||
|
|
|
|||
|
|
@ -107,6 +107,10 @@ func (service *ircService) Notice(rb *ResponseBuffer, text string) {
|
|||
rb.Add(nil, service.prefix, "NOTICE", rb.target.Nick(), text)
|
||||
}
|
||||
|
||||
func (service *ircService) TaggedNotice(rb *ResponseBuffer, text string, tags map[string]string) {
|
||||
rb.Add(tags, service.prefix, "NOTICE", rb.target.Nick(), text)
|
||||
}
|
||||
|
||||
// all service commands at the protocol level, by uppercase command name
|
||||
// e.g., NICKSERV, NS
|
||||
var ergoServicesByCommandAlias map[string]*ircService
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import (
|
|||
"encoding/hex"
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
|
@ -31,8 +32,26 @@ var (
|
|||
|
||||
const (
|
||||
SecretTokenLength = 26
|
||||
MachineId = 1 // Since there's no scaling Ergo, id is fixed at 1. Other things can have 2-127
|
||||
)
|
||||
|
||||
var inc uint64 = 0
|
||||
|
||||
// slingamn, if you ever see this, i'm sorry - I just didn't want to attach what i think is redundant data to every
|
||||
// message.
|
||||
func GenerateMessageId() uint64 {
|
||||
inc++
|
||||
var ts = time.Now().Unix() & 0xffffffffffff
|
||||
var flake = uint64(ts << 16)
|
||||
flake |= MachineId << 10
|
||||
flake |= inc % 0x3ff
|
||||
return flake
|
||||
}
|
||||
|
||||
func GenerateMessageIdStr() string {
|
||||
return strconv.FormatUint(GenerateMessageId(), 10)
|
||||
}
|
||||
|
||||
// generate a secret token that cannot be brute-forced via online attacks
|
||||
func GenerateSecretToken() string {
|
||||
// 128 bits of entropy are enough to resist any online attack:
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ func IsRestrictedCTCPMessage(message string) bool {
|
|||
|
||||
type MessagePair struct {
|
||||
Message string
|
||||
Tags map[string]string
|
||||
Concat bool // should be relayed with the multiline-concat tag
|
||||
}
|
||||
|
||||
|
|
@ -37,19 +38,20 @@ type SplitMessage struct {
|
|||
|
||||
func MakeMessage(original string) (result SplitMessage) {
|
||||
result.Message = original
|
||||
result.Msgid = GenerateSecretToken()
|
||||
result.Msgid = GenerateMessageIdStr()
|
||||
result.SetTime()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (sm *SplitMessage) Append(message string, concat bool) {
|
||||
func (sm *SplitMessage) Append(message string, concat bool, tags map[string]string) {
|
||||
if sm.Msgid == "" {
|
||||
sm.Msgid = GenerateSecretToken()
|
||||
sm.Msgid = GenerateMessageIdStr()
|
||||
}
|
||||
sm.Split = append(sm.Split, MessagePair{
|
||||
Message: message,
|
||||
Concat: concat,
|
||||
Tags: tags,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue