1// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
2// released under the MIT license
3
4package irc
5
6import (
7	"fmt"
8	"sort"
9	"strings"
10	"sync"
11
12	"github.com/ergochat/ergo/irc/languages"
13)
14
15// HelpEntryType represents the different sorts of help entries that can exist.
16type HelpEntryType int
17
18const (
19	// CommandHelpEntry is a help entry explaining a client command.
20	CommandHelpEntry HelpEntryType = iota
21	// InformationHelpEntry is a help entry explaining general server info.
22	InformationHelpEntry
23	// ISupportHelpEntry is a help entry explaining a specific RPL_ISUPPORT token.
24	ISupportHelpEntry
25)
26
27// HelpEntry represents an entry in the Help map.
28type HelpEntry struct {
29	oper          bool
30	text          string
31	textGenerator func(*Client) string
32	helpType      HelpEntryType
33	duplicate     bool
34}
35
36// used for duplicates
37var (
38	cmodeHelpText = `== Channel Modes ==
39
40Ergo supports the following channel modes:
41
42  +b  |  Client masks that are banned from the channel (e.g. *!*@127.0.0.1)
43  +e  |  Client masks that are exempted from bans.
44  +I  |  Client masks that are exempted from the invite-only flag.
45  +i  |  Invite-only mode, only invited clients can join the channel.
46  +k  |  Key required when joining the channel.
47  +l  |  Client join limit for the channel.
48  +f  |  Users who are unable to join this channel (due to another mode) are forwarded
49         to the provided channel instead.
50  +m  |  Moderated mode, only privileged clients can talk on the channel.
51  +n  |  No-outside-messages mode, only users that are on the channel can send
52      |  messages to it.
53  +R  |  Only registered users can join the channel.
54  +M  |  Only registered or voiced users can speak in the channel.
55  +s  |  Secret mode, channel won't show up in /LIST or whois replies.
56  +t  |  Only channel opers can modify the topic.
57  +E  |  Roleplaying commands are enabled in the channel.
58  +C  |  Clients are blocked from sending CTCP messages in the channel.
59  +u  |  Auditorium mode: JOIN, PART, QUIT, NAMES, and WHO are hidden
60         from unvoiced clients.
61  +U  |  Op-moderated mode: messages from unprivileged clients are sent
62         only to channel operators.
63
64= Prefixes =
65
66  +q (~)  |  Founder channel mode.
67  +a (&)  |  Admin channel mode.
68  +o (@)  |  Operator channel mode.
69  +h (%)  |  Halfop channel mode.
70  +v (+)  |  Voice channel mode.`
71	umodeHelpText = `== User Modes ==
72
73Ergo supports the following user modes:
74
75  +a  |  User is marked as being away. This mode is set with the /AWAY command.
76  +i  |  User is marked as invisible (their channels are hidden from whois replies).
77  +o  |  User is an IRC operator.
78  +R  |  User only accepts messages from other registered users.
79  +s  |  Server Notice Masks (see help with /HELPOP snomasks).
80  +Z  |  User is connected via TLS.
81  +B  |  User is a bot.
82  +E  |  User can receive roleplaying commands.
83  +T  |  CTCP messages to the user are blocked.`
84	snomaskHelpText = `== Server Notice Masks ==
85
86Ergo supports the following server notice masks for operators:
87
88  a  |  Local announcements.
89  c  |  Local client connections.
90  d  |  Local client disconnects.
91  j  |  Local channel actions.
92  k  |  Local kills.
93  n  |  Local nick changes.
94  o  |  Local oper actions.
95  q  |  Local quits.
96  t  |  Local /STATS usage.
97  u  |  Local client account actions.
98  x  |  Local X-lines (DLINE/KLINE/etc).
99  v  |  Local vhost changes.
100
101To set a snomask, do this with your nickname:
102
103  /MODE <nick> +s <chars>
104
105For instance, this would set the kill, oper, account and xline snomasks on dan:
106
107  /MODE dan +s koux`
108)
109
110// Help contains the help strings distributed with the IRCd.
111var Help = map[string]HelpEntry{
112	// Commands
113	"ambiance": {
114		text: `AMBIANCE <target> <text to be sent>
115
116The AMBIANCE command is used to send a scene notification to the given target.`,
117	},
118	"authenticate": {
119		text: `AUTHENTICATE
120
121Used during SASL authentication. See the IRCv3 specs for more info:
122http://ircv3.net/specs/extensions/sasl-3.1.html`,
123	},
124	"away": {
125		text: `AWAY [message]
126
127If [message] is sent, marks you away. If [message] is not sent, marks you no
128longer away.`,
129	},
130	"batch": {
131		text: `BATCH {+,-}reference-tag type [params...]
132
133BATCH initiates an IRCv3 client-to-server batch. You should never need to
134issue this command manually.`,
135	},
136	"cap": {
137		text: `CAP <subcommand> [:<capabilities>]
138
139Used in capability negotiation. See the IRCv3 specs for more info:
140http://ircv3.net/specs/core/capability-negotiation-3.1.html
141http://ircv3.net/specs/core/capability-negotiation-3.2.html`,
142	},
143	"chathistory": {
144		text: `CHATHISTORY [params]
145
146CHATHISTORY is a history replay command associated with the IRCv3
147specification draft/chathistory. See this document:
148https://github.com/ircv3/ircv3-specifications/pull/393`,
149	},
150	"debug": {
151		oper: true,
152		text: `DEBUG <option>
153
154Provides various debugging commands for the IRCd. <option> can be one of:
155
156* GCSTATS: Garbage control statistics.
157* NUMGOROUTINE: Number of goroutines in use.
158* STARTCPUPROFILE: Starts the CPU profiler.
159* STOPCPUPROFILE: Stops the CPU profiler.
160* PROFILEHEAP: Writes a memory profile.
161* CRASHSERVER: Crashes the server (for use in failover testing)`,
162	},
163	"defcon": {
164		oper: true,
165		text: `DEFCON [level]
166
167The DEFCON system can disable server features at runtime, to mitigate
168spam or other hostile activity. It has five levels, which are cumulative
169(i.e., level 3 includes all restrictions from level 4 and so on):
170
1715: Normal operation
1724: No new account or channel registrations; if Tor is enabled, no new
173   unauthenticated connections from Tor
1743: All users are +R; no changes to vhosts
1752: No new unauthenticated connections; all channels are +R
1761: No new connections except from localhost or other trusted IPs`,
177	},
178	"deoper": {
179		oper: true,
180		text: `DEOPER
181
182DEOPER removes the IRCop privileges granted to you by a successful /OPER.`,
183	},
184	"dline": {
185		oper: true,
186		text: `DLINE [ANDKILL] [MYSELF] [duration] <ip>/<net> [ON <server>] [reason [| oper reason]]
187DLINE LIST
188
189Bans an IP address or network from connecting to the server. If the duration is
190given then only for that long. The reason is shown to the user themselves, but
191everyone else will see a standard message. The oper reason is shown to
192operators getting info about the DLINEs that exist.
193
194Bans are saved across subsequent launches of the server.
195
196"ANDKILL" means that all matching clients are also removed from the server.
197
198"MYSELF" is required when the DLINE matches the address the person applying it is connected
199from. If "MYSELF" is not given, trying to DLINE yourself will result in an error.
200
201[duration] can be of the following forms:
202	1y 12mo 31d 10h 8m 13s
203
204<net> is specified in typical CIDR notation. For example:
205	127.0.0.1/8
206	8.8.8.8/24
207
208ON <server> specifies that the ban is to be set on that specific server.
209
210[reason] and [oper reason], if they exist, are separated by a vertical bar (|).
211
212If "DLINE LIST" is sent, the server sends back a list of our current DLINEs.
213
214To remove a DLINE, use the "UNDLINE" command.`,
215	},
216	"extjwt": {
217		text: `EXTJWT <target> [service_name]
218
219Get a JSON Web Token for target (either * or a channel name).`,
220	},
221	"help": {
222		text: `HELP <argument>
223
224Get an explanation of <argument>, or "index" for a list of help topics.`,
225	},
226	"helpop": {
227		text: `HELPOP <argument>
228
229Get an explanation of <argument>, or "index" for a list of help topics.`,
230	},
231	"history": {
232		text: `HISTORY <target> [limit]
233
234Replay message history. <target> can be a channel name, "me" to replay direct
235message history, or a nickname to replay another client's direct message
236history (they must be logged into the same account as you). [limit] can be
237either an integer (the maximum number of messages to replay), or a time
238duration like 10m or 1h (the time window within which to replay messages).`,
239	},
240	"info": {
241		text: `INFO
242
243Sends information about the server, developers, etc.`,
244	},
245	"invite": {
246		text: `INVITE <nickname> <channel>
247
248Invites the given user to the given channel, so long as you have the
249appropriate channel privs.`,
250	},
251	"ison": {
252		text: `ISON <nickname>{ <nickname>}
253
254Returns whether the given nicks exist on the network.`,
255	},
256	"join": {
257		text: `JOIN <channel>{,<channel>} [<key>{,<key>}]
258
259Joins the given channels with the matching keys.`,
260	},
261	"kick": {
262		text: `KICK <channel> <user> [reason]
263
264Removes the user from the given channel, so long as you have the appropriate
265channel privs.`,
266	},
267	"kill": {
268		oper: true,
269		text: `KILL <nickname> [reason]
270
271Removes the given user from the network, showing them the reason if it is
272supplied.`,
273	},
274	"kline": {
275		oper: true,
276		text: `KLINE [ANDKILL] [MYSELF] [duration] <mask> [ON <server>] [reason [| oper reason]]
277KLINE LIST
278
279Bans a mask from connecting to the server. If the duration is given then only for that
280long. The reason is shown to the user themselves, but everyone else will see a standard
281message. The oper reason is shown to operators getting info about the KLINEs that exist.
282
283Bans are saved across subsequent launches of the server.
284
285"ANDKILL" means that all matching clients are also removed from the server.
286
287"MYSELF" is required when the KLINE matches the address the person applying it is connected
288from. If "MYSELF" is not given, trying to KLINE yourself will result in an error.
289
290[duration] can be of the following forms:
291	1y 12mo 31d 10h 8m 13s
292
293<mask> is specified in typical IRC format. For example:
294	dan
295	dan!5*@127.*
296
297ON <server> specifies that the ban is to be set on that specific server.
298
299[reason] and [oper reason], if they exist, are separated by a vertical bar (|).
300
301If "KLINE LIST" is sent, the server sends back a list of our current KLINEs.
302
303To remove a KLINE, use the "UNKLINE" command.`,
304	},
305	"language": {
306		text: `LANGUAGE <code>{ <code>}
307
308Sets your preferred languages to the given ones.`,
309	},
310	"list": {
311		text: `LIST [<channel>{,<channel>}] [<elistcond>{,<elistcond>}]
312
313Shows information on the given channels (or if none are given, then on all
314channels). <elistcond>s modify how the channels are selected.`,
315		//TODO(dan): Explain <elistcond>s in more specific detail
316	},
317	"lusers": {
318		text: `LUSERS [<mask> [<server>]]
319
320Shows statistics about the size of the network. If <mask> is given, only
321returns stats for servers matching the given mask.  If <server> is given, the
322command is processed by that server.`,
323	},
324	"mode": {
325		text: `MODE <target> [<modestring> [<mode arguments>...]]
326
327Sets and removes modes from the given target. For more specific information on
328mode characters, see the help for "modes".`,
329	},
330	"monitor": {
331		text: `MONITOR <subcmd>
332
333Allows the monitoring of nicknames, for alerts when they are online and
334offline. The subcommands are:
335
336    MONITOR + target{,target}
337Adds the given names to your list of monitored nicknames.
338
339    MONITOR - target{,target}
340Removes the given names from your list of monitored nicknames.
341
342    MONITOR C
343Clears your list of monitored nicknames.
344
345    MONITOR L
346Lists all the nicknames you are currently monitoring.
347
348    MONITOR S
349Lists whether each nick in your MONITOR list is online or offline.`,
350	},
351	"motd": {
352		text: `MOTD [server]
353
354Returns the message of the day for this, or the given, server.`,
355	},
356	"names": {
357		text: `NAMES [<channel>{,<channel>}]
358
359Views the clients joined to a channel and their channel membership prefixes. To
360view the channel membership prefixes supported by this server, see the help for
361"PREFIX".`,
362	},
363	"nick": {
364		text: `NICK <newnick>
365
366Sets your nickname to the new given one.`,
367	},
368	"notice": {
369		text: `NOTICE <target>{,<target>} <text to be sent>
370
371Sends the text to the given targets as a NOTICE.`,
372	},
373	"npc": {
374		text: `NPC <target> <sourcenick> <text to be sent>
375
376The NPC command is used to send a message to the target as the source.
377
378Requires the roleplay mode (+E) to be set on the target.`,
379	},
380	"npca": {
381		text: `NPCA <target> <sourcenick> <text to be sent>
382
383The NPC command is used to send an action to the target as the source.
384
385Requires the roleplay mode (+E) to be set on the target.`,
386	},
387	"oper": {
388		text: `OPER <name> [password]
389
390If the correct details are given, gives you IRCop privs.`,
391	},
392	"part": {
393		text: `PART <channel>{,<channel>} [reason]
394
395Leaves the given channels and shows people the given reason.`,
396	},
397	"pass": {
398		text: `PASS <password>
399
400When the server requires a connection password to join, used to send us the
401password.`,
402	},
403	"ping": {
404		text: `PING <args>...
405
406Requests a PONG. Used to check link connectivity.`,
407	},
408	"pong": {
409		text: `PONG <args>...
410
411Replies to a PING. Used to check link connectivity.`,
412	},
413	"privmsg": {
414		text: `PRIVMSG <target>{,<target>} <text to be sent>
415
416Sends the text to the given targets as a PRIVMSG.`,
417	},
418	"relaymsg": {
419		text: `RELAYMSG <channel> <spoofed nick> :<message>
420
421This command lets channel operators relay messages to their
422channel from other messaging systems using relay bots. The
423spoofed nickname MUST contain a forwardslash.
424
425For example:
426	RELAYMSG #ircv3 Mallory/D :Welp, we linked Discord...`,
427	},
428	"rename": {
429		text: `RENAME <channel> <newname> [<reason>]
430
431Renames the given channel with the given reason, if possible.
432
433For example:
434	RENAME #ircv2 #ircv3 :Protocol upgrades!`,
435	},
436	"sajoin": {
437		oper: true,
438		text: `SAJOIN [nick] #channel{,#channel}
439
440Forcibly joins a user to a channel, ignoring restrictions like bans, user limits
441and channel keys. If [nick] is omitted, it defaults to the operator.`,
442	},
443	"sanick": {
444		oper: true,
445		text: `SANICK <currentnick> <newnick>
446
447Gives the given user a new nickname.`,
448	},
449	"samode": {
450		oper: true,
451		text: `SAMODE <target> [<modestring> [<mode arguments>...]]
452
453Forcibly sets and removes modes from the given target -- only available to
454opers. For more specific information on mode characters, see the help for
455"cmode" and "umode".`,
456	},
457	"scene": {
458		text: `SCENE <target> <text to be sent>
459
460The SCENE command is used to send a scene notification to the given target.`,
461	},
462	"setname": {
463		text: `SETNAME <realname>
464
465The SETNAME command updates the realname to be the newly-given one.`,
466	},
467	"summon": {
468		text: `SUMMON [parameters]
469
470The SUMMON command is not implemented.`,
471	},
472	"tagmsg": {
473		text: `@+client-only-tags TAGMSG <target>{,<target>}
474
475Sends the given client-only tags to the given targets as a TAGMSG. See the IRCv3
476specs for more info: http://ircv3.net/specs/core/message-tags-3.3.html`,
477	},
478	"quit": {
479		text: `QUIT [reason]
480
481Indicates that you're leaving the server, and shows everyone the given reason.`,
482	},
483	"register": {
484		text: `REGISTER <email | *> <password>
485
486Registers an account in accordance with the draft/register capability.`,
487	},
488	"rehash": {
489		oper: true,
490		text: `REHASH
491
492Reloads the config file and updates TLS certificates on listeners`,
493	},
494	"time": {
495		text: `TIME [server]
496
497Shows the time of the current, or the given, server.`,
498	},
499	"topic": {
500		text: `TOPIC <channel> [topic]
501
502If [topic] is given, sets the topic in the channel to that. If [topic] is not
503given, views the current topic on the channel.`,
504	},
505	"uban": {
506		text: `UBAN <subcommand> [arguments]
507
508Ergo's "unified ban" system. Accepts the following subcommands:
509
5101. UBAN ADD <target> [REQUIRE-SASL] [DURATION <duration>] [REASON...]
5112. UBAN DEL <target>
5123. UBAN LIST
5134. UBAN INFO <target>
514
515<target> may be an IP, a CIDR, a nickmask with wildcards, or the name of an
516account to suspend. Note that REQUIRE-SASL is only valid for IP and CIDR bans.`,
517	},
518	"undline": {
519		oper: true,
520		text: `UNDLINE <ip>/<net>
521
522Removes an existing ban on an IP address or a network.
523
524<net> is specified in typical CIDR notation. For example:
525	127.0.0.1/8
526	8.8.8.8/24`,
527	},
528	"unkline": {
529		oper: true,
530		text: `UNKLINE <mask>
531
532Removes an existing ban on a mask.
533
534For example:
535	dan
536	dan!5*@127.*`,
537	},
538	"user": {
539		text: `USER <username> 0 * <realname>
540
541Used in connection registration, sets your username and realname to the given
542values (though your username may also be looked up with Ident).`,
543	},
544	"uninvite": {
545		text: `UNINVITE <nickname> <channel>
546
547UNINVITE rescinds a channel invitation sent for an invite-only channel.`,
548	},
549	"users": {
550		text: `USERS [parameters]
551
552The USERS command is not implemented.`,
553	},
554	"userhost": {
555		text: `USERHOST <nickname>{ <nickname>}
556
557Shows information about the given users. Takes up to 10 nicknames.`,
558	},
559	"verify": {
560		text: `VERIFY <account> <password>
561
562Verifies an account in accordance with the draft/register capability.`,
563	},
564	"version": {
565		text: `VERSION [server]
566
567Views the version of software and the RPL_ISUPPORT tokens for the given server.`,
568	},
569	"webirc": {
570		oper: true, // not really, but it's restricted anyways
571		text: `WEBIRC <password> <gateway> <hostname> <ip> [:<flags>]
572
573Used by web<->IRC gateways and bouncers, the WEBIRC command allows gateways to
574pass-through the real IP addresses of clients:
575ircv3.net/specs/extensions/webirc.html
576
577<flags> is a list of space-separated strings indicating various details about
578the connection from the client to the gateway, such as:
579
580- tls: this flag indicates that the client->gateway connection is secure`,
581	},
582	"who": {
583		text: `WHO <name> [o]
584
585Returns information for the given user.`,
586	},
587	"whois": {
588		text: `WHOIS <client>{,<client>}
589
590Returns information for the given user(s).`,
591	},
592	"whowas": {
593		text: `WHOWAS <nickname>
594
595Returns historical information on the last user with the given nickname.`,
596	},
597	"znc": {
598		text: `ZNC <module> [params]
599
600Used to emulate features of the ZNC bouncer. This command is not intended
601for direct use by end users.`,
602		duplicate: true,
603	},
604
605	// Informational
606	"modes": {
607		textGenerator: modesTextGenerator,
608		helpType:      InformationHelpEntry,
609	},
610	"cmode": {
611		text:     cmodeHelpText,
612		helpType: InformationHelpEntry,
613	},
614	"cmodes": {
615		text:      cmodeHelpText,
616		helpType:  InformationHelpEntry,
617		duplicate: true,
618	},
619	"umode": {
620		text:     umodeHelpText,
621		helpType: InformationHelpEntry,
622	},
623	"umodes": {
624		text:      umodeHelpText,
625		helpType:  InformationHelpEntry,
626		duplicate: true,
627	},
628	"snomask": {
629		text:      snomaskHelpText,
630		helpType:  InformationHelpEntry,
631		oper:      true,
632		duplicate: true,
633	},
634	"snomasks": {
635		text:     snomaskHelpText,
636		helpType: InformationHelpEntry,
637		oper:     true,
638	},
639
640	// RPL_ISUPPORT
641	"casemapping": {
642		text: `RPL_ISUPPORT CASEMAPPING
643
644Ergo supports an experimental unicode casemapping designed for extended
645Unicode support. This casemapping is based off RFC 7613 and the draft rfc7613
646casemapping spec here: https://ergo.chat/specs.html`,
647		helpType: ISupportHelpEntry,
648	},
649	"prefix": {
650		text: `RPL_ISUPPORT PREFIX
651
652Ergo supports the following channel membership prefixes:
653
654  +q (~)  |  Founder channel mode.
655  +a (&)  |  Admin channel mode.
656  +o (@)  |  Operator channel mode.
657  +h (%)  |  Halfop channel mode.
658  +v (+)  |  Voice channel mode.`,
659		helpType: ISupportHelpEntry,
660	},
661}
662
663// modesTextGenerator generates the text for the 'modes' help entry.
664// it exists only so we can translate this entry appropriately.
665func modesTextGenerator(client *Client) string {
666	return client.t(cmodeHelpText) + "\n\n" + client.t(umodeHelpText)
667}
668
669type HelpIndexManager struct {
670	sync.RWMutex // tier 1
671
672	langToIndex     map[string]string
673	langToOperIndex map[string]string
674}
675
676// GenerateHelpIndex is used to generate HelpIndex.
677// Returns: a map from language code to the help index in that language.
678func GenerateHelpIndex(lm *languages.Manager, forOpers bool) map[string]string {
679	// generate the help entry lists
680	var commands, isupport, information []string
681
682	var line string
683	for name, info := range Help {
684		if info.duplicate {
685			continue
686		}
687		if info.oper && !forOpers {
688			continue
689		}
690
691		line = fmt.Sprintf("   %s", name)
692
693		if info.helpType == CommandHelpEntry {
694			commands = append(commands, line)
695		} else if info.helpType == ISupportHelpEntry {
696			isupport = append(isupport, line)
697		} else if info.helpType == InformationHelpEntry {
698			information = append(information, line)
699		}
700	}
701
702	// create the strings
703	sort.Strings(commands)
704	commandsString := strings.Join(commands, "\n")
705	sort.Strings(isupport)
706	isupportString := strings.Join(isupport, "\n")
707	sort.Strings(information)
708	informationString := strings.Join(information, "\n")
709
710	// sub them in
711	defaultHelpIndex := `= Help Topics =
712
713Commands:
714%[1]s
715
716RPL_ISUPPORT Tokens:
717%[2]s
718
719Information:
720%[3]s`
721
722	newHelpIndex := make(map[string]string)
723
724	newHelpIndex["en"] = fmt.Sprintf(defaultHelpIndex, commandsString, isupportString, informationString)
725
726	for langCode := range lm.Languages {
727		translatedHelpIndex := lm.Translate([]string{langCode}, defaultHelpIndex)
728		if translatedHelpIndex != defaultHelpIndex {
729			newHelpIndex[langCode] = fmt.Sprintf(translatedHelpIndex, commandsString, isupportString, informationString)
730		}
731	}
732
733	return newHelpIndex
734}
735
736// GenerateIndices regenerates our help indexes for each currently enabled language.
737func (hm *HelpIndexManager) GenerateIndices(lm *languages.Manager) {
738	// generate help indexes
739	langToIndex := GenerateHelpIndex(lm, false)
740	langToOperIndex := GenerateHelpIndex(lm, true)
741
742	hm.Lock()
743	defer hm.Unlock()
744	hm.langToIndex = langToIndex
745	hm.langToOperIndex = langToOperIndex
746}
747
748// sendHelp sends the client help of the given string.
749func (client *Client) sendHelp(helpEntry string, text string, rb *ResponseBuffer) {
750	helpEntry = strings.ToUpper(helpEntry)
751	nick := client.Nick()
752	textLines := strings.Split(text, "\n")
753
754	for i, line := range textLines {
755		if i == 0 {
756			rb.Add(nil, client.server.name, RPL_HELPSTART, nick, helpEntry, line)
757		} else {
758			rb.Add(nil, client.server.name, RPL_HELPTXT, nick, helpEntry, line)
759		}
760	}
761	rb.Add(nil, client.server.name, RPL_ENDOFHELP, nick, helpEntry, client.t("End of /HELPOP"))
762}
763
764// GetHelpIndex returns the help index for the given language.
765func (hm *HelpIndexManager) GetIndex(languages []string, oper bool) string {
766	hm.RLock()
767	langToIndex := hm.langToIndex
768	if oper {
769		langToIndex = hm.langToOperIndex
770	}
771	hm.RUnlock()
772
773	for _, lang := range languages {
774		index, exists := langToIndex[lang]
775		if exists {
776			return index
777		}
778	}
779	// 'en' always exists
780	return langToIndex["en"]
781}
782
783func init() {
784	// startup check that we have HELP entries for every command
785	for name := range Commands {
786		_, exists := Help[strings.ToLower(name)]
787		if !exists {
788			panic(fmt.Sprintf("Help entry does not exist for command %s", name))
789		}
790	}
791}
792