check / check (push) Waiting to run
The IRC listener's relay dropped every line sent under the connection's own nick, so a user on the web client and an IRC client at once never saw on IRC what they did from the web client. The relay now drops nothing by nick. Instead the fan-out skips only the sending client's own queue: the IRC listener passes its own client, whose handler already wrote the echo, and the HTTP API skips none. Channel messages and PARTs now reach the sender's other clients, so an HTTP client gets its own PART event; neoirc-cli ignores it, as it already shows its own part. README.md names the commands covered. Judgement call: a PRIVMSG or NOTICE sent on an IRC connection is not echoed back to it, as in IRC. Model: opus-5-5
929 lines
16 KiB
Go
929 lines
16 KiB
Go
// Package cli implements the neoirc-cli terminal client.
|
|
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
api "sneak.berlin/go/neoirc/internal/cli/api"
|
|
"sneak.berlin/go/neoirc/pkg/irc"
|
|
)
|
|
|
|
const (
|
|
splitParts = 2
|
|
pollTimeout = 15
|
|
pollRetry = 2 * time.Second
|
|
timeFormat = "15:04"
|
|
)
|
|
|
|
// App holds the application state.
|
|
type App struct {
|
|
ui *UI
|
|
client *api.Client
|
|
|
|
mu sync.Mutex
|
|
nick string
|
|
target string
|
|
connected bool
|
|
lastQID int64
|
|
stopPoll chan struct{}
|
|
}
|
|
|
|
// Run creates and runs the CLI application.
|
|
func Run() {
|
|
app := &App{ //nolint:exhaustruct
|
|
ui: NewUI(),
|
|
nick: "guest",
|
|
}
|
|
|
|
app.ui.OnInput(app.handleInput)
|
|
app.ui.SetStatus(app.nick, "", "disconnected")
|
|
|
|
app.ui.AddStatus(
|
|
"Welcome to neoirc-cli — an IRC-style client",
|
|
)
|
|
app.ui.AddStatus(
|
|
"Type [yellow]/connect <server-url>" +
|
|
"[white] to begin, " +
|
|
"or [yellow]/help[white] for commands",
|
|
)
|
|
|
|
err := app.ui.Run()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func (a *App) handleInput(text string) {
|
|
if strings.HasPrefix(text, "/") {
|
|
a.handleCommand(text)
|
|
|
|
return
|
|
}
|
|
|
|
a.mu.Lock()
|
|
target := a.target
|
|
connected := a.connected
|
|
a.mu.Unlock()
|
|
|
|
if !connected {
|
|
a.ui.AddStatus(
|
|
"[red]Not connected. Use /connect <url>",
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
if target == "" {
|
|
a.ui.AddStatus(
|
|
"[red]No target. " +
|
|
"Use /join #channel or /query nick",
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
err := a.client.SendMessage(&api.Message{ //nolint:exhaustruct
|
|
Command: irc.CmdPrivmsg,
|
|
To: target,
|
|
Body: []string{text},
|
|
})
|
|
if err != nil {
|
|
a.ui.AddStatus(
|
|
"[red]Send error: " + err.Error(),
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
timestamp := time.Now().Format(timeFormat)
|
|
|
|
a.mu.Lock()
|
|
nick := a.nick
|
|
a.mu.Unlock()
|
|
|
|
a.ui.AddLine(target, fmt.Sprintf(
|
|
"[gray]%s [green]<%s>[white] %s",
|
|
timestamp, nick, text,
|
|
))
|
|
}
|
|
|
|
func (a *App) handleCommand(text string) {
|
|
parts := strings.SplitN(text, " ", splitParts)
|
|
cmd := strings.ToLower(parts[0])
|
|
|
|
args := ""
|
|
if len(parts) > 1 {
|
|
args = parts[1]
|
|
}
|
|
|
|
a.dispatchCommand(cmd, args)
|
|
}
|
|
|
|
func (a *App) dispatchCommand(cmd, args string) {
|
|
switch cmd {
|
|
case "/connect":
|
|
a.cmdConnect(args)
|
|
case "/nick":
|
|
a.cmdNick(args)
|
|
case "/join":
|
|
a.cmdJoin(args)
|
|
case "/part":
|
|
a.cmdPart(args)
|
|
case "/msg":
|
|
a.cmdMsg(args)
|
|
case "/query":
|
|
a.cmdQuery(args)
|
|
case "/topic":
|
|
a.cmdTopic(args)
|
|
case "/window", "/w":
|
|
a.cmdWindow(args)
|
|
case "/quit":
|
|
a.cmdQuit()
|
|
case "/help":
|
|
a.cmdHelp()
|
|
default:
|
|
a.dispatchInfoCommand(cmd, args)
|
|
}
|
|
}
|
|
|
|
func (a *App) dispatchInfoCommand(cmd, args string) {
|
|
switch cmd {
|
|
case "/names":
|
|
a.cmdNames()
|
|
case "/list":
|
|
a.cmdList()
|
|
case "/motd":
|
|
a.cmdMotd()
|
|
case "/who":
|
|
a.cmdWho(args)
|
|
case "/whois":
|
|
a.cmdWhois(args)
|
|
default:
|
|
a.ui.AddStatus(
|
|
"[red]Unknown command: " + cmd,
|
|
)
|
|
}
|
|
}
|
|
|
|
func (a *App) cmdConnect(serverURL string) {
|
|
if serverURL == "" {
|
|
a.ui.AddStatus(
|
|
"[red]Usage: /connect <server-url>",
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
serverURL = strings.TrimRight(serverURL, "/")
|
|
|
|
a.ui.AddStatus("Connecting to " + serverURL + "...")
|
|
|
|
a.mu.Lock()
|
|
nick := a.nick
|
|
a.mu.Unlock()
|
|
|
|
client := api.NewClient(serverURL)
|
|
|
|
resp, err := client.CreateSession(nick)
|
|
if err != nil {
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[red]Connection failed: %v", err,
|
|
))
|
|
|
|
return
|
|
}
|
|
|
|
a.mu.Lock()
|
|
a.client = client
|
|
a.nick = resp.Nick
|
|
a.connected = true
|
|
a.lastQID = 0
|
|
a.mu.Unlock()
|
|
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[green]Connected! Nick: %s, Session: %d",
|
|
resp.Nick, resp.ID,
|
|
))
|
|
a.ui.SetStatus(resp.Nick, "", "connected")
|
|
|
|
a.stopPoll = make(chan struct{})
|
|
|
|
go a.pollLoop()
|
|
}
|
|
|
|
func (a *App) cmdNick(nick string) {
|
|
if nick == "" {
|
|
a.ui.AddStatus(
|
|
"[red]Usage: /nick <name>",
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
a.mu.Lock()
|
|
connected := a.connected
|
|
a.mu.Unlock()
|
|
|
|
if !connected {
|
|
a.mu.Lock()
|
|
a.nick = nick
|
|
a.mu.Unlock()
|
|
|
|
a.ui.AddStatus(
|
|
"Nick set to " + nick +
|
|
" (will be used on connect)",
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
err := a.client.SendMessage(&api.Message{ //nolint:exhaustruct
|
|
Command: irc.CmdNick,
|
|
Body: []string{nick},
|
|
})
|
|
if err != nil {
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[red]Nick change failed: %v", err,
|
|
))
|
|
|
|
return
|
|
}
|
|
|
|
a.mu.Lock()
|
|
a.nick = nick
|
|
target := a.target
|
|
a.mu.Unlock()
|
|
|
|
a.ui.SetStatus(nick, target, "connected")
|
|
a.ui.AddStatus("Nick changed to " + nick)
|
|
}
|
|
|
|
func (a *App) cmdJoin(channel string) {
|
|
if channel == "" {
|
|
a.ui.AddStatus(
|
|
"[red]Usage: /join #channel",
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
if !strings.HasPrefix(channel, "#") {
|
|
channel = "#" + channel
|
|
}
|
|
|
|
a.mu.Lock()
|
|
connected := a.connected
|
|
a.mu.Unlock()
|
|
|
|
if !connected {
|
|
a.ui.AddStatus("[red]Not connected")
|
|
|
|
return
|
|
}
|
|
|
|
err := a.client.JoinChannel(channel)
|
|
if err != nil {
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[red]Join failed: %v", err,
|
|
))
|
|
|
|
return
|
|
}
|
|
|
|
a.mu.Lock()
|
|
a.target = channel
|
|
nick := a.nick
|
|
a.mu.Unlock()
|
|
|
|
a.ui.SwitchToBuffer(channel)
|
|
a.ui.AddLine(channel,
|
|
"[yellow]*** Joined "+channel,
|
|
)
|
|
a.ui.SetStatus(nick, channel, "connected")
|
|
}
|
|
|
|
func (a *App) cmdPart(channel string) {
|
|
a.mu.Lock()
|
|
if channel == "" {
|
|
channel = a.target
|
|
}
|
|
|
|
connected := a.connected
|
|
a.mu.Unlock()
|
|
|
|
if channel == "" ||
|
|
!strings.HasPrefix(channel, "#") {
|
|
a.ui.AddStatus("[red]No channel to part")
|
|
|
|
return
|
|
}
|
|
|
|
if !connected {
|
|
a.ui.AddStatus("[red]Not connected")
|
|
|
|
return
|
|
}
|
|
|
|
err := a.client.PartChannel(channel)
|
|
if err != nil {
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[red]Part failed: %v", err,
|
|
))
|
|
|
|
return
|
|
}
|
|
|
|
a.ui.AddLine(channel,
|
|
"[yellow]*** Left "+channel,
|
|
)
|
|
|
|
a.mu.Lock()
|
|
if a.target == channel {
|
|
a.target = ""
|
|
}
|
|
|
|
nick := a.nick
|
|
a.mu.Unlock()
|
|
|
|
a.ui.SwitchBuffer(0)
|
|
a.ui.SetStatus(nick, "", "connected")
|
|
}
|
|
|
|
func (a *App) cmdMsg(args string) {
|
|
parts := strings.SplitN(args, " ", splitParts)
|
|
if len(parts) < splitParts {
|
|
a.ui.AddStatus(
|
|
"[red]Usage: /msg <nick> <text>",
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
target, text := parts[0], parts[1]
|
|
|
|
a.mu.Lock()
|
|
connected := a.connected
|
|
nick := a.nick
|
|
a.mu.Unlock()
|
|
|
|
if !connected {
|
|
a.ui.AddStatus("[red]Not connected")
|
|
|
|
return
|
|
}
|
|
|
|
err := a.client.SendMessage(&api.Message{ //nolint:exhaustruct
|
|
Command: irc.CmdPrivmsg,
|
|
To: target,
|
|
Body: []string{text},
|
|
})
|
|
if err != nil {
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[red]Send failed: %v", err,
|
|
))
|
|
|
|
return
|
|
}
|
|
|
|
timestamp := time.Now().Format(timeFormat)
|
|
|
|
a.ui.AddLine(target, fmt.Sprintf(
|
|
"[gray]%s [green]<%s>[white] %s",
|
|
timestamp, nick, text,
|
|
))
|
|
}
|
|
|
|
func (a *App) cmdQuery(nick string) {
|
|
if nick == "" {
|
|
a.ui.AddStatus(
|
|
"[red]Usage: /query <nick>",
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
a.mu.Lock()
|
|
a.target = nick
|
|
myNick := a.nick
|
|
a.mu.Unlock()
|
|
|
|
a.ui.SwitchToBuffer(nick)
|
|
a.ui.SetStatus(myNick, nick, "connected")
|
|
}
|
|
|
|
func (a *App) cmdTopic(args string) {
|
|
a.mu.Lock()
|
|
target := a.target
|
|
connected := a.connected
|
|
a.mu.Unlock()
|
|
|
|
if !connected {
|
|
a.ui.AddStatus("[red]Not connected")
|
|
|
|
return
|
|
}
|
|
|
|
if !strings.HasPrefix(target, "#") {
|
|
a.ui.AddStatus("[red]Not in a channel")
|
|
|
|
return
|
|
}
|
|
|
|
// The HTTP API has no topic query: a TOPIC needs text.
|
|
if args == "" {
|
|
a.ui.AddStatus("[red]Usage: /topic <text>")
|
|
|
|
return
|
|
}
|
|
|
|
err := a.client.SendMessage(&api.Message{ //nolint:exhaustruct
|
|
Command: irc.CmdTopic,
|
|
To: target,
|
|
Body: []string{args},
|
|
})
|
|
if err != nil {
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[red]Topic set failed: %v", err,
|
|
))
|
|
}
|
|
}
|
|
|
|
func (a *App) cmdNames() {
|
|
a.mu.Lock()
|
|
target := a.target
|
|
connected := a.connected
|
|
a.mu.Unlock()
|
|
|
|
if !connected {
|
|
a.ui.AddStatus("[red]Not connected")
|
|
|
|
return
|
|
}
|
|
|
|
if !strings.HasPrefix(target, "#") {
|
|
a.ui.AddStatus("[red]Not in a channel")
|
|
|
|
return
|
|
}
|
|
|
|
members, err := a.client.GetMembers(target)
|
|
if err != nil {
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[red]Names failed: %v", err,
|
|
))
|
|
|
|
return
|
|
}
|
|
|
|
nicks := make([]string, 0, len(members))
|
|
|
|
for _, member := range members {
|
|
nicks = append(nicks, memberPrefix(member)+member.Nick)
|
|
}
|
|
|
|
a.ui.AddLine(target, fmt.Sprintf(
|
|
"[cyan]*** Members of %s: %s",
|
|
target, strings.Join(nicks, " "),
|
|
))
|
|
}
|
|
|
|
// memberPrefix returns "@" for an operator, "+" for a voiced
|
|
// member, and "" for anyone else.
|
|
func memberPrefix(member api.Member) string {
|
|
if member.IsOperator {
|
|
return "@"
|
|
}
|
|
|
|
if member.IsVoiced {
|
|
return "+"
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func (a *App) cmdList() {
|
|
a.mu.Lock()
|
|
connected := a.connected
|
|
a.mu.Unlock()
|
|
|
|
if !connected {
|
|
a.ui.AddStatus("[red]Not connected")
|
|
|
|
return
|
|
}
|
|
|
|
channels, err := a.client.ListChannels()
|
|
if err != nil {
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[red]List failed: %v", err,
|
|
))
|
|
|
|
return
|
|
}
|
|
|
|
a.ui.AddStatus("[cyan]*** Channel list:")
|
|
|
|
for _, ch := range channels {
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
" %s %s", ch.Name, ch.Topic,
|
|
))
|
|
}
|
|
|
|
a.ui.AddStatus("[cyan]*** End of channel list")
|
|
}
|
|
|
|
func (a *App) cmdMotd() {
|
|
a.mu.Lock()
|
|
connected := a.connected
|
|
a.mu.Unlock()
|
|
|
|
if !connected {
|
|
a.ui.AddStatus("[red]Not connected")
|
|
|
|
return
|
|
}
|
|
|
|
err := a.client.SendMessage(
|
|
&api.Message{Command: irc.CmdMotd}, //nolint:exhaustruct
|
|
)
|
|
if err != nil {
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[red]MOTD failed: %v", err,
|
|
))
|
|
}
|
|
}
|
|
|
|
func (a *App) cmdWho(args string) {
|
|
a.mu.Lock()
|
|
connected := a.connected
|
|
target := a.target
|
|
a.mu.Unlock()
|
|
|
|
if !connected {
|
|
a.ui.AddStatus("[red]Not connected")
|
|
|
|
return
|
|
}
|
|
|
|
channel := args
|
|
if channel == "" {
|
|
channel = target
|
|
}
|
|
|
|
if channel == "" ||
|
|
!strings.HasPrefix(channel, "#") {
|
|
a.ui.AddStatus(
|
|
"[red]Usage: /who #channel",
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
err := a.client.SendMessage(
|
|
&api.Message{ //nolint:exhaustruct
|
|
Command: irc.CmdWho, To: channel,
|
|
},
|
|
)
|
|
if err != nil {
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[red]WHO failed: %v", err,
|
|
))
|
|
}
|
|
}
|
|
|
|
func (a *App) cmdWhois(args string) {
|
|
a.mu.Lock()
|
|
connected := a.connected
|
|
a.mu.Unlock()
|
|
|
|
if !connected {
|
|
a.ui.AddStatus("[red]Not connected")
|
|
|
|
return
|
|
}
|
|
|
|
if args == "" {
|
|
a.ui.AddStatus(
|
|
"[red]Usage: /whois <nick>",
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
err := a.client.SendMessage(
|
|
&api.Message{ //nolint:exhaustruct
|
|
Command: irc.CmdWhois, To: args,
|
|
},
|
|
)
|
|
if err != nil {
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[red]WHOIS failed: %v", err,
|
|
))
|
|
}
|
|
}
|
|
|
|
func (a *App) cmdWindow(args string) {
|
|
if args == "" {
|
|
a.ui.AddStatus(
|
|
"[red]Usage: /window <number>",
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
var bufIndex int
|
|
|
|
_, _ = fmt.Sscanf(args, "%d", &bufIndex)
|
|
|
|
a.ui.SwitchBuffer(bufIndex)
|
|
|
|
a.mu.Lock()
|
|
nick := a.nick
|
|
a.mu.Unlock()
|
|
|
|
if bufIndex >= 0 && bufIndex < a.ui.BufferCount() {
|
|
buf := a.ui.buffers[bufIndex]
|
|
if buf.Name != "(status)" {
|
|
a.mu.Lock()
|
|
a.target = buf.Name
|
|
a.mu.Unlock()
|
|
|
|
a.ui.SetStatus(
|
|
nick, buf.Name, "connected",
|
|
)
|
|
} else {
|
|
a.ui.SetStatus(nick, "", "connected")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a *App) cmdQuit() {
|
|
a.mu.Lock()
|
|
|
|
if a.connected && a.client != nil {
|
|
_ = a.client.SendMessage(
|
|
&api.Message{Command: irc.CmdQuit}, //nolint:exhaustruct
|
|
)
|
|
}
|
|
|
|
if a.stopPoll != nil {
|
|
close(a.stopPoll)
|
|
}
|
|
|
|
a.mu.Unlock()
|
|
a.ui.Stop()
|
|
}
|
|
|
|
func (a *App) cmdHelp() {
|
|
help := []string{
|
|
"[cyan]*** neoirc-cli commands:",
|
|
" /connect <url> — Connect to server",
|
|
" /nick <name> — Change nickname",
|
|
" /join #channel — Join channel",
|
|
" /part [#chan] — Leave channel",
|
|
" /msg <nick> <text> — Send DM",
|
|
" /query <nick> — Open DM window",
|
|
" /topic <text> — Set topic",
|
|
" /names — List channel members",
|
|
" /list — List channels",
|
|
" /who [#channel] — List users in channel",
|
|
" /whois <nick> — Show user info",
|
|
" /motd — Show message of the day",
|
|
" /window <n> — Switch buffer",
|
|
" /quit — Disconnect and exit",
|
|
" /help — This help",
|
|
" Plain text sends to current target.",
|
|
}
|
|
|
|
for _, line := range help {
|
|
a.ui.AddStatus(line)
|
|
}
|
|
}
|
|
|
|
// pollLoop long-polls for messages in the background.
|
|
func (a *App) pollLoop() {
|
|
for {
|
|
select {
|
|
case <-a.stopPoll:
|
|
return
|
|
default:
|
|
}
|
|
|
|
a.mu.Lock()
|
|
client := a.client
|
|
lastQID := a.lastQID
|
|
a.mu.Unlock()
|
|
|
|
if client == nil {
|
|
return
|
|
}
|
|
|
|
result, err := client.PollMessages(
|
|
lastQID, pollTimeout,
|
|
)
|
|
if err != nil {
|
|
time.Sleep(pollRetry)
|
|
|
|
continue
|
|
}
|
|
|
|
if result.LastID > 0 {
|
|
a.mu.Lock()
|
|
a.lastQID = result.LastID
|
|
a.mu.Unlock()
|
|
}
|
|
|
|
for i := range result.Messages {
|
|
a.handleServerMessage(&result.Messages[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a *App) handleServerMessage(msg *api.Message) {
|
|
timestamp := a.formatTS(msg)
|
|
|
|
a.mu.Lock()
|
|
myNick := a.nick
|
|
a.mu.Unlock()
|
|
|
|
switch msg.Command {
|
|
case irc.CmdPrivmsg:
|
|
a.handlePrivmsgEvent(msg, timestamp, myNick)
|
|
case irc.CmdJoin:
|
|
a.handleJoinEvent(msg, timestamp)
|
|
case irc.CmdPart:
|
|
a.handlePartEvent(msg, timestamp, myNick)
|
|
case irc.CmdQuit:
|
|
a.handleQuitEvent(msg, timestamp)
|
|
case irc.CmdNick:
|
|
a.handleNickEvent(msg, timestamp, myNick)
|
|
case irc.CmdNotice:
|
|
a.handleNoticeEvent(msg, timestamp)
|
|
case irc.CmdTopic:
|
|
a.handleTopicEvent(msg, timestamp)
|
|
default:
|
|
a.handleDefaultEvent(msg, timestamp)
|
|
}
|
|
}
|
|
|
|
func (a *App) formatTS(msg *api.Message) string {
|
|
if msg.TS != "" {
|
|
return msg.ParseTS().UTC().Format(timeFormat)
|
|
}
|
|
|
|
return time.Now().Format(timeFormat)
|
|
}
|
|
|
|
func (a *App) handlePrivmsgEvent(
|
|
msg *api.Message, timestamp, myNick string,
|
|
) {
|
|
lines := msg.BodyLines()
|
|
text := strings.Join(lines, " ")
|
|
|
|
if msg.From == myNick {
|
|
return
|
|
}
|
|
|
|
target := msg.To
|
|
if !strings.HasPrefix(target, "#") {
|
|
target = msg.From
|
|
}
|
|
|
|
a.ui.AddLine(target, fmt.Sprintf(
|
|
"[gray]%s [green]<%s>[white] %s",
|
|
timestamp, msg.From, text,
|
|
))
|
|
}
|
|
|
|
func (a *App) handleJoinEvent(
|
|
msg *api.Message, timestamp string,
|
|
) {
|
|
if msg.To == "" {
|
|
return
|
|
}
|
|
|
|
a.ui.AddLine(msg.To, fmt.Sprintf(
|
|
"[gray]%s [yellow]*** %s has joined %s",
|
|
timestamp, msg.From, msg.To,
|
|
))
|
|
}
|
|
|
|
// handlePartEvent shows another user's part. The server
|
|
// also sends the user's own part back to it, which cmdPart
|
|
// has already shown; showing it again would add a second
|
|
// line and mark the channel just left as unread.
|
|
func (a *App) handlePartEvent(
|
|
msg *api.Message, timestamp, myNick string,
|
|
) {
|
|
if msg.To == "" || msg.From == myNick {
|
|
return
|
|
}
|
|
|
|
lines := msg.BodyLines()
|
|
reason := strings.Join(lines, " ")
|
|
|
|
if reason != "" {
|
|
a.ui.AddLine(msg.To, fmt.Sprintf(
|
|
"[gray]%s [yellow]*** %s has left %s (%s)",
|
|
timestamp, msg.From, msg.To, reason,
|
|
))
|
|
} else {
|
|
a.ui.AddLine(msg.To, fmt.Sprintf(
|
|
"[gray]%s [yellow]*** %s has left %s",
|
|
timestamp, msg.From, msg.To,
|
|
))
|
|
}
|
|
}
|
|
|
|
func (a *App) handleQuitEvent(
|
|
msg *api.Message, timestamp string,
|
|
) {
|
|
lines := msg.BodyLines()
|
|
reason := strings.Join(lines, " ")
|
|
|
|
if reason != "" {
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[gray]%s [yellow]*** %s has quit (%s)",
|
|
timestamp, msg.From, reason,
|
|
))
|
|
} else {
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[gray]%s [yellow]*** %s has quit",
|
|
timestamp, msg.From,
|
|
))
|
|
}
|
|
}
|
|
|
|
func (a *App) handleNickEvent(
|
|
msg *api.Message, timestamp, myNick string,
|
|
) {
|
|
lines := msg.BodyLines()
|
|
|
|
newNick := ""
|
|
if len(lines) > 0 {
|
|
newNick = lines[0]
|
|
}
|
|
|
|
if msg.From == myNick && newNick != "" {
|
|
a.mu.Lock()
|
|
a.nick = newNick
|
|
|
|
target := a.target
|
|
a.mu.Unlock()
|
|
|
|
a.ui.SetStatus(newNick, target, "connected")
|
|
}
|
|
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[gray]%s [yellow]*** %s is now known as %s",
|
|
timestamp, msg.From, newNick,
|
|
))
|
|
}
|
|
|
|
func (a *App) handleNoticeEvent(
|
|
msg *api.Message, timestamp string,
|
|
) {
|
|
lines := msg.BodyLines()
|
|
text := strings.Join(lines, " ")
|
|
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[gray]%s [magenta]--%s-- %s",
|
|
timestamp, msg.From, text,
|
|
))
|
|
}
|
|
|
|
func (a *App) handleTopicEvent(
|
|
msg *api.Message, timestamp string,
|
|
) {
|
|
if msg.To == "" {
|
|
return
|
|
}
|
|
|
|
lines := msg.BodyLines()
|
|
text := strings.Join(lines, " ")
|
|
|
|
a.ui.AddLine(msg.To, fmt.Sprintf(
|
|
"[gray]%s [cyan]*** %s set topic: %s",
|
|
timestamp, msg.From, text,
|
|
))
|
|
}
|
|
|
|
func (a *App) handleDefaultEvent(
|
|
msg *api.Message, timestamp string,
|
|
) {
|
|
lines := msg.BodyLines()
|
|
text := strings.Join(lines, " ")
|
|
|
|
if text != "" {
|
|
a.ui.AddStatus(fmt.Sprintf(
|
|
"[gray]%s [white][%s] %s",
|
|
timestamp, msg.Command, text,
|
|
))
|
|
}
|
|
}
|