omlish 0.0.0.dev222__py3-none-any.whl → 0.0.0.dev224__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. omlish/__about__.py +4 -4
  2. omlish/asyncs/asyncio/subprocesses.py +15 -23
  3. omlish/dynamic.py +1 -1
  4. omlish/funcs/genmachine.py +1 -1
  5. omlish/http/coro/fdio.py +3 -3
  6. omlish/http/coro/server.py +1 -1
  7. omlish/http/handlers.py +65 -0
  8. omlish/http/parsing.py +13 -0
  9. omlish/lite/marshal.py +22 -6
  10. omlish/lite/timing.py +8 -0
  11. omlish/logs/timing.py +58 -0
  12. omlish/secrets/tempssl.py +43 -21
  13. omlish/specs/irc/messages/__init__.py +0 -0
  14. omlish/specs/irc/messages/base.py +49 -0
  15. omlish/specs/irc/messages/formats.py +92 -0
  16. omlish/specs/irc/messages/messages.py +774 -0
  17. omlish/specs/irc/messages/parsing.py +98 -0
  18. omlish/specs/irc/numerics/numerics.py +57 -0
  19. omlish/subprocesses.py +107 -0
  20. {omlish-0.0.0.dev222.dist-info → omlish-0.0.0.dev224.dist-info}/METADATA +5 -5
  21. {omlish-0.0.0.dev222.dist-info → omlish-0.0.0.dev224.dist-info}/RECORD +35 -28
  22. /omlish/specs/irc/{format → protocol}/LICENSE +0 -0
  23. /omlish/specs/irc/{format → protocol}/__init__.py +0 -0
  24. /omlish/specs/irc/{format → protocol}/consts.py +0 -0
  25. /omlish/specs/irc/{format → protocol}/errors.py +0 -0
  26. /omlish/specs/irc/{format → protocol}/message.py +0 -0
  27. /omlish/specs/irc/{format → protocol}/nuh.py +0 -0
  28. /omlish/specs/irc/{format → protocol}/parsing.py +0 -0
  29. /omlish/specs/irc/{format → protocol}/rendering.py +0 -0
  30. /omlish/specs/irc/{format → protocol}/tags.py +0 -0
  31. /omlish/specs/irc/{format → protocol}/utils.py +0 -0
  32. {omlish-0.0.0.dev222.dist-info → omlish-0.0.0.dev224.dist-info}/LICENSE +0 -0
  33. {omlish-0.0.0.dev222.dist-info → omlish-0.0.0.dev224.dist-info}/WHEEL +0 -0
  34. {omlish-0.0.0.dev222.dist-info → omlish-0.0.0.dev224.dist-info}/entry_points.txt +0 -0
  35. {omlish-0.0.0.dev222.dist-info → omlish-0.0.0.dev224.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,774 @@
1
+ """
2
+ TODO:
3
+ - PONG - optional *before* required ...
4
+ - CAPABILITIES - ':' last param
5
+
6
+ ==
7
+
8
+ https://datatracker.ietf.org/doc/html/rfc2812
9
+ https://modern.ircdocs.horse/
10
+ """
11
+ import typing as ta
12
+
13
+ from .... import check
14
+ from .... import dataclasses as dc
15
+ from ..numerics import numerics as nr
16
+ from .base import Message
17
+ from .base import MessageFormat
18
+ from .base import list_pair_params_unpacker
19
+
20
+
21
+ #
22
+
23
+
24
+ _REGISTERED_IRC_MESSAGES_BY_NAME: dict[str, list[type['Message']]] = {}
25
+
26
+
27
+ def _register_irc_message(cls):
28
+ check.issubclass(cls, Message)
29
+ _REGISTERED_IRC_MESSAGES_BY_NAME.setdefault(cls.FORMAT.name, []).append(cls)
30
+ return cls
31
+
32
+
33
+ ##
34
+ # Connection Messages
35
+
36
+
37
+ @_register_irc_message
38
+ class CapMessage(Message):
39
+ FORMAT = MessageFormat.of(
40
+ 'CAP',
41
+ 'subcommand',
42
+ 'capabilities',
43
+ )
44
+
45
+ subcommand: str
46
+ capabilities: ta.Sequence[str]
47
+
48
+
49
+ @_register_irc_message
50
+ class AuthenticateMessage(Message):
51
+ FORMAT = MessageFormat.of('AUTHENTICATE')
52
+
53
+
54
+ @_register_irc_message
55
+ class PassMessage(Message):
56
+ FORMAT = MessageFormat.of(
57
+ 'PASS',
58
+ 'password',
59
+ )
60
+
61
+ REPLIES = (
62
+ nr.ERR_NEEDMOREPARAMS,
63
+ nr.ERR_ALREADYREGISTERED,
64
+ nr.ERR_PASSWDMISMATCH,
65
+ )
66
+
67
+ password: str
68
+
69
+
70
+ @_register_irc_message
71
+ class NickMessage(Message):
72
+ FORMAT = MessageFormat.of(
73
+ 'NICK',
74
+ 'nickname',
75
+ )
76
+
77
+ REPLIES = (
78
+ nr.ERR_NONICKNAMEGIVEN,
79
+ nr.ERR_ERRONEUSNICKNAME,
80
+ nr.ERR_NICKNAMEINUSE,
81
+ nr.ERR_NICKCOLLISION,
82
+ )
83
+
84
+ nickname: str
85
+
86
+
87
+ class UserMessage(Message):
88
+ FORMAT = MessageFormat.of(
89
+ 'USER',
90
+ 'username',
91
+ 'param1', # 0
92
+ 'param2', # *
93
+ 'realname',
94
+ )
95
+
96
+ REPLIES = (
97
+ nr.ERR_NEEDMOREPARAMS,
98
+ nr.ERR_ALREADYREGISTERED,
99
+ )
100
+
101
+ username: str
102
+ realname: str
103
+
104
+ param1: str = '8'
105
+ param2: str = '*'
106
+
107
+
108
+ @_register_irc_message
109
+ class PingMessage(Message):
110
+ FORMAT = MessageFormat.of(
111
+ 'PING',
112
+ 'token',
113
+ )
114
+
115
+ REPLIES = (
116
+ nr.ERR_NEEDMOREPARAMS,
117
+ nr.ERR_NOORIGIN,
118
+ nr.ERR_NOSUCHSERVER,
119
+ )
120
+
121
+ token: str
122
+
123
+
124
+ @_register_irc_message
125
+ class PongMessage(Message):
126
+ FORMAT = MessageFormat.of(
127
+ 'PONG',
128
+ '?server',
129
+ 'token',
130
+ )
131
+
132
+ token: str
133
+ server: str | None = None
134
+
135
+
136
+ @_register_irc_message
137
+ class OperMessage(Message):
138
+ FORMAT = MessageFormat.of(
139
+ 'OPER',
140
+ 'name',
141
+ 'password',
142
+ )
143
+
144
+ REPLIES = (
145
+ nr.ERR_NEEDMOREPARAMS,
146
+ nr.ERR_PASSWDMISMATCH,
147
+ nr.ERR_NOOPERHOST,
148
+ nr.RPL_YOUREOPER,
149
+ )
150
+
151
+ name: str
152
+ password: str
153
+
154
+
155
+ @_register_irc_message
156
+ class QuitMessage(Message):
157
+ FORMAT = MessageFormat.of(
158
+ 'QUIT',
159
+ '?reason',
160
+ )
161
+
162
+ reason: str | None = None
163
+
164
+
165
+ @_register_irc_message
166
+ class ErrorMessage(Message):
167
+ FORMAT = MessageFormat.of(
168
+ 'ERROR',
169
+ '?reason',
170
+ )
171
+
172
+ reason: str | None = None
173
+
174
+
175
+ ##
176
+ # Channel Operations
177
+
178
+
179
+ @_register_irc_message
180
+ class JoinMessage(Message):
181
+ FORMAT = MessageFormat.of(
182
+ 'JOIN',
183
+ ','
184
+ 'channels', '?,keys',
185
+ unpack_params=list_pair_params_unpacker(
186
+ 'channels',
187
+ 'channels',
188
+ 'keys',
189
+ ),
190
+ )
191
+
192
+ REPLIES = (
193
+ nr.ERR_NEEDMOREPARAMS,
194
+ nr.ERR_NOSUCHCHANNEL,
195
+ nr.ERR_TOOMANYCHANNELS,
196
+ nr.ERR_BADCHANNELKEY,
197
+ nr.ERR_BANNEDFROMCHAN,
198
+ nr.ERR_CHANNELISFULL,
199
+ nr.ERR_INVITEONLYCHAN,
200
+ nr.ERR_BADCHANMASK,
201
+ nr.RPL_TOPIC,
202
+ nr.RPL_TOPICWHOTIME,
203
+ nr.RPL_NAMREPLY,
204
+ nr.RPL_ENDOFNAMES,
205
+ )
206
+
207
+ channels: ta.Sequence[str | tuple[str, str]]
208
+
209
+ @property
210
+ def has_keys(self) -> bool:
211
+ return check.single({isinstance(e, tuple) for e in self.channels})
212
+
213
+ @dc.init
214
+ def _validate_channels(self) -> None:
215
+ check.not_isinstance(self.channels, str)
216
+ if self.has_keys:
217
+ for c, _ in self.channels: # type: ignore[misc]
218
+ check.non_empty_str(c)
219
+ else:
220
+ for c in self.channels:
221
+ check.non_empty_str(c) # type: ignore
222
+
223
+
224
+ @_register_irc_message
225
+ class LeaveAllJoinMessage(Message):
226
+ FORMAT = MessageFormat.of(
227
+ 'JOIN',
228
+ MessageFormat.LiteralParam('0'),
229
+ )
230
+
231
+ REPLIES = JoinMessage.REPLIES
232
+
233
+
234
+ @_register_irc_message
235
+ class PartMessage(Message):
236
+ FORMAT = MessageFormat.of(
237
+ 'PART',
238
+ ',channels',
239
+ '?reason',
240
+ )
241
+
242
+ REPLIES = (
243
+ nr.ERR_NEEDMOREPARAMS,
244
+ nr.ERR_NOSUCHCHANNEL,
245
+ nr.ERR_NOTONCHANNEL,
246
+ )
247
+
248
+ channels: ta.Sequence[str] = dc.xfield(validate=check.of_not_isinstance(str))
249
+ reason: str | None = None
250
+
251
+
252
+ @_register_irc_message
253
+ class TopicMessage(Message):
254
+ FORMAT = MessageFormat.of(
255
+ 'TOPIC',
256
+ 'channel',
257
+ '?topic',
258
+ )
259
+
260
+ REPLIES = (
261
+ nr.ERR_NEEDMOREPARAMS,
262
+ nr.ERR_NOSUCHCHANNEL,
263
+ nr.ERR_NOTONCHANNEL,
264
+ nr.ERR_CHANOPRIVSNEEDED,
265
+ nr.RPL_NOTOPIC,
266
+ nr.RPL_TOPIC,
267
+ nr.RPL_TOPICWHOTIME,
268
+ )
269
+
270
+ channel: str
271
+ topic: str | None = None
272
+
273
+
274
+ @_register_irc_message
275
+ class NamesMessage(Message):
276
+ FORMAT = MessageFormat.of(
277
+ 'NAMES',
278
+ ',channels',
279
+ )
280
+
281
+ REPLIES = (
282
+ nr.RPL_NAMREPLY,
283
+ nr.RPL_ENDOFNAMES,
284
+ )
285
+
286
+ channels: ta.Sequence[str] = dc.xfield(validate=check.of_not_isinstance(str))
287
+
288
+
289
+ @_register_irc_message
290
+ class ListMessage(Message):
291
+ FORMAT = MessageFormat.of(
292
+ 'LIST',
293
+ '?,channels',
294
+ '?,elistconds',
295
+ )
296
+
297
+ REPLIES = (
298
+ nr.RPL_LISTSTART,
299
+ nr.RPL_LIST,
300
+ nr.RPL_LISTEND,
301
+ )
302
+
303
+ channels: ta.Sequence[str]
304
+ elistconds: ta.Sequence[str]
305
+
306
+
307
+ @_register_irc_message
308
+ class InviteMessage(Message):
309
+ FORMAT = MessageFormat.of(
310
+ 'INVITE',
311
+ 'nickname',
312
+ 'channel',
313
+ )
314
+
315
+ REPLIES = (
316
+ nr.RPL_INVITING,
317
+ nr.ERR_NEEDMOREPARAMS,
318
+ nr.ERR_NOSUCHCHANNEL,
319
+ nr.ERR_NOTONCHANNEL,
320
+ nr.ERR_CHANOPRIVSNEEDED,
321
+ nr.ERR_USERONCHANNEL,
322
+ )
323
+
324
+ nickname: str
325
+ channel: str
326
+
327
+
328
+ @_register_irc_message
329
+ class KickMessage(Message):
330
+ FORMAT = MessageFormat.of(
331
+ 'KICK',
332
+ 'channel',
333
+ ',users',
334
+ '?comment',
335
+ )
336
+
337
+ REPLIES = (
338
+ nr.ERR_NEEDMOREPARAMS,
339
+ nr.ERR_NOSUCHCHANNEL,
340
+ nr.ERR_CHANOPRIVSNEEDED,
341
+ nr.ERR_USERNOTINCHANNEL,
342
+ nr.ERR_NOTONCHANNEL,
343
+ nr.ERR_BADCHANMASK,
344
+ )
345
+
346
+ channel: str
347
+ users: ta.Sequence[str]
348
+ comment: str | None = None
349
+
350
+
351
+ ##
352
+ # Server Queries and Commands
353
+
354
+
355
+ @_register_irc_message
356
+ class MotdMessage(Message):
357
+ FORMAT = MessageFormat.of(
358
+ 'MOTD',
359
+ '?target',
360
+ )
361
+
362
+ REPLIES = (
363
+ nr.ERR_NOSUCHSERVER,
364
+ nr.ERR_NOMOTD,
365
+ nr.RPL_MOTDSTART,
366
+ nr.RPL_MOTD,
367
+ nr.RPL_ENDOFMOTD,
368
+ )
369
+
370
+ target: str | None = None
371
+
372
+
373
+ @_register_irc_message
374
+ class VersionMessage(Message):
375
+ FORMAT = MessageFormat.of(
376
+ 'VERSION',
377
+ '?target',
378
+ )
379
+
380
+ REPLIES = (
381
+ nr.ERR_NOSUCHSERVER,
382
+ nr.RPL_ISUPPORT,
383
+ nr.RPL_VERSION,
384
+ )
385
+
386
+ target: str | None = None
387
+
388
+
389
+ @_register_irc_message
390
+ class AdminMessage(Message):
391
+ FORMAT = MessageFormat.of(
392
+ 'ADMIN',
393
+ '?target',
394
+ )
395
+
396
+ REPLIES = (
397
+ nr.ERR_NOSUCHSERVER,
398
+ nr.RPL_ADMINME,
399
+ nr.RPL_ADMINLOC1,
400
+ nr.RPL_ADMINLOC2,
401
+ nr.RPL_ADMINEMAIL,
402
+ )
403
+
404
+ target: str | None = None
405
+
406
+
407
+ @_register_irc_message
408
+ class ConnectMessage(Message):
409
+ FORMAT = MessageFormat.of(
410
+ 'CONNECT',
411
+ 'target_server',
412
+ '?port',
413
+ '?remote_server',
414
+ )
415
+
416
+ REPLIES = (
417
+ nr.ERR_NOSUCHSERVER,
418
+ nr.ERR_NEEDMOREPARAMS,
419
+ nr.ERR_NOPRIVILEGES,
420
+ nr.ERR_NOPRIVS,
421
+ )
422
+
423
+ target_server: str
424
+ port: str | None = None
425
+ remote_server: str | None = None
426
+
427
+
428
+ @_register_irc_message
429
+ class LusersMessage(Message):
430
+ FORMAT = MessageFormat.of('LUSERS')
431
+
432
+ REPLIES = (
433
+ nr.RPL_LUSERCLIENT,
434
+ nr.RPL_LUSEROP,
435
+ nr.RPL_LUSERUNKNOWN,
436
+ nr.RPL_LUSERCHANNELS,
437
+ nr.RPL_LUSERME,
438
+ nr.RPL_LOCALUSERS,
439
+ nr.RPL_GLOBALUSERS,
440
+ )
441
+
442
+
443
+ @_register_irc_message
444
+ class TimeMessage(Message):
445
+ FORMAT = MessageFormat.of(
446
+ 'TIME',
447
+ '?server',
448
+ )
449
+
450
+ REPLIES = (
451
+ nr.ERR_NOSUCHSERVER,
452
+ nr.RPL_TIME,
453
+ )
454
+
455
+ server: str | None = None
456
+
457
+
458
+ @_register_irc_message
459
+ class StatsMessage(Message):
460
+ FORMAT = MessageFormat.of(
461
+ 'STATS',
462
+ 'query',
463
+ '?server',
464
+ )
465
+
466
+ REPLIES = (
467
+ nr.ERR_NOSUCHSERVER,
468
+ nr.ERR_NEEDMOREPARAMS,
469
+ nr.ERR_NOPRIVILEGES,
470
+ nr.ERR_NOPRIVS,
471
+ nr.RPL_STATSCLINE,
472
+ nr.RPL_STATSHLINE,
473
+ nr.RPL_STATSILINE,
474
+ nr.RPL_STATSKLINE,
475
+ nr.RPL_STATSLLINE,
476
+ nr.RPL_STATSOLINE,
477
+ nr.RPL_STATSLINKINFO,
478
+ nr.RPL_STATSUPTIME,
479
+ nr.RPL_STATSCOMMANDS,
480
+ nr.RPL_ENDOFSTATS,
481
+ )
482
+
483
+ query: str
484
+ server: str | None = None
485
+
486
+
487
+ @_register_irc_message
488
+ class HelpMessage(Message):
489
+ FORMAT = MessageFormat.of(
490
+ 'HELP',
491
+ '?subject',
492
+ )
493
+
494
+ REPLIES = (
495
+ nr.ERR_HELPNOTFOUND,
496
+ nr.RPL_HELPSTART,
497
+ nr.RPL_HELPTXT,
498
+ nr.RPL_ENDOFHELP,
499
+ )
500
+
501
+ subject: str | None = None
502
+
503
+
504
+ @_register_irc_message
505
+ class InfoMessage(Message):
506
+ FORMAT = MessageFormat.of('INFO')
507
+
508
+ REPLIES = (
509
+ nr.RPL_INFO,
510
+ nr.RPL_ENDOFINFO,
511
+ )
512
+
513
+
514
+ @_register_irc_message
515
+ class ModeMessage(Message):
516
+ FORMAT = MessageFormat.of(
517
+ 'MODE',
518
+ 'target',
519
+ '?modestring',
520
+ '*mode_arguments',
521
+ )
522
+
523
+ REPLIES = (
524
+ nr.ERR_NOSUCHCHANNEL,
525
+ nr.RPL_CHANNELMODEIS,
526
+ nr.RPL_CREATIONTIME,
527
+ nr.RPL_CHANNELMODEIS,
528
+ nr.ERR_CHANOPRIVSNEEDED,
529
+ nr.RPL_BANLIST,
530
+ nr.RPL_ENDOFBANLIST,
531
+ nr.RPL_EXCEPTLIST,
532
+ nr.RPL_ENDOFEXCEPTLIST,
533
+ nr.RPL_INVITELIST,
534
+ nr.RPL_ENDOFINVITELIST,
535
+ )
536
+
537
+ target: str
538
+ modestring: str | None = None
539
+ mode_arguments: ta.Sequence[str] | None = None
540
+
541
+
542
+ ##
543
+ # Sending Messages
544
+
545
+
546
+ @_register_irc_message
547
+ class PrivmsgMessage(Message):
548
+ FORMAT = MessageFormat.of(
549
+ 'PRIVMSG',
550
+ ',targets',
551
+ 'text',
552
+ )
553
+
554
+ REPLIES = (
555
+ nr.ERR_NOSUCHNICK,
556
+ nr.ERR_NOSUCHSERVER,
557
+ nr.ERR_CANNOTSENDTOCHAN,
558
+ nr.ERR_TOOMANYTARGETS,
559
+ nr.ERR_NORECIPIENT,
560
+ nr.ERR_NOTEXTTOSEND,
561
+ nr.ERR_NOTOPLEVEL,
562
+ nr.ERR_WILDTOPLEVEL,
563
+ nr.RPL_AWAY,
564
+ )
565
+
566
+ targets: ta.Sequence[str]
567
+ text: str
568
+
569
+
570
+ @_register_irc_message
571
+ class NoticeMessage(Message):
572
+ FORMAT = MessageFormat.of(
573
+ 'NOTICE',
574
+ ',targets',
575
+ 'text',
576
+ )
577
+
578
+ targets: ta.Sequence[str]
579
+ text: str
580
+
581
+
582
+ ##
583
+ # User-Based Queries
584
+
585
+ @_register_irc_message
586
+ class WhoMessage(Message):
587
+ FORMAT = MessageFormat.of(
588
+ 'WHO',
589
+ 'mask',
590
+ )
591
+
592
+ REPLIES = (
593
+ nr.RPL_WHOREPLY,
594
+ nr.RPL_ENDOFWHO,
595
+ )
596
+
597
+ mask: str
598
+
599
+
600
+ @_register_irc_message
601
+ class WhoisMessage(Message):
602
+ FORMAT = MessageFormat.of(
603
+ 'WHOIS',
604
+ '?target',
605
+ 'nick',
606
+ )
607
+
608
+ REPLIES = (
609
+ nr.ERR_NOSUCHNICK,
610
+ nr.ERR_NOSUCHSERVER,
611
+ nr.ERR_NONICKNAMEGIVEN,
612
+ nr.RPL_WHOISCERTFP,
613
+ nr.RPL_WHOISREGNICK,
614
+ nr.RPL_WHOISUSER,
615
+ nr.RPL_WHOISSERVER,
616
+ nr.RPL_WHOISOPERATOR,
617
+ nr.RPL_WHOISIDLE,
618
+ nr.RPL_WHOISCHANNELS,
619
+ nr.RPL_WHOISSPECIAL,
620
+ nr.RPL_WHOISACCOUNT,
621
+ nr.RPL_WHOISACTUALLY,
622
+ nr.RPL_WHOISHOST,
623
+ nr.RPL_WHOISMODES,
624
+ nr.RPL_WHOISSECURE,
625
+ nr.RPL_AWAY,
626
+ )
627
+
628
+ nick: str
629
+ target: str | None = None
630
+
631
+
632
+ @_register_irc_message
633
+ class WhowasMessage(Message):
634
+ FORMAT = MessageFormat.of(
635
+ 'WHOWAS',
636
+ 'nick',
637
+ '?count',
638
+ )
639
+
640
+ REPLIES = (
641
+ nr.ERR_WASNOSUCHNICK,
642
+ nr.RPL_ENDOFWHOWAS,
643
+ nr.RPL_WHOWASUSER,
644
+ nr.RPL_WHOISACTUALLY,
645
+ nr.RPL_WHOISSERVER,
646
+ nr.RPL_WHOWASUSER,
647
+ nr.ERR_NONICKNAMEGIVEN,
648
+ nr.ERR_NEEDMOREPARAMS,
649
+ )
650
+
651
+ nick: str
652
+ count: str | None = None
653
+
654
+
655
+ ##
656
+ # Operator Messages
657
+
658
+
659
+ @_register_irc_message
660
+ class KillMessage(Message):
661
+ FORMAT = MessageFormat.of(
662
+ 'KILL',
663
+ 'nickname',
664
+ 'comment',
665
+ )
666
+
667
+ REPLIES = (
668
+ nr.ERR_NOSUCHSERVER,
669
+ nr.ERR_NEEDMOREPARAMS,
670
+ nr.ERR_NOPRIVILEGES,
671
+ nr.ERR_NOPRIVS,
672
+ )
673
+
674
+ nickname: str
675
+ comment: str
676
+
677
+
678
+ @_register_irc_message
679
+ class RehashMessage(Message):
680
+ FORMAT = MessageFormat.of('REHASH')
681
+
682
+ REPLIES = (
683
+ nr.RPL_REHASHING,
684
+ nr.ERR_NOPRIVILEGES,
685
+ )
686
+
687
+
688
+ @_register_irc_message
689
+ class RestartMessage(Message):
690
+ FORMAT = MessageFormat.of('RESTART')
691
+
692
+ REPLIES = (
693
+ nr.ERR_NOPRIVILEGES,
694
+ )
695
+
696
+
697
+ @_register_irc_message
698
+ class SquitMessage(Message):
699
+ FORMAT = MessageFormat.of(
700
+ 'SQUIT',
701
+ 'server',
702
+ 'comment',
703
+ )
704
+
705
+ REPLIES = (
706
+ nr.ERR_NOSUCHSERVER,
707
+ nr.ERR_NEEDMOREPARAMS,
708
+ nr.ERR_NOPRIVILEGES,
709
+ nr.ERR_NOPRIVS,
710
+ )
711
+
712
+ server: str
713
+ comment: str
714
+
715
+
716
+ ##
717
+ # Optional Messages
718
+
719
+
720
+ @_register_irc_message
721
+ class AwayMessage(Message):
722
+ FORMAT = MessageFormat.of(
723
+ 'AWAY',
724
+ '?text',
725
+ )
726
+
727
+ REPLIES = (
728
+ nr.RPL_AWAY,
729
+ nr.RPL_UNAWAY,
730
+ nr.RPL_NOWAWAY,
731
+ )
732
+
733
+ text: str | None = None
734
+
735
+
736
+ @_register_irc_message
737
+ class LinksMessage(Message):
738
+ FORMAT = MessageFormat.of('LINKS')
739
+
740
+ REPLIES = (
741
+ nr.RPL_LINKS,
742
+ nr.RPL_ENDOFLINKS,
743
+ )
744
+
745
+
746
+ @_register_irc_message
747
+ class UserhostMessage(Message):
748
+ FORMAT = MessageFormat.of(
749
+ 'USERHOST',
750
+ '*nicknames',
751
+ )
752
+
753
+ REPLIES = (
754
+ nr.ERR_NEEDMOREPARAMS,
755
+ nr.RPL_USERHOST,
756
+ )
757
+
758
+ nicknames: ta.Sequence[str] | None = dc.xfield(default=None, validate=check.of_not_isinstance(str))
759
+
760
+
761
+ @_register_irc_message
762
+ class WallopsMessage(Message):
763
+ FORMAT = MessageFormat.of(
764
+ 'WALLOPS',
765
+ 'text',
766
+ )
767
+
768
+ REPLIES = (
769
+ nr.ERR_NEEDMOREPARAMS,
770
+ nr.ERR_NOPRIVILEGES,
771
+ nr.ERR_NOPRIVS,
772
+ )
773
+
774
+ text: str