| | @@ -103,10 +103,13 @@ |
| 103 | 103 | |
| 104 | 104 | msgTotal atomic.Int64 |
| 105 | 105 | |
| 106 | 106 | joinCh chan string |
| 107 | 107 | client *girc.Client |
| 108 | + |
| 109 | + // RELAYMSG support detected from ISUPPORT. |
| 110 | + relaySep string // separator (e.g. "/"), empty if unsupported |
| 108 | 111 | } |
| 109 | 112 | |
| 110 | 113 | // New creates a bridge Bot. |
| 111 | 114 | func New(ircAddr, nick, password string, channels []string, bufSize int, webUserTTL time.Duration, log *slog.Logger) *Bot { |
| 112 | 115 | if nick == "" { |
| | @@ -172,10 +175,22 @@ |
| 172 | 175 | PingTimeout: 30 * time.Second, |
| 173 | 176 | SSL: false, |
| 174 | 177 | }) |
| 175 | 178 | |
| 176 | 179 | c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) { |
| 180 | + // Check RELAYMSG support from ISUPPORT (RPL_005). |
| 181 | + if sep, ok := cl.GetServerOption("RELAYMSG"); ok && sep != "" { |
| 182 | + b.relaySep = sep |
| 183 | + if b.log != nil { |
| 184 | + b.log.Info("bridge: RELAYMSG supported", "separator", sep) |
| 185 | + } |
| 186 | + } else { |
| 187 | + b.relaySep = "" |
| 188 | + if b.log != nil { |
| 189 | + b.log.Info("bridge: RELAYMSG not supported, using [nick] prefix fallback") |
| 190 | + } |
| 191 | + } |
| 177 | 192 | if b.log != nil { |
| 178 | 193 | b.log.Info("bridge connected") |
| 179 | 194 | } |
| 180 | 195 | for _, ch := range b.initChannels { |
| 181 | 196 | cl.Cmd.Join(ch) |
| | @@ -338,19 +353,27 @@ |
| 338 | 353 | } |
| 339 | 354 | |
| 340 | 355 | // SendWithMeta sends a message to channel with optional structured metadata. |
| 341 | 356 | // IRC receives only the plain text; SSE subscribers receive the full message |
| 342 | 357 | // including meta for rich rendering in the web UI. |
| 358 | +// |
| 359 | +// When the server supports RELAYMSG (IRCv3), messages are attributed natively |
| 360 | +// so other clients see the real sender nick. Falls back to [nick] prefix. |
| 343 | 361 | func (b *Bot) SendWithMeta(ctx context.Context, channel, text, senderNick string, meta *Meta) error { |
| 344 | 362 | if b.client == nil { |
| 345 | 363 | return fmt.Errorf("bridge: not connected") |
| 346 | 364 | } |
| 347 | | - ircText := text |
| 348 | | - if senderNick != "" { |
| 349 | | - ircText = "[" + senderNick + "] " + text |
| 365 | + if senderNick != "" && b.relaySep != "" { |
| 366 | + // Use RELAYMSG for native attribution. |
| 367 | + b.client.Cmd.SendRawf("RELAYMSG %s %s :%s", channel, senderNick, text) |
| 368 | + } else { |
| 369 | + ircText := text |
| 370 | + if senderNick != "" { |
| 371 | + ircText = "[" + senderNick + "] " + text |
| 372 | + } |
| 373 | + b.client.Cmd.Message(channel, ircText) |
| 350 | 374 | } |
| 351 | | - b.client.Cmd.Message(channel, ircText) |
| 352 | 375 | |
| 353 | 376 | if senderNick != "" { |
| 354 | 377 | b.TouchUser(channel, senderNick) |
| 355 | 378 | } |
| 356 | 379 | |
| 357 | 380 | |