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,
|
# 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
|
# 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:
|
# 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
|
# 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
|
# if you don't want to publicize how popular the server is
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ package caps
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// number of recognized capabilities:
|
// number of recognized capabilities:
|
||||||
numCapabs = 34
|
numCapabs = 35
|
||||||
// length of the uint32 array that represents the bitset:
|
// length of the uint32 array that represents the bitset:
|
||||||
bitsetLen = 2
|
bitsetLen = 2
|
||||||
)
|
)
|
||||||
|
|
@ -148,6 +148,8 @@ const (
|
||||||
// ZNCSelfMessage is the ZNC vendor capability named "znc.in/self-message":
|
// ZNCSelfMessage is the ZNC vendor capability named "znc.in/self-message":
|
||||||
// https://wiki.znc.in/Query_buffers
|
// https://wiki.znc.in/Query_buffers
|
||||||
ZNCSelfMessage Capability = iota
|
ZNCSelfMessage Capability = iota
|
||||||
|
|
||||||
|
ExtendedNames Capability = iota
|
||||||
)
|
)
|
||||||
|
|
||||||
// `capabilityNames[capab]` is the string name of the capability `capab`
|
// `capabilityNames[capab]` is the string name of the capability `capab`
|
||||||
|
|
@ -187,5 +189,6 @@ var (
|
||||||
"userhost-in-names",
|
"userhost-in-names",
|
||||||
"znc.in/playback",
|
"znc.in/playback",
|
||||||
"znc.in/self-message",
|
"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) {
|
if respectAuditorium && memberData.modes.HighestChannelUserMode() == modes.Mode(0) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if rb.session.capabilities.Has(caps.ExtendedNames) {
|
||||||
|
away, _ := target.Away()
|
||||||
|
if away {
|
||||||
|
nick = nick + "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
tl.AddParts(memberData.modes.Prefixes(isMultiPrefix), 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))
|
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) {
|
givenMode := func() (givenMode modes.Mode) {
|
||||||
channel.joinPartMutex.Lock()
|
channel.joinPartMutex.Lock()
|
||||||
|
|
@ -991,6 +999,7 @@ func (channel *Channel) playJoinForSession(session *Session) {
|
||||||
channel.Names(client, sessionRb)
|
channel.Names(client, sessionRb)
|
||||||
}
|
}
|
||||||
sessionRb.Send(false)
|
sessionRb.Send(false)
|
||||||
|
client.server.cefManager.CEFMessage("POLL", channel.NameCasefolded())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Part parts the given client from this channel, with the given message.
|
// 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.server.channels.Cleanup(channel)
|
||||||
}
|
}
|
||||||
client.removeChannel(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) {
|
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()
|
now := time.Now().UTC()
|
||||||
for _, member := range members {
|
for _, member := range members {
|
||||||
tnick := member.Nick()
|
tnick := member.Nick()
|
||||||
msgid := utils.GenerateSecretToken()
|
msgid := utils.GenerateMessageIdStr()
|
||||||
for _, session := range member.Sessions() {
|
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"))
|
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) {
|
func csInfoHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
|
|
||||||
if len(params) == 0 {
|
if len(params) == 0 {
|
||||||
// #765
|
// #765
|
||||||
listRegisteredChannels(service, client.Account(), rb)
|
listRegisteredChannels(service, client.Account(), rb)
|
||||||
|
|
@ -765,37 +766,41 @@ func csInfoHandler(service *ircService, server *Server, client *Client, command
|
||||||
return
|
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
|
var chinfo RegisteredChannel
|
||||||
channel := server.channels.Get(params[0])
|
channel := server.channels.Get(params[0])
|
||||||
if channel != nil {
|
if channel != nil {
|
||||||
chinfo = channel.exportSummary()
|
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:
|
// channel exists but is unregistered, or doesn't exist:
|
||||||
if chinfo.Founder == "" {
|
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
|
return
|
||||||
}
|
}
|
||||||
service.Notice(rb, fmt.Sprintf(client.t("Channel %s is registered"), chinfo.Name))
|
service.TaggedNotice(rb, fmt.Sprintf(client.t("Channel %s is registered"), chinfo.Name), tags)
|
||||||
service.Notice(rb, fmt.Sprintf(client.t("Founder: %s"), chinfo.Founder))
|
service.TaggedNotice(rb, fmt.Sprintf(client.t("Founder: %s"), chinfo.Founder), tags)
|
||||||
service.Notice(rb, fmt.Sprintf(client.t("Registered at: %s"), chinfo.RegisteredAt.Format(time.RFC1123)))
|
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) {
|
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 {
|
for _, msg := range message.Split {
|
||||||
message := ircmsg.MakeMessage(nil, fromNickMask, command, target, msg.Message)
|
message := ircmsg.MakeMessage(nil, fromNickMask, command, target, msg.Message)
|
||||||
message.SetTag("batch", batchID)
|
message.SetTag("batch", batchID)
|
||||||
|
for k, v := range msg.Tags {
|
||||||
|
message.SetTag(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
if msg.Concat {
|
if msg.Concat {
|
||||||
message.SetTag(caps.MultilineConcatTag, "")
|
message.SetTag(caps.MultilineConcatTag, "")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ package irc
|
||||||
const (
|
const (
|
||||||
// maxLastArgLength is used to simply cap off the final argument when creating general messages where we need to select a limit.
|
// 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.
|
// 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 is the maximum number of targets for PRIVMSG and NOTICE.
|
||||||
maxTargets = 4
|
maxTargets = 4
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -410,7 +410,11 @@ func (client *Client) SetMode(mode modes.Mode, on bool) bool {
|
||||||
|
|
||||||
func (client *Client) SetRealname(realname string) {
|
func (client *Client) SetRealname(realname string) {
|
||||||
client.stateMutex.Lock()
|
client.stateMutex.Lock()
|
||||||
|
// TODO: make this configurable
|
||||||
client.realname = realname
|
client.realname = realname
|
||||||
|
if len(realname) > 64 {
|
||||||
|
client.realname = client.realname[:64]
|
||||||
|
}
|
||||||
alwaysOn := client.registered && client.alwaysOn
|
alwaysOn := client.registered && client.alwaysOn
|
||||||
client.stateMutex.Unlock()
|
client.stateMutex.Unlock()
|
||||||
if alwaysOn {
|
if alwaysOn {
|
||||||
|
|
|
||||||
|
|
@ -1128,6 +1128,12 @@ func extjwtHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respo
|
||||||
}
|
}
|
||||||
|
|
||||||
claims["channel"] = channel.Name()
|
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["joined"] = 0
|
||||||
claims["cmodes"] = []string{}
|
claims["cmodes"] = []string{}
|
||||||
if present, joinTimeSecs, cModes := channel.ClientStatus(client); present {
|
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 {
|
if !isConcat && len(rb.session.batch.message.Split) != 0 {
|
||||||
rb.session.batch.lenBytes++ // bill for the newline
|
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])
|
rb.session.batch.lenBytes += len(msg.Params[1])
|
||||||
config := server.Config()
|
config := server.Config()
|
||||||
if config.Limits.Multiline.MaxBytes < rb.session.batch.lenBytes {
|
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
|
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) {
|
func dispatchMessageToTarget(client *Client, tags map[string]string, histType history.ItemType, command, target string, message utils.SplitMessage, rb *ResponseBuffer) {
|
||||||
server := client.server
|
server := client.server
|
||||||
|
|
||||||
|
|
@ -2333,7 +2374,15 @@ func dispatchMessageToTarget(client *Client, tags map[string]string, histType hi
|
||||||
}
|
}
|
||||||
return
|
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)
|
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") {
|
} else if target[0] == '$' && len(target) > 2 && client.Oper().HasRoleCapab("massmessage") {
|
||||||
details := client.Details()
|
details := client.Details()
|
||||||
matcher, err := utils.CompileGlob(target[2:], false)
|
matcher, err := utils.CompileGlob(target[2:], false)
|
||||||
|
|
@ -2456,7 +2505,9 @@ func dispatchMessageToTarget(client *Client, tags map[string]string, histType hi
|
||||||
Message: message,
|
Message: message,
|
||||||
Tags: tags,
|
Tags: tags,
|
||||||
}
|
}
|
||||||
|
|
||||||
client.addHistoryItem(user, item, &details, &tDetails, config)
|
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 members []*Client // members of a channel, or both parties of a PM
|
||||||
var canDelete CanDelete
|
var canDelete CanDelete
|
||||||
|
|
||||||
msgid := utils.GenerateSecretToken()
|
msgid := utils.GenerateMessageIdStr()
|
||||||
time := time.Now().UTC().Round(0)
|
time := time.Now().UTC().Round(0)
|
||||||
details := client.Details()
|
details := client.Details()
|
||||||
isBot := client.HasMode(modes.Bot)
|
isBot := client.HasMode(modes.Bot)
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ func (t *JwtServiceConfig) Enabled() bool {
|
||||||
|
|
||||||
func (t *JwtServiceConfig) Sign(claims MapClaims) (result string, err error) {
|
func (t *JwtServiceConfig) Sign(claims MapClaims) (result string, err error) {
|
||||||
claims["exp"] = time.Now().Unix() + int64(t.Expiration/time.Second)
|
claims["exp"] = time.Now().Unix() + int64(t.Expiration/time.Second)
|
||||||
|
claims["now"] = time.Now().Unix()
|
||||||
if t.rsaPrivateKey != nil {
|
if t.rsaPrivateKey != nil {
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims(claims))
|
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims(claims))
|
||||||
return token.SignedString(t.rsaPrivateKey)
|
return token.SignedString(t.rsaPrivateKey)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/ergochat/ergo/irc/history"
|
"github.com/ergochat/ergo/irc/history"
|
||||||
"github.com/ergochat/ergo/irc/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 123 / '{' is the magic number that means JSON;
|
// 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)
|
return json.Unmarshal(data, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: probably should convert the internal mysql column to uint
|
||||||
func decodeMsgid(msgid string) ([]byte, error) {
|
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 {
|
if i == 0 {
|
||||||
msgid = message.Msgid
|
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
|
semaphores ServerSemaphores
|
||||||
flock flock.Flocker
|
flock flock.Flocker
|
||||||
defcon atomic.Uint32
|
defcon atomic.Uint32
|
||||||
|
|
||||||
|
// CEF
|
||||||
|
cefManager *CefConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer returns a new Oragono server.
|
// NewServer returns a new Oragono server.
|
||||||
|
|
@ -163,6 +166,8 @@ func (server *Server) Shutdown() {
|
||||||
func (server *Server) Run() {
|
func (server *Server) Run() {
|
||||||
defer server.Shutdown()
|
defer server.Shutdown()
|
||||||
|
|
||||||
|
server.cefManager = cefConnection(server)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-server.exitSignals:
|
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)
|
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
|
// all service commands at the protocol level, by uppercase command name
|
||||||
// e.g., NICKSERV, NS
|
// e.g., NICKSERV, NS
|
||||||
var ergoServicesByCommandAlias map[string]*ircService
|
var ergoServicesByCommandAlias map[string]*ircService
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
@ -31,8 +32,26 @@ var (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SecretTokenLength = 26
|
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
|
// generate a secret token that cannot be brute-forced via online attacks
|
||||||
func GenerateSecretToken() string {
|
func GenerateSecretToken() string {
|
||||||
// 128 bits of entropy are enough to resist any online attack:
|
// 128 bits of entropy are enough to resist any online attack:
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ func IsRestrictedCTCPMessage(message string) bool {
|
||||||
|
|
||||||
type MessagePair struct {
|
type MessagePair struct {
|
||||||
Message string
|
Message string
|
||||||
|
Tags map[string]string
|
||||||
Concat bool // should be relayed with the multiline-concat tag
|
Concat bool // should be relayed with the multiline-concat tag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,19 +38,20 @@ type SplitMessage struct {
|
||||||
|
|
||||||
func MakeMessage(original string) (result SplitMessage) {
|
func MakeMessage(original string) (result SplitMessage) {
|
||||||
result.Message = original
|
result.Message = original
|
||||||
result.Msgid = GenerateSecretToken()
|
result.Msgid = GenerateMessageIdStr()
|
||||||
result.SetTime()
|
result.SetTime()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm *SplitMessage) Append(message string, concat bool) {
|
func (sm *SplitMessage) Append(message string, concat bool, tags map[string]string) {
|
||||||
if sm.Msgid == "" {
|
if sm.Msgid == "" {
|
||||||
sm.Msgid = GenerateSecretToken()
|
sm.Msgid = GenerateMessageIdStr()
|
||||||
}
|
}
|
||||||
sm.Split = append(sm.Split, MessagePair{
|
sm.Split = append(sm.Split, MessagePair{
|
||||||
Message: message,
|
Message: message,
|
||||||
Concat: concat,
|
Concat: concat,
|
||||||
|
Tags: tags,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue