11from __future__ import annotations
22
33import base64
4+ import functools
45import threading
56import subprocess
67import platform
78import time
8- import functools
99import logging
1010import sys
1111from typing import Optional
1616from src .functions import get_participants , get_all_roles , match_role
1717from src .dispatcher import MessageDispatcher
1818from src .decorators import handle_error , command , hook
19- from src .context import Features
19+ from src .context import Features , NotLoggedIn
2020from src .users import User
2121from src .events import Event , EventListener
2222from src .transport .irc import get_services
2323from src .channels import Channel
2424
2525@handle_error
26- def on_privmsg (cli , rawnick , chan , msg , * , notice = False ):
26+ def on_privmsg (cli , rawnick , chan , msg , * , notice = False , tags = None ):
2727 if notice and "!" not in rawnick or not rawnick : # server notice; we don't care about those
2828 return
2929
3030 _ignore_locals_ = False
3131 if config .Main .get ("telemetry.errors.user_data_level" ) == 0 or config .Main .get ("telemetry.errors.channel_data_level" ) == 0 :
3232 _ignore_locals_ = True # don't expose in tb if we're trying to anonymize stuff
3333
34+ if tags is None :
35+ tags = {}
36+
3437 user = users .get (rawnick , allow_none = True )
38+ account_tag = tags .get ("account" , NotLoggedIn )
3539
3640 ch = chan .lstrip ("" .join (Features ["PREFIX" ]))
3741
@@ -43,6 +47,13 @@ def on_privmsg(cli, rawnick, chan, msg, *, notice=False):
4347 if user is None or target is None :
4448 return
4549
50+ if Features .account_tag and user .account != account_tag :
51+ old_account = user .account
52+ old_user = user
53+ user .account = account_tag
54+ user = users .get (user .nick , user .ident , user .host , account_tag )
55+ Event ("account_change" , {}, old = old_user ).dispatch (user , old_account )
56+
4657 wrapper = MessageDispatcher (user , target )
4758
4859 if wrapper .public and config .Main .get ("transports[0].user.ignore.hidden" ) and not chan .startswith (tuple (Features ["CHANTYPES" ])):
@@ -194,9 +205,9 @@ def parse_and_dispatch(wrapper: MessageDispatcher,
194205 if phase == cur_phase : # don't call any more commands if one we just called executed a phase transition
195206 fn .caller (dispatch , message )
196207
197- def unhandled (cli , prefix , cmd , * args ):
208+ def unhandled (cli , prefix , cmd , * args , tags ):
198209 for fn in decorators .HOOKS .get (cmd , []):
199- fn .caller (cli , prefix , * args )
210+ fn .caller (cli , prefix , * args , tags = tags )
200211
201212def ping_server (cli : IRCClient ):
202213 cli .send ("PING :{0}" .format (time .time ()))
@@ -206,7 +217,7 @@ def latency(wrapper, message):
206217 ping_server (wrapper .client )
207218
208219 @hook ("pong" , hookid = 300 )
209- def latency_pong (cli , server , target , ts ):
220+ def latency_pong (cli , server , target , ts , * , tags ):
210221 lat = round (time .time () - float (ts ), 3 )
211222 wrapper .reply (messages ["latency" ].format (lat ))
212223 hook .unhook (300 )
@@ -235,7 +246,7 @@ def connect_callback(cli: IRCClient):
235246
236247 @hook ("endofmotd" , hookid = 294 )
237248 @hook ("nomotd" , hookid = 294 )
238- def prepare_stuff (cli : IRCClient , prefix : str , * args : str ):
249+ def prepare_stuff (cli : IRCClient , prefix : str , * args , tags ):
239250 logger .info ("Received end of MOTD from {0}" , prefix )
240251
241252 # This callback only sets up event listeners
@@ -285,7 +296,7 @@ def setup_handler(evt, target: User | Channel):
285296 who_end = EventListener (setup_handler )
286297 who_end .install ("who_end" )
287298
288- def mustregain (cli : IRCClient , server , bot_nick , nick , msg ):
299+ def mustregain (cli : IRCClient , server , bot_nick , nick , msg , * , tags ):
289300 nonlocal regaincount
290301
291302 config_nick = config .Main .get ("transports[0].user.nick" )
@@ -303,7 +314,7 @@ def mustregain(cli: IRCClient, server, bot_nick, nick, msg):
303314 regaincount += 1
304315 users .Bot .change_nick (config_nick )
305316
306- def mustrelease (cli : IRCClient , server , bot_nick , nick , msg ):
317+ def mustrelease (cli : IRCClient , server , bot_nick , nick , msg , * , tags ):
307318 nonlocal releasecount
308319
309320 config_nick = config .Main .get ("transports[0].user.nick" )
@@ -331,7 +342,14 @@ def must_use_temp_nick(cli, *etc):
331342 if services .supports_regain () or services .supports_ghost ():
332343 hook ("nicknameinuse" , hookid = 241 )(mustregain )
333344
334- request_caps = {"account-notify" , "chghost" , "extended-join" , "multi-prefix" }
345+ request_caps = {
346+ "account-notify" ,
347+ "account-tag" ,
348+ "chghost" ,
349+ "extended-join" ,
350+ "message-tags" ,
351+ "multi-prefix" ,
352+ }
335353
336354 if config .Main .get ("transports[0].authentication.services.use_sasl" ):
337355 request_caps .add ("sasl" )
@@ -341,7 +359,7 @@ def must_use_temp_nick(cli, *etc):
341359 selected_sasl = None
342360
343361 @hook ("cap" )
344- def on_cap (cli : IRCClient , svr , mynick , cmd : str , * caps : str ):
362+ def on_cap (cli : IRCClient , svr , mynick , cmd : str , * caps : str , tags ):
345363 nonlocal supported_sasl , selected_sasl
346364 # caps is a star because we might receive multiline in LS
347365 if cmd == "LS" :
@@ -422,7 +440,7 @@ def on_cap(cli: IRCClient, svr, mynick, cmd: str, *caps: str):
422440
423441 if config .Main .get ("transports[0].authentication.services.use_sasl" ):
424442 @hook ("authenticate" )
425- def auth_plus (cli : IRCClient , _ , plus ):
443+ def auth_plus (cli : IRCClient , _ , plus , * , tags ):
426444 username : str = config .Main .get ("transports[0].authentication.services.username" )
427445 if not username :
428446 username = config .Main .get ("transports[0].user.nick" )
@@ -443,14 +461,14 @@ def auth_plus(cli: IRCClient, _, plus):
443461 cli .send ("AUTHENTICATE " + auth_token , log = "AUTHENTICATE [redacted]" )
444462
445463 @hook ("saslsuccess" )
446- def on_successful_auth (cli : IRCClient , * _ ):
464+ def on_successful_auth (cli : IRCClient , * args , tags ):
447465 Features ["sasl" ] = selected_sasl
448466 cli .send ("CAP END" )
449467
450468 @hook ("saslfail" )
451469 @hook ("sasltoolong" )
452470 @hook ("saslaborted" )
453- def on_failure_auth (cli : IRCClient , * _ ):
471+ def on_failure_auth (cli : IRCClient , * args , tags ):
454472 nonlocal selected_sasl
455473 if selected_sasl == "EXTERNAL" and (supported_sasl is None or "PLAIN" in supported_sasl ):
456474 # EXTERNAL failed, retry with PLAIN as we may not have set up the client cert yet
0 commit comments