mirror of
https://github.com/ergochat/ergo.git
synced 2024-12-22 18:52:41 +01:00
Initial implementation of labeled-responses for WHOIS
This commit is contained in:
parent
095e71b2fe
commit
d4a8984e63
78
irc/batch.go
Normal file
78
irc/batch.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
|
||||||
|
// released under the MIT license
|
||||||
|
|
||||||
|
package irc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/goshuirc/irc-go/ircmsg"
|
||||||
|
"github.com/oragono/oragono/irc/caps"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// maxBatchID is the maximum ID the batch counter can get to before it rotates.
|
||||||
|
//
|
||||||
|
// Batch IDs are made up of the current unix timestamp plus a rolling int ID that's
|
||||||
|
// incremented for every new batch. It's an alright solution and will work unless we get
|
||||||
|
// more than maxId batches per nanosecond. Later on when we have S2S linking, the batch
|
||||||
|
// ID will also contain the server ID to ensure they stay unique.
|
||||||
|
maxBatchID uint64 = 60000
|
||||||
|
)
|
||||||
|
|
||||||
|
// BatchManager helps generate new batches and new batch IDs.
|
||||||
|
type BatchManager struct {
|
||||||
|
idCounter uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBatchManager returns a new Manager.
|
||||||
|
func NewBatchManager() *BatchManager {
|
||||||
|
return &BatchManager{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewID returns a new batch ID that should be unique.
|
||||||
|
func (bm *BatchManager) NewID() string {
|
||||||
|
bm.idCounter++
|
||||||
|
if maxBatchID < bm.idCounter {
|
||||||
|
bm.idCounter = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return strconv.FormatInt(time.Now().UnixNano(), 10) + strconv.FormatUint(bm.idCounter, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch represents an IRCv3 batch.
|
||||||
|
type Batch struct {
|
||||||
|
ID string
|
||||||
|
Type string
|
||||||
|
Params []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new batch.
|
||||||
|
func (bm *BatchManager) New(batchType string, params ...string) *Batch {
|
||||||
|
newBatch := Batch{
|
||||||
|
ID: bm.NewID(),
|
||||||
|
Type: batchType,
|
||||||
|
Params: params,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &newBatch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start sends the batch start message to this client
|
||||||
|
func (b *Batch) Start(client *Client, tags *map[string]ircmsg.TagValue) {
|
||||||
|
if client.capabilities.Has(caps.Batch) {
|
||||||
|
params := []string{"+" + b.ID, b.Type}
|
||||||
|
for _, param := range b.Params {
|
||||||
|
params = append(params, param)
|
||||||
|
}
|
||||||
|
client.Send(tags, client.server.name, "BATCH", params...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// End sends the batch end message to this client
|
||||||
|
func (b *Batch) End(client *Client) {
|
||||||
|
if client.capabilities.Has(caps.Batch) {
|
||||||
|
client.Send(nil, client.server.name, "BATCH", "-"+b.ID)
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
// SupportedCapabilities are the caps we advertise.
|
// SupportedCapabilities are the caps we advertise.
|
||||||
// MaxLine, SASL and STS are set during server startup.
|
// MaxLine, SASL and STS are set during server startup.
|
||||||
SupportedCapabilities = caps.NewSet(caps.AccountTag, caps.AccountNotify, caps.AwayNotify, caps.CapNotify, caps.ChgHost, caps.EchoMessage, caps.ExtendedJoin, caps.InviteNotify, caps.MessageTags, caps.MultiPrefix, caps.Rename, caps.ServerTime, caps.UserhostInNames)
|
SupportedCapabilities = caps.NewSet(caps.AccountTag, caps.AccountNotify, caps.AwayNotify, caps.Batch, caps.CapNotify, caps.ChgHost, caps.EchoMessage, caps.ExtendedJoin, caps.InviteNotify, caps.LabeledResponse, caps.MessageTags, caps.MultiPrefix, caps.Rename, caps.ServerTime, caps.UserhostInNames)
|
||||||
|
|
||||||
// CapValues are the actual values we advertise to v3.2 clients.
|
// CapValues are the actual values we advertise to v3.2 clients.
|
||||||
// actual values are set during server startup.
|
// actual values are set during server startup.
|
||||||
|
@ -621,7 +621,7 @@ func (client *Client) SendFromClient(msgid string, from *Client, tags *map[strin
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// these are all the output commands that MUST have their last param be a trailing.
|
// these are all the output commands that MUST have their last param be a trailing.
|
||||||
// this is needed because silly clients like to treat trailing as separate from the
|
// this is needed because dumb clients like to treat trailing params separately from the
|
||||||
// other params in messages.
|
// other params in messages.
|
||||||
commandsThatMustUseTrailing = map[string]bool{
|
commandsThatMustUseTrailing = map[string]bool{
|
||||||
"PRIVMSG": true,
|
"PRIVMSG": true,
|
||||||
@ -632,6 +632,47 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SendRawMessage sends a raw message to the client.
|
||||||
|
func (client *Client) SendRawMessage(message ircmsg.IrcMessage) error {
|
||||||
|
// use dumb hack to force the last param to be a trailing param if required
|
||||||
|
var usedTrailingHack bool
|
||||||
|
if commandsThatMustUseTrailing[strings.ToUpper(message.Command)] && len(message.Params) > 0 {
|
||||||
|
lastParam := message.Params[len(message.Params)-1]
|
||||||
|
// to force trailing, we ensure the final param contains a space
|
||||||
|
if !strings.Contains(lastParam, " ") {
|
||||||
|
message.Params[len(message.Params)-1] = lastParam + " "
|
||||||
|
usedTrailingHack = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assemble message
|
||||||
|
maxlenTags, maxlenRest := client.maxlens()
|
||||||
|
line, err := message.LineMaxLen(maxlenTags, maxlenRest)
|
||||||
|
if err != nil {
|
||||||
|
// try not to fail quietly - especially useful when running tests, as a note to dig deeper
|
||||||
|
// log.Println("Error assembling message:")
|
||||||
|
// spew.Dump(message)
|
||||||
|
// debug.PrintStack()
|
||||||
|
|
||||||
|
message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
|
||||||
|
line, _ := message.Line()
|
||||||
|
|
||||||
|
// if we used the trailing hack, we need to strip the final space we appended earlier on
|
||||||
|
if usedTrailingHack {
|
||||||
|
line = line[:len(line)-3] + "\r\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
client.socket.Write(line)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client.server.logger.Debug("useroutput", client.nick, " ->", strings.TrimRight(line, "\r\n"))
|
||||||
|
|
||||||
|
client.socket.Write(line)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Send sends an IRC line to the client.
|
// Send sends an IRC line to the client.
|
||||||
func (client *Client) Send(tags *map[string]ircmsg.TagValue, prefix string, command string, params ...string) error {
|
func (client *Client) Send(tags *map[string]ircmsg.TagValue, prefix string, command string, params ...string) error {
|
||||||
// attach server-time
|
// attach server-time
|
||||||
@ -644,41 +685,9 @@ func (client *Client) Send(tags *map[string]ircmsg.TagValue, prefix string, comm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// force trailing, if message requires it
|
|
||||||
var usedTrailingHack bool
|
|
||||||
if commandsThatMustUseTrailing[strings.ToUpper(command)] && len(params) > 0 {
|
|
||||||
lastParam := params[len(params)-1]
|
|
||||||
// to force trailing, we ensure the final param contains a space
|
|
||||||
if !strings.Contains(lastParam, " ") {
|
|
||||||
params[len(params)-1] = lastParam + " "
|
|
||||||
usedTrailingHack = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// send out the message
|
// send out the message
|
||||||
message := ircmsg.MakeMessage(tags, prefix, command, params...)
|
message := ircmsg.MakeMessage(tags, prefix, command, params...)
|
||||||
maxlenTags, maxlenRest := client.maxlens()
|
client.SendRawMessage(message)
|
||||||
line, err := message.LineMaxLen(maxlenTags, maxlenRest)
|
|
||||||
if err != nil {
|
|
||||||
// try not to fail quietly - especially useful when running tests, as a note to dig deeper
|
|
||||||
// log.Println("Error assembling message:")
|
|
||||||
// spew.Dump(message)
|
|
||||||
// debug.PrintStack()
|
|
||||||
|
|
||||||
message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
|
|
||||||
line, _ := message.Line()
|
|
||||||
client.socket.Write(line)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// is we used the trailing hack, we need to strip the final space we appended earlier
|
|
||||||
if usedTrailingHack {
|
|
||||||
line = line[:len(line)-3] + "\r\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
client.server.logger.Debug("useroutput", client.nick, " ->", strings.TrimRight(line, "\r\n"))
|
|
||||||
|
|
||||||
client.socket.Write(line)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
120
irc/responsebuffer.go
Normal file
120
irc/responsebuffer.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
|
||||||
|
// released under the MIT license
|
||||||
|
|
||||||
|
package irc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/goshuirc/irc-go/ircmsg"
|
||||||
|
"github.com/oragono/oragono/irc/caps"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResponseBuffer - put simply - buffers messages and then outputs them to a given client.
|
||||||
|
//
|
||||||
|
// Using a ResponseBuffer lets you really easily implement labeled-response, since the
|
||||||
|
// buffer will silently create a batch if required and label the outgoing messages as
|
||||||
|
// necessary (or leave it off and simply tag the outgoing message).
|
||||||
|
type ResponseBuffer struct {
|
||||||
|
Label string
|
||||||
|
target *Client
|
||||||
|
messages []ircmsg.IrcMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLabel returns the label from the given message.
|
||||||
|
func GetLabel(msg ircmsg.IrcMessage) string {
|
||||||
|
return msg.Tags["label"].Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResponseBuffer returns a new ResponseBuffer.
|
||||||
|
func NewResponseBuffer(target *Client) *ResponseBuffer {
|
||||||
|
return &ResponseBuffer{
|
||||||
|
target: target,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a standard new message to our queue.
|
||||||
|
func (rb *ResponseBuffer) Add(tags *map[string]ircmsg.TagValue, prefix string, command string, params ...string) {
|
||||||
|
message := ircmsg.MakeMessage(tags, prefix, command, params...)
|
||||||
|
|
||||||
|
rb.messages = append(rb.messages, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFromClient adds a new message from a specific client to our queue.
|
||||||
|
func (rb *ResponseBuffer) AddFromClient(msgid string, from *Client, tags *map[string]ircmsg.TagValue, command string, params ...string) {
|
||||||
|
// attach account-tag
|
||||||
|
if rb.target.capabilities.Has(caps.AccountTag) && from.account != &NoAccount {
|
||||||
|
if tags == nil {
|
||||||
|
tags = ircmsg.MakeTags("account", from.account.Name)
|
||||||
|
} else {
|
||||||
|
(*tags)["account"] = ircmsg.MakeTagValue(from.account.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// attach message-id
|
||||||
|
if len(msgid) > 0 && rb.target.capabilities.Has(caps.MessageTags) {
|
||||||
|
if tags == nil {
|
||||||
|
tags = ircmsg.MakeTags("draft/msgid", msgid)
|
||||||
|
} else {
|
||||||
|
(*tags)["draft/msgid"] = ircmsg.MakeTagValue(msgid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rb.Add(tags, from.nickMaskString, command, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSplitMessageFromClient adds a new split message from a specific client to our queue.
|
||||||
|
func (rb *ResponseBuffer) AddSplitMessageFromClient(msgid string, from *Client, tags *map[string]ircmsg.TagValue, command string, target string, message SplitMessage) {
|
||||||
|
if rb.target.capabilities.Has(caps.MaxLine) {
|
||||||
|
rb.AddFromClient(msgid, from, tags, command, target, message.ForMaxLine)
|
||||||
|
} else {
|
||||||
|
for _, str := range message.For512 {
|
||||||
|
rb.AddFromClient(msgid, from, tags, command, target, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send sends the message to our target client.
|
||||||
|
func (rb *ResponseBuffer) Send() error {
|
||||||
|
// make batch and all if required
|
||||||
|
var batch *Batch
|
||||||
|
useLabel := rb.target.capabilities.Has(caps.LabeledResponse) && rb.Label != ""
|
||||||
|
if useLabel && 1 < len(rb.messages) && rb.target.capabilities.Has(caps.Batch) {
|
||||||
|
batch = rb.target.server.batches.New("draft/labeled-response")
|
||||||
|
}
|
||||||
|
|
||||||
|
// if label but no batch, add label to first message
|
||||||
|
if useLabel && batch == nil {
|
||||||
|
message := rb.messages[0]
|
||||||
|
message.Tags["label"] = ircmsg.MakeTagValue(rb.Label)
|
||||||
|
rb.messages[0] = message
|
||||||
|
}
|
||||||
|
|
||||||
|
// start batch if required
|
||||||
|
if batch != nil {
|
||||||
|
batch.Start(rb.target, ircmsg.MakeTags("label", rb.Label))
|
||||||
|
}
|
||||||
|
|
||||||
|
// send each message out
|
||||||
|
for _, message := range rb.messages {
|
||||||
|
// attach server-time if needed
|
||||||
|
if rb.target.capabilities.Has(caps.ServerTime) {
|
||||||
|
t := time.Now().UTC().Format("2006-01-02T15:04:05.999Z")
|
||||||
|
message.Tags["time"] = ircmsg.MakeTagValue(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// attach batch ID
|
||||||
|
if batch != nil {
|
||||||
|
message.Tags["batch"] = ircmsg.MakeTagValue(batch.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// send message out
|
||||||
|
rb.target.SendRawMessage(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// end batch if required
|
||||||
|
if batch != nil {
|
||||||
|
batch.End(rb.target)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -78,6 +78,7 @@ type Server struct {
|
|||||||
accountAuthenticationEnabled bool
|
accountAuthenticationEnabled bool
|
||||||
accountRegistration *AccountRegistration
|
accountRegistration *AccountRegistration
|
||||||
accounts map[string]*ClientAccount
|
accounts map[string]*ClientAccount
|
||||||
|
batches *BatchManager
|
||||||
channelRegistrationEnabled bool
|
channelRegistrationEnabled bool
|
||||||
channels ChannelNameMap
|
channels ChannelNameMap
|
||||||
channelJoinPartMutex sync.Mutex // used when joining/parting channels to prevent stomping over each others' access and all
|
channelJoinPartMutex sync.Mutex // used when joining/parting channels to prevent stomping over each others' access and all
|
||||||
@ -146,6 +147,7 @@ func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
|
|||||||
// initialize data structures
|
// initialize data structures
|
||||||
server := &Server{
|
server := &Server{
|
||||||
accounts: make(map[string]*ClientAccount),
|
accounts: make(map[string]*ClientAccount),
|
||||||
|
batches: NewBatchManager(),
|
||||||
channels: *NewChannelNameMap(),
|
channels: *NewChannelNameMap(),
|
||||||
clients: NewClientLookupSet(),
|
clients: NewClientLookupSet(),
|
||||||
commands: make(chan Command),
|
commands: make(chan Command),
|
||||||
@ -988,8 +990,12 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
masksString = msg.Params[0]
|
masksString = msg.Params[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rb := NewResponseBuffer(client)
|
||||||
|
rb.Label = GetLabel(msg)
|
||||||
|
|
||||||
if len(strings.TrimSpace(masksString)) < 1 {
|
if len(strings.TrimSpace(masksString)) < 1 {
|
||||||
client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, "No masks given")
|
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, "No masks given")
|
||||||
|
rb.Send()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -998,16 +1004,16 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
for _, mask := range masks {
|
for _, mask := range masks {
|
||||||
casefoldedMask, err := Casefold(mask)
|
casefoldedMask, err := Casefold(mask)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
client.Send(nil, client.server.name, ERR_NOSUCHNICK, client.nick, mask, "No such nick")
|
rb.Add(nil, client.server.name, ERR_NOSUCHNICK, client.nick, mask, "No such nick")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
matches := server.clients.FindAll(casefoldedMask)
|
matches := server.clients.FindAll(casefoldedMask)
|
||||||
if len(matches) == 0 {
|
if len(matches) == 0 {
|
||||||
client.Send(nil, client.server.name, ERR_NOSUCHNICK, client.nick, mask, "No such nick")
|
rb.Add(nil, client.server.name, ERR_NOSUCHNICK, client.nick, mask, "No such nick")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for mclient := range matches {
|
for mclient := range matches {
|
||||||
client.getWhoisOf(mclient)
|
client.getWhoisOf(mclient, rb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1015,36 +1021,37 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
casefoldedMask, err := Casefold(strings.Split(masksString, ",")[0])
|
casefoldedMask, err := Casefold(strings.Split(masksString, ",")[0])
|
||||||
mclient := server.clients.Get(casefoldedMask)
|
mclient := server.clients.Get(casefoldedMask)
|
||||||
if err != nil || mclient == nil {
|
if err != nil || mclient == nil {
|
||||||
client.Send(nil, client.server.name, ERR_NOSUCHNICK, client.nick, masksString, "No such nick")
|
rb.Add(nil, client.server.name, ERR_NOSUCHNICK, client.nick, masksString, "No such nick")
|
||||||
// fall through, ENDOFWHOIS is always sent
|
// fall through, ENDOFWHOIS is always sent
|
||||||
} else {
|
} else {
|
||||||
client.getWhoisOf(mclient)
|
client.getWhoisOf(mclient, rb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
client.Send(nil, server.name, RPL_ENDOFWHOIS, client.nick, masksString, "End of /WHOIS list")
|
rb.Add(nil, server.name, RPL_ENDOFWHOIS, client.nick, masksString, "End of /WHOIS list")
|
||||||
|
rb.Send()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) getWhoisOf(target *Client) {
|
func (client *Client) getWhoisOf(target *Client, rb *ResponseBuffer) {
|
||||||
client.Send(nil, client.server.name, RPL_WHOISUSER, client.nick, target.nick, target.username, target.hostname, "*", target.realname)
|
rb.Add(nil, client.server.name, RPL_WHOISUSER, client.nick, target.nick, target.username, target.hostname, "*", target.realname)
|
||||||
|
|
||||||
whoischannels := client.WhoisChannelsNames(target)
|
whoischannels := client.WhoisChannelsNames(target)
|
||||||
if whoischannels != nil {
|
if whoischannels != nil {
|
||||||
client.Send(nil, client.server.name, RPL_WHOISCHANNELS, client.nick, target.nick, strings.Join(whoischannels, " "))
|
rb.Add(nil, client.server.name, RPL_WHOISCHANNELS, client.nick, target.nick, strings.Join(whoischannels, " "))
|
||||||
}
|
}
|
||||||
if target.class != nil {
|
if target.class != nil {
|
||||||
client.Send(nil, client.server.name, RPL_WHOISOPERATOR, client.nick, target.nick, target.whoisLine)
|
rb.Add(nil, client.server.name, RPL_WHOISOPERATOR, client.nick, target.nick, target.whoisLine)
|
||||||
}
|
}
|
||||||
if client.flags[Operator] || client == target {
|
if client.flags[Operator] || client == target {
|
||||||
client.Send(nil, client.server.name, RPL_WHOISACTUALLY, client.nick, target.nick, fmt.Sprintf("%s@%s", target.username, utils.LookupHostname(target.IPString())), target.IPString(), "Actual user@host, Actual IP")
|
rb.Add(nil, client.server.name, RPL_WHOISACTUALLY, client.nick, target.nick, fmt.Sprintf("%s@%s", target.username, utils.LookupHostname(target.IPString())), target.IPString(), "Actual user@host, Actual IP")
|
||||||
}
|
}
|
||||||
if target.flags[TLS] {
|
if target.flags[TLS] {
|
||||||
client.Send(nil, client.server.name, RPL_WHOISSECURE, client.nick, target.nick, "is using a secure connection")
|
rb.Add(nil, client.server.name, RPL_WHOISSECURE, client.nick, target.nick, "is using a secure connection")
|
||||||
}
|
}
|
||||||
if target.certfp != "" && (client.flags[Operator] || client == target) {
|
if target.certfp != "" && (client.flags[Operator] || client == target) {
|
||||||
client.Send(nil, client.server.name, RPL_WHOISCERTFP, client.nick, target.nick, fmt.Sprintf("has client certificate fingerprint %s", target.certfp))
|
rb.Add(nil, client.server.name, RPL_WHOISCERTFP, client.nick, target.nick, fmt.Sprintf("has client certificate fingerprint %s", target.certfp))
|
||||||
}
|
}
|
||||||
client.Send(nil, client.server.name, RPL_WHOISIDLE, client.nick, target.nick, strconv.FormatUint(target.IdleSeconds(), 10), strconv.FormatInt(target.SignonTime(), 10), "seconds idle, signon time")
|
rb.Add(nil, client.server.name, RPL_WHOISIDLE, client.nick, target.nick, strconv.FormatUint(target.IdleSeconds(), 10), strconv.FormatInt(target.SignonTime(), 10), "seconds idle, signon time")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RplWhoReplyNoMutex returns the WHO reply between one user and another channel/user.
|
// RplWhoReplyNoMutex returns the WHO reply between one user and another channel/user.
|
||||||
|
Loading…
Reference in New Issue
Block a user