1
0
mirror of https://github.com/anope/anope.git synced 2026-06-19 11:34:46 +02:00

Compare commits

..

51 Commits

Author SHA1 Message Date
Sadie Powell 9930fa01ac Release 2.1.16. 2025-07-01 10:56:40 +01:00
Sadie Powell 40929a0490 Update the change logs. 2025-07-01 10:13:36 +01:00
Sadie Powell 783be31f64 Update author list. 2025-07-01 09:48:19 +01:00
Sadie Powell 0f5f2aef2e Bump the minimum OpenSSL version to 1.1.1.
Close #517.
2025-07-01 00:00:55 +01:00
Sadie Powell 6cc997c4e9 When unsetting a temporary ban delete the unset timer.
This prevents Anope from unsetting a later-added ban,

Resolves MantisBT bug 1709.
2025-06-25 19:16:15 +01:00
Sadie Powell eda6d8cc0f Fix storing the setter of a list mode. 2025-06-25 19:10:49 +01:00
Sadie Powell 3440b38a21 Merge branch '2.0' into 2.1. 2025-06-25 17:50:01 +01:00
Sadie Powell fca421aa2a Fix resetpass confirming accounts when not using email confirmation.
Fixes MantisBT bug 1734.
2025-06-25 17:36:16 +01:00
Sadie Powell 035905d321 Fix example messages using .site which is a real TLD now. 2025-06-25 15:39:35 +01:00
Sadie Powell d1cd57d98e Resynchronise the en_US language file. 2025-06-25 15:34:01 +01:00
Sadie Powell 48daeeac1d Merge branch '2.0' into 2.1. 2025-06-25 15:33:36 +01:00
Sadie Powell 9bcf46f8ea Fix using service's instead of services' where appropriate. 2025-06-25 15:29:16 +01:00
Sadie Powell 0b6c7ce5d6 Update some messages for the language used by modern BIND versions. 2025-06-25 15:28:54 +01:00
Sadie Powell a0d21b207b Fix reading the purge time in cs_seen. 2025-06-25 14:08:44 +01:00
Sadie Powell 3cc5b5143f Remove the old 1.8-style seen system. 2025-06-25 14:06:59 +01:00
Sadie Powell da738126a4 Merge branch '2.0' into 2.1. 2025-06-25 12:07:36 +01:00
Sadie Powell 0bb1bc5c67 Backport various minor config changes from 2.1. 2025-06-25 12:00:33 +01:00
Sadie Powell 72010cd1a9 Fix messages that say IRC Operator when they mean Services Operator. 2025-06-24 13:58:47 +01:00
Sadie Powell c48b3af3d4 Remove an obsolete workaround from Config. 2025-06-24 09:29:33 +01:00
Sadie Powell 85c129701b Clarify how to migrate db_flatfile databases to db_json. 2025-06-23 23:39:20 +01:00
Sadie Powell a9e5a79e91 Remove some unused strings in os_news. 2025-06-22 15:45:21 +01:00
Sadie Powell adc1343d6c Serialize item types as strings instead of integers.
Using the type cast to an integer here was undefined behaviour.
2025-06-22 15:45:21 +01:00
Sadie Powell 8aa1102c7a Default to db_json. 2025-06-22 15:45:21 +01:00
TehPeGaSuS 1a89d32926 Document NS_STATS and CS_STATS. 2025-06-21 17:09:26 +01:00
Sadie Powell ad06853edf Fix suggesting unloaded commands in the "did you mean" message. 2025-06-21 09:56:02 +01:00
Sadie Powell 66ae20e0f2 Tweak a message to make it easier for ESL speakers to understand. 2025-06-21 09:48:01 +01:00
Sadie Powell 2850e3c65c Implement serializer hashing in db_json. 2025-06-21 00:31:02 +01:00
Sadie Powell 80b8856254 Consistently use "account" instead of "nick group". 2025-06-20 20:30:21 +01:00
Sadie Powell 21c8164539 When assigning a new display nick use the oldest not the first. 2025-06-20 20:27:39 +01:00
Sadie Powell a4abd27ffd Fix the grammar of a message in ns_confirm. 2025-06-20 19:43:02 +01:00
Sadie Powell b912b403f0 Fix a comment in CommandInfo. 2025-06-20 19:43:02 +01:00
Sadie Powell 59e9d47667 Update another message to use pluralisation. 2025-06-18 20:53:11 +01:00
Sadie Powell 1108e54250 We don't need to reserialize aliases now we use account identifiers. 2025-06-17 14:50:36 +01:00
Sadie Powell 8b37cdb5d5 Make the access description more useful when importing from Atheme. 2025-06-17 14:50:36 +01:00
Sadie Powell c5bff3a099 Fix various issues with language strings. 2025-06-16 12:30:42 +01:00
Sadie Powell 61b0c82884 Rework how confirmation works and make it modular. 2025-06-16 10:00:41 +01:00
Sadie Powell c4c159d197 Fix showing the MORE_INFO message. 2025-06-15 11:49:26 +01:00
Sadie Powell 04a32be1e1 Add support for code authentication via IRC.
This breaks spambots which try to register but that can't handle
reading the response from services.
2025-06-15 11:22:03 +01:00
Sadie Powell fe4b8ee669 When a command is missing in GetQueryCommand just return (MISSING). 2025-06-15 11:20:34 +01:00
Sadie Powell 34b451f36c The require_privilege option should default to yes not no. 2025-06-11 09:28:46 +01:00
Sadie Powell 16aff210fd Allow configuring fantasy commands to not require the FANTASY priv. 2025-06-10 16:05:11 +01:00
Sadie Powell 5702fb9145 Add a PREPEND subcommand to chanserv/topic. 2025-06-10 12:06:02 +01:00
Sadie Powell 783ba3fd74 Undocument DISCOURAGED; we do not use this anymore. 2025-06-08 16:21:08 +01:00
Sadie Powell f1ddd7cd02 Promote NickServ from OPTIONAL to RECOMMENDED.
Not sure why this wasn't already.
2025-06-08 16:09:43 +01:00
Sadie Powell c424c4d24d Document cs_statusupdate in the same way as other modules. 2025-06-08 12:22:45 +01:00
Sadie Powell 2d88383d9e Group some more chanserv commands. 2025-06-08 12:18:10 +01:00
Sadie Powell c73a6c621f Make the set_misc help format consistent with other set commands. 2025-06-07 13:27:30 +01:00
Sadie Powell 96a503b4d9 Fix a memory leak in the db_json module.
This reworks the module to store Data instead of a yyjson document
which does not free until yyjson_doc_free even if an array is cleared
(which annoyingly was only documented using a single comment in the
yyjson docs).
2025-06-05 22:01:45 +01:00
Sadie Powell 0632abd111 Banish irc2sql to the shadow realm. 2025-06-05 18:22:13 +01:00
TehPeGaSuS 1043e2189c Remove a reference to a config option that doesn't exist.
There's no `memoreceipt` anywhere, as far as I could check.
2025-06-01 15:29:35 +01:00
Sadie Powell 85f0d56c39 Bump for 2.1.16-git. 2025-06-01 10:14:44 +01:00
73 changed files with 1334 additions and 2466 deletions
+2 -10
View File
@@ -13,14 +13,6 @@
#
###########################################################################
exists () { # because some shells don't have test -e
if [ -f $1 -o -d $1 -o -p $1 -o -c $1 -o -b $1 ] ; then
return 0
else
return 1
fi
}
Load_Cache () {
if [ -f $SOURCE_DIR/config.cache -a -r $SOURCE_DIR/config.cache -a ! "$IGNORE_CACHE" ] ; then
echo "Using defaults from config.cache. To ignore, $SOURCE_DIR/Config -nocache"
@@ -208,7 +200,7 @@ while [ $ok -eq 0 ] ; do
INPUT=$INSTDIR
fi
if [ ! -d "$INPUT" ] ; then
if exists "$INPUT" ; then
if [ -e "$INPUT" ]; then
echo "$INPUT exists, but is not a directory!"
else
echo "$INPUT does not exist. Create it?"
@@ -220,7 +212,7 @@ while [ $ok -eq 0 ] ; do
fi
fi
fi
elif exists "$INPUT/include/services.h" ; then
elif [ -e "$INPUT/include/services.h" ]; then
echo "You cannot use the Anope source directory as a target directory."
else
ok=1
+1 -1
View File
@@ -1,6 +1,6 @@
# Only install cron.example.sh and anope.example.conf from this directory
# NOTE: I would've had this just find all files in the directory, but that would include files not needed (like this file)
set(DATA cron.example.sh anope.example.conf botserv.example.conf hostserv.example.conf modules.example.conf operserv.example.conf chanserv.example.conf global.example.conf memoserv.example.conf nickserv.example.conf chanstats.example.conf irc2sql.example.conf stats.standalone.example.conf)
set(DATA cron.example.sh anope.example.conf botserv.example.conf hostserv.example.conf modules.example.conf operserv.example.conf chanserv.example.conf global.example.conf memoserv.example.conf nickserv.example.conf chanstats.example.conf)
install(FILES ${DATA}
DESTINATION ${CONF_DIR}
)
+17 -40
View File
@@ -65,10 +65,6 @@
* will typically be disabled. If this is not the case, more
* information will be given in the documentation.
*
* [DISCOURAGED]
* Indicates a directive which may cause undesirable side effects if
* specified.
*
* [DEPRECATED]
* Indicates a directive which will disappear in a future version of
* Anope, usually because its functionality has been either
@@ -347,7 +343,7 @@ networkinfo
*
* It is recommended you DON'T change this.
*/
disallow_start_or_end = ".-"
disallow_start_or_end = ".-/"
}
/*
@@ -587,7 +583,7 @@ include
}
/*
* [OPTIONAL] NickServ
* [RECOMMENDED] NickServ
*
* Includes nickserv.example.conf, which is necessary for NickServ functionality.
*
@@ -745,7 +741,8 @@ log
* nickserv/alist - Can see the channel access list of other users
* nickserv/auspex - Can see any information with /NICKSERV INFO
* nickserv/cert - Can modify other users certificate lists
* nickserv/confirm - Can confirm other users nicknames
* nickserv/confirm/email - Can confirm other users email address change
* nickserv/confirm/register - Can confirm other users account registration
* nickserv/drop - Can drop other users nicks
* nickserv/drop/display - Allows dropping display nicks when preservedisplay is enabled
* nickserv/drop/override - Allows dropping nicks without using a confirmation code
@@ -817,7 +814,7 @@ opertype
commands = "chanserv/list chanserv/suspend chanserv/topic memoserv/staff nickserv/list nickserv/suspend operserv/mode operserv/chankill operserv/akill operserv/session operserv/modinfo operserv/sqline operserv/oper operserv/kick operserv/ignore operserv/snline"
/* What privs (see above) this opertype has */
privs = "chanserv/auspex chanserv/no-register-limit memoserv/* nickserv/auspex nickserv/confirm"
privs = "chanserv/auspex chanserv/no-register-limit memoserv/* nickserv/auspex nickserv/confirm/*"
/*
* Modes to be set on users when they identify to accounts linked to this opertype.
@@ -994,7 +991,7 @@ mail
registration_message = "Hi,
You have requested to register the nickname {nick} on {network}.
Please type \" /msg NickServ CONFIRM {code} \" to complete registration.
Please type \" /msg NickServ CONFIRM REGISTER {code} \" to complete registration.
If you don't know why this mail was sent to you, please ignore it silently.
@@ -1012,7 +1009,7 @@ mail
reset_message = "Hi,
You have requested to have the password for {nick} reset.
To reset your password, type \" /msg NickServ CONFIRM {nick} {code} \"
To reset your password, type \" /msg NickServ CONFIRM RESETPASS {nick} {code} \"
If you don't know why this mail was sent to you, please ignore it silently.
@@ -1032,7 +1029,7 @@ mail
emailchange_message = "Hi,
You have requested to change your email address from {old_email} to {new_email}.
Please type \" /msg NickServ CONFIRM {code} \" to confirm this change.
Please type \" /msg NickServ CONFIRM EMAIL {code} \" to confirm this change.
If you don't know why this mail was sent to you, please ignore it silently.
@@ -1104,11 +1101,16 @@ mail
}
/*
* [RECOMMENDED] db_flatfile
* db_flatfile
*
* This is the default flatfile database format.
* Stores your database in a custom flatfile format.
*
* This was the recommended database module in 2.0 but it is now recommended
* that you use db_json instead. You can migrate your db_flatfile database to
* db_json by loading db_flatfile BEFORE db_json, sending SIGUSR1 to force a
* database write, and then unloading db_flatfile.
*/
module
#module
{
name = "db_flatfile"
@@ -1149,14 +1151,9 @@ module
}
/*
* db_json
* [RECOMMENDED] db_json
*
* Stores your database in a JSON file.
*
* IMPORTANT: This will become the default database module in the future but is
* currently experimental and has not been fully tested so make sure you have
* db_flatfile loaded as a secondary database module if you use this as your
* primary database module.
*/
module
{
@@ -1414,23 +1411,3 @@ include
type = "file"
name = "chanstats.example.conf"
}
/*
* [DEPRECATED] IRC2SQL Gateway
*
* This module collects data about users, channels and servers. It doesn't build stats
* itself, however, it gives you the database, it's up to you how you use it.
*
* This module is deprecated in favour of the RPC interface and the rpc_data
* module. This should provide almost all of the same information as irc2sql but
* without requiring MySQL and with much better performance. If you have a use
* case which is not covered by rpc_data then please open an issue so we can
* extend rpc_data before irc2sql is removed.
*
* Requires a MySQL Database and MySQL version 5.5 or higher
*/
#include
{
type = "file"
name = "irc2sql.example.conf"
}
+12 -19
View File
@@ -100,6 +100,7 @@ module
* not in use.
* - cs_no_expire: Enables no expire. Needs founder, successor (if set) or anyone in the access list
* to be a registered nick, otherwise the channel will be dropped.
* - cs_stats: Enable Chanstats for newly registered channels
* - none: No defaults
*
* This directive is optional, if left blank, the options will default to cs_keep_modes, keeptopic, peace,
@@ -946,7 +947,7 @@ command { service = "ChanServ"; name = "AKICK"; command = "chanserv/akick"; grou
* Used for banning users from channels.
*/
module { name = "cs_ban" }
command { service = "ChanServ"; name = "BAN"; command = "chanserv/ban"; }
command { service = "ChanServ"; name = "BAN"; command = "chanserv/ban"; group = "chanserv/management"; }
/*
* cs_clone
@@ -1047,7 +1048,7 @@ command { service = "ChanServ"; name = "INVITE"; command = "chanserv/invite"; }
* Used for kicking users from channels.
*/
module { name = "cs_kick" }
command { service = "ChanServ"; name = "KICK"; command = "chanserv/kick"; }
command { service = "ChanServ"; name = "KICK"; command = "chanserv/kick"; group = "chanserv/management"; }
/*
* cs_list
@@ -1178,9 +1179,6 @@ module
{
name = "cs_seen"
/* If set, uses the older 1.8 style seen, which is less resource intensive */
simple = no
/* Sets the time to keep seen entries in the seen database. */
purgetime = 90d
}
@@ -1271,6 +1269,14 @@ module { name = "cs_status" }
command { service = "ChanServ"; name = "STATUS"; command = "chanserv/status"; }
command { service = "ChanServ"; name = "WHY"; command = "chanserv/status"; hide = yes; }
/*
* cs_statusupdate
*
* This module automatically updates users' status on channels when the
* channel's access list is modified.
*/
module { name = "cs_statusupdate" }
/*
* cs_suspend
*
@@ -1330,7 +1336,7 @@ command { service = "ChanServ"; name = "SET KEEPTOPIC"; command = "chanserv/set/
* Used for unbanning users from channels.
*/
module { name = "cs_unban" }
command { service = "ChanServ"; name = "UNBAN"; command = "chanserv/unban"; }
command { service = "ChanServ"; name = "UNBAN"; command = "chanserv/unban"; group = "chanserv/management"; }
/*
* cs_updown
@@ -1363,16 +1369,3 @@ command { service = "ChanServ"; name = "SOP"; command = "chanserv/xop"; group =
command { service = "ChanServ"; name = "AOP"; command = "chanserv/xop"; group = "chanserv/access"; }
command { service = "ChanServ"; name = "HOP"; command = "chanserv/xop"; group = "chanserv/access"; }
command { service = "ChanServ"; name = "VOP"; command = "chanserv/xop"; group = "chanserv/access"; }
/*
* Extra ChanServ related modules.
*/
/*
* cs_statusupdate
*
* This module automatically updates users' status on channels when the
* channel's access list is modified.
*/
module { name = "cs_statusupdate" }
+1 -1
View File
@@ -93,7 +93,7 @@ module
#globaloncycleup = "Services are now back online - have a nice day"
/*
* If set, Anope will hide the IRC Operator's nick in a global
* If set, Anope will hide the Services Operator's nick in a global
* message/notice.
*
* This directive is optional.
-97
View File
@@ -1,97 +0,0 @@
/*
* Example configuration file for the irc2sql gateway
*
*/
service
{
/*
* The name of the StatServ client.
*/
nick = "StatServ"
/*
* The username of the StatServ client.
*/
user = "StatServ"
/*
* The hostname of the StatServ client.
*/
host = "${services.host}"
/*
* The realname of the StatServ client.
*/
real = "Statistical Service"
/*
* The modes this client should use.
* Do not modify this unless you know what you are doing.
*
* These modes are very IRCd specific. If left commented, sane defaults
* are used based on what protocol module you have loaded.
*
* Note that setting this option incorrectly could potentially BREAK some, if
* not all, usefulness of the client. We will not support you if this client is
* unable to do certain things if this option is enabled.
*/
#modes = "+o"
/*
* An optional comma separated list of channels this service should join. Outside
* of log channels this is not very useful, as the service will just idle in the
* specified channels, and will not accept any types of commands.
*
* Prefixes may be given to the channels in the form of mode characters or prefix symbols.
*/
#channels = "@#stats,#mychan"
}
module
{
name = "irc2sql"
/*
* The name of the client that should send the CTCP VERSION requests.
* It must already exist or must be defined in the following service block.
*/
client = "StatServ"
/*
* The name of the SQL engine to use.
* This must be MySQL and must match the name in the mysql{} block
*/
engine = "mysql/main"
/*
* An optional prefix to prepended to the name of each created table.
* Do not use the same prefix for other programs.
*/
prefix = "anope_"
/*
* GeoIP - Automatically adds users geoip location to the user table.
* Tables are created by irc2sql, you have to run the
* geoipupdate script after you started Anope to download
* and import the GeoIP database.
*
* The geoip database can be the smaller "country" database or the
* larger "city" database. Comment to disable geoip lookup.
*/
geoip_database = "country"
/*
* Get the CTCP version from users
* The users connecting to the network will receive a CTCP VERSION
* request from the above configured stats client
*/
ctcpuser = "yes"
/*
* Send out CTCP VERSION requests to users during burst.
* Disable this if you restart Anope often and don't want to
* annoy your users.
*/
ctcpeob = "yes"
}
-2
View File
@@ -196,8 +196,6 @@ command { service = "MemoServ"; name = "READ"; command = "memoserv/read"; }
* Provides the command memoserv/rsend.
*
* Used to send a memo requiring a receipt be sent back once it is read.
*
* Requires configuring memoserv:memoreceipt.
*/
#module
{
+16 -16
View File
@@ -65,7 +65,7 @@ module { name = "help" }
/* This should be the names of the public facing nameservers serving the records. */
nameservers = "ns1.example.com ns2.example.com"
/* The time slave servers are allowed to cache. This should be reasonably low
/* The time secondary servers are allowed to cache for. This should be reasonably low
* if you want your records to be updated without much delay.
*/
refresh = 3600
@@ -233,7 +233,7 @@ module { name = "help" }
}
/*
* ldap [EXTRA]
* [EXTRA] ldap
*
* This module allows other modules to use LDAP. By itself, this module does nothing useful.
*/
@@ -304,13 +304,13 @@ module { name = "help" }
*
* If not set, then registration is not blocked.
*/
#disable_register_reason = "To register on this network visit https://some.misconfigured.site/register"
#disable_register_reason = "To register on this network visit https://some.misconfigured.site.example/register"
/*
* If set, the reason to give the users who try to "/msg NickServ SET EMAIL".
* If not set, then email changing is not blocked.
*/
#disable_email_reason = "To change your email address visit https://some.misconfigured.site"
#disable_email_reason = "To change your email address visit https://some.misconfigured.site.example"
}
/*
@@ -355,7 +355,7 @@ module { name = "help" }
}
/*
* mysql [EXTRA]
* [EXTRA] mysql
*
* This module allows other modules to use MySQL.
*/
@@ -402,7 +402,7 @@ module { name = "help" }
}
/*
* regex_pcre2 [EXTRA]
* [EXTRA] regex_pcre2
*
* Provides the regex engine regex/pcre, which uses version 2 of the Perl Compatible Regular
* Expressions library.
@@ -410,7 +410,7 @@ module { name = "help" }
#module { name = "regex_pcre2" }
/*
* regex_posix [EXTRA]
* [EXTRA] regex_posix
*
* Provides the regex engine regex/posix, which uses the POSIX compliant regular expressions.
*/
@@ -440,7 +440,7 @@ module
}
/*
* regex_tre [EXTRA]
* [EXTRA] regex_tre
*
* Provides the regex engine regex/tre, which uses the TRE regex library.
*/
@@ -559,7 +559,7 @@ module
}
/*
* ssl_gnutls [EXTRA]
* [EXTRA] ssl_gnutls
*
* This module provides SSL services to Anope using GnuTLS, for example to
* connect to the uplink server(s) via SSL.
@@ -598,7 +598,7 @@ module
}
/*
* ssl_openssl [EXTRA]
* [EXTRA] ssl_openssl
*
* This module provides SSL services to Anope using OpenSSL, for example to
* connect to the uplink server(s) via SSL.
@@ -691,20 +691,20 @@ module
* If set, the reason to give the users who try to "/msg NickServ REGISTER".
* If not set, then registration is not blocked.
*/
#disable_reason = "To register on this network visit https://some.misconfigured.site/register"
#disable_reason = "To register on this network visit https://some.misconfigured.site.example/register"
/*
* If set, the reason to give the users who try to "/msg NickServ SET EMAIL".
* If not set, then email changing is not blocked.
*/
#disable_email_reason = "To change your email address visit https://some.misconfigured.site"
#disable_email_reason = "To change your email address visit https://some.misconfigured.site.example"
}
/*
* sql_log
*
* This module adds an additional target option to log{} blocks
* that allows logging Service's logs to SQL. To log to SQL, add
* that allows logging Services' logs to SQL. To log to SQL, add
* the SQL service name to log:targets prefixed by sql_log:. For
* example:
*
@@ -723,8 +723,8 @@ module
/*
* sql_oper
*
* This module allows granting users services operator privileges and possibly IRC Operator
* privileges based on an external SQL database using a custom query.
* This module allows granting users Services Operator privileges based on an
* external SQL database using a custom query.
*/
#module
{
@@ -747,7 +747,7 @@ module
}
/*
* sqlite [EXTRA]
* [EXTRA] sqlite
*
* This module allows other modules to use SQLite.
*/
+43 -11
View File
@@ -118,6 +118,7 @@ module
* - msg: Messages will be sent as PRIVMSGs instead of NOTICEs
* - ns_keep_modes: Enables keepmodes, which retains user modes across sessions
* - protect: Protects the registered nickname from use by unidentified users.
* - ns_stats: Enable Chanstats for newly registered nicks
*
* This directive is optional, if left blank, the options will default to memo_signon, and
* memo_receive. If you really want no defaults, use "none" by itself as the option.
@@ -250,8 +251,8 @@ module
maxpasslen = 50
/*
* Whether all of the secondary nicks of a nick group have to expire or be
dropped before the display nick can expire or be dropped.
* Whether all of the secondary nicks of an account have to expire or be
* dropped before the display nick can expire or be dropped.
*/
preservedisplay = no
}
@@ -335,6 +336,16 @@ module
}
command { service = "NickServ"; name = "CERT"; command = "nickserv/cert"; }
/*
* ns_confirm
*
* Provides the command nickserv/confirm.
*
* Used for confirming previous account actions.
*/
module { name = "ns_confirm" }
command { service = "NickServ"; name = "CONFIRM"; command = "nickserv/confirm"; }
/*
* ns_drop
*
@@ -351,6 +362,7 @@ command { service = "NickServ"; name = "DROP"; command = "nickserv/drop"; }
* Provides various functionality relating to email addresses. This includes the
* following commands:
*
* - nickserv/confirm/email: Used for confirming email changes.
* - nickserv/getemail: Used for getting accounts by searching for emails.
* - nickserv/set/email, nickserv/saset/email: Used for setting an account's
* emailvaddress.
@@ -359,6 +371,12 @@ module
{
name = "ns_email"
/*
* The amount of time a user has after requesting a change of email address
* before it expires. Defaults to 1 day.
*/
#changeexpire = 1d
/*
* The limit to how many registered accounts can use the same email address.
* If set to 0 or left commented there will be no limit enforced when
@@ -373,7 +391,7 @@ module
*/
#remove_aliases = yes
}
command { service = "NickServ"; name = "CONFIRM EMAIL"; command = "nickserv/confirm/email"; }
command { service = "NickServ"; name = "GETEMAIL"; command = "nickserv/getemail"; permission = "nickserv/getemail"; group = "nickserv/admin"; }
command { service = "NickServ"; name = "SET EMAIL"; command = "nickserv/set/email"; }
command { service = "NickServ"; name = "SASET EMAIL"; command = "nickserv/saset/email"; permission = "nickserv/saset/email"; }
@@ -383,7 +401,7 @@ command { service = "NickServ"; name = "SASET EMAIL"; command = "nickserv/saset/
*
* Provides the commands nickserv/group, nickserv/glist, and nickserv/ungroup.
*
* Used for controlling nick groups.
* Used for controlling grouped nicknames.
*/
module
{
@@ -504,7 +522,7 @@ command { service = "NickServ"; name = "RELEASE"; command = "nickserv/recover";
/*
* ns_register
*
* Provides the commands nickserv/confirm, nickserv/register, and nickserv/resend.
* Provides the commands nickserv/confirm/register, nickserv/register, and nickserv/resend.
*
* Used for registering accounts.
*/
@@ -513,9 +531,13 @@ module
name = "ns_register"
/*
* Registration confirmation setting. Set to "none" for no registration confirmation,
* "mail" for email confirmation, and "admin" to have services operators manually confirm
* every registration. Set to "disable" to completely disable all registrations.
* The method for confirming account registrations. Possible values are:
*
* "admin" to require confirmation by a Services Operator.
* "code" to require confirmation with a code provided via IRC.
* "disable" to disable account registration.
* "mail" to require confirmation with a code provided via email.
* "none" to automatically confirm (this is the default).
*/
registration = "none"
@@ -540,18 +562,28 @@ module
*/
#unconfirmedexpire = 1d
}
command { service = "NickServ"; name = "CONFIRM"; command = "nickserv/confirm"; }
command { service = "NickServ"; name = "CONFIRM REGISTER"; command = "nickserv/confirm/register"; }
command { service = "NickServ"; name = "REGISTER"; command = "nickserv/register"; }
command { service = "NickServ"; name = "RESEND"; command = "nickserv/resend"; }
/*
* ns_resetpass
*
* Provides the command nickserv/resetpass.
* Provides the command nickserv/confirm/resetpass and nickserv/resetpass.
*
* Used for resetting passwords by emailing users a temporary one.
*/
module { name = "ns_resetpass" }
module
{
name = "ns_resetpass"
/*
* The amount of time a user has after requesting a password reset before it
* expires. Defaults to 1 day.
*/
#resetexpire = 1d
}
command { service = "NickServ"; name = "CONFIRM RESETPASS"; command = "nickserv/confirm/resetpass"; }
command { service = "NickServ"; name = "RESETPASS"; command = "nickserv/resetpass"; }
/*
+4 -4
View File
@@ -111,7 +111,7 @@ module
killonsqline = yes
/*
* Adds the nickname of the IRC Operator issuing an AKILL to the kill reason.
* Adds the nickname of the Services Operator issuing an AKILL to the kill reason.
*
* This directive is optional.
*/
@@ -287,15 +287,15 @@ command { service = "OperServ"; name = "CHANKILL"; command = "operserv/chankill"
* To use this module you must set a nameserver record for services
* so that DNS queries go to services.
*
* Alternatively, you may use a slave DNS server to hide service's IP,
* Alternatively, you may use a secondary DNS server to hide services' IP,
* provide query caching, and provide better fault tolerance.
*
* To do this using BIND, configure similar to:
*
* options { max-refresh-time 60; };
* zone "irc.example.com" IN {
* type slave;
* masters { 127.0.0.1 port 5353; };
* type secondary;
* primaries { 127.0.0.1 port 5353; };
* };
*
* Where 127.0.0.1:5353 is the IP and port services are listening on.
-514
View File
@@ -1,514 +0,0 @@
/*
* Example configuration file for Anope. After making the appropriate
* changes to this file, place it in the Anope conf directory (as
* specified in the "Config" script, default /home/username/anope/conf)
* under the name "anope.conf".
*
* The format of this file is fairly simple: three types of comments are supported:
* - All text after a '#' on a line is ignored, as in shell scripting
* - All text after '//' on a line is ignored, as in C++
* - A block of text like this one is ignored, as in C
*
* Outside of comments, there are three structures: blocks, keys, and values.
*
* A block is a named container, which contains a number of key to value pairs
* - you may think of this as an array.
*
* A block is created like so:
* foobar
* {
* moo = "cow"
* foo = bar
* }
*
* Note that nameless blocks are allowed and are often used with comments to allow
* easily commenting an entire block, for example:
* #foobar
* {
* moo = "cow"
* foo = bar
* }
* is an entirely commented block.
*
* Keys are case insensitive. Values depend on what key - generally, information is
* given in the key comment. The quoting of values (and most other syntax) is quite
* flexible, however, please do not forget to quote your strings:
*
* "This is a parameter string with spaces in it"
*
* If you need to include a double quote inside a quoted string, precede it
* by a backslash:
*
* "This string has \"double quotes\" in it"
*
* Time parameters can be specified either as an integer representing a
* number of seconds (e.g. "3600" = 1 hour), or as an integer with a unit
* specifier: "s" = seconds, "m" = minutes, "h" = hours, "d" = days.
* Combinations (such as "1h30m") are not permitted. Examples (all of which
* represent the same length of time, one day):
*
* "86400", "86400s", "1440m", "24h", "1d"
*
* In the documentation for each directive, one of the following will be
* included to indicate whether an option is required:
*
* [REQUIRED]
* Indicates a directive which must be given. Without it, Anope will
* not start.
*
* [RECOMMENDED]
* Indicates a directive which may be omitted, but omitting it may cause
* undesirable side effects.
*
* [OPTIONAL]
* Indicates a directive which is optional. If not given, the feature
* will typically be disabled. If this is not the case, more
* information will be given in the documentation.
*
* [DISCOURAGED]
* Indicates a directive which may cause undesirable side effects if
* specified.
*
* [DEPRECATED]
* Indicates a directive which will disappear in a future version of
* Anope, usually because its functionality has been either
* superseded by that of other directives or incorporated into the main
* program.
*/
/*
* [OPTIONAL] Defines
*
* You can use defines for repeated information, which can be used to easily change many
* values in the configuration at once.
*
* To use a define called foo.bar you use ${foo.bar} in your config file. You can also use
* environment variables by prefixing their name with "env." like ${env.USER}.
*/
/*
* The services.host define is used in multiple different locations throughout the
* configuration for the server name and pseudoclient hostnames.
*/
define
{
name = "services.host"
value = "stats.example.com"
}
/*
* [OPTIONAL] Additional Includes
*
* You can include additional configuration files here.
* You may also include executable files, which will be executed and
* the output from it will be included into your configuration.
*/
#include
{
type = "file"
name = "some.conf"
}
#include
{
type = "executable"
name = "/usr/bin/wget -q -O - https://some.misconfigured.network.com/stats.conf"
}
/*
* [REQUIRED] IRCd Config
*
* This section is used to set up Anope to connect to your IRC network.
* This section can be included multiple times, and Anope will attempt to
* connect to each server until it finally connects.
*
* Each uplink IRCd should have a corresponding configuration to allow Anope
* to link to it.
*
* An example configuration for InspIRCd that is compatible with the below uplink
* and serverinfo configuration would look like:
*
* # This goes in inspircd.conf, *NOT* your Anope config!
* <link name="stats.example.com"
* ipaddr="127.0.0.1"
* port="7000"
* sendpass="mypassword"
* recvpass="mypassword">
* <uline server="stats.example.com" silent="yes">
* <bind address="127.0.0.1" port="7000" type="servers">
*
* An example configuration for UnrealIRCd that is compatible with the below uplink
* and serverinfo configuration would look like:
*
* // This goes in unrealircd.conf, *NOT* your Anope config!
* listen {
* ip 127.0.0.1;
* port 7000;
* options {
* serversonly;
* };
* };
* link stats.example.com {
* incoming {
* mask *@127.0.0.1;
* };
* password "mypassword";
* class servers;
* };
* ulines { stats.example.com; };
*/
uplink
{
/*
* The IP address, hostname, or UNIX socket path of the IRC server you wish
* to connect Anope to.
* Usually, you will want to connect over 127.0.0.1 (aka localhost).
*
* NOTE: On some shell providers, this will not be an option.
*/
host = "127.0.0.1"
/*
* The protocol that Anope should use when connecting to the uplink. Can
* be set to "ipv4" (the default), "ipv6", or "unix".
*/
protocol = "ipv4"
/*
* Enable if Anope should connect using SSL.
* You must have an SSL module loaded for this to work.
*/
ssl = no
/*
* The port to connect to.
* The IRCd *MUST* be configured to listen on this port, and to accept
* server connections.
*
* Refer to your IRCd documentation for how this is to be done.
*/
port = 7000
/*
* The password to send to the IRC server for authentication.
* This must match the link block on your IRCd.
*
* Refer to your IRCd documentation for more information on link blocks.
*/
password = "mypassword"
}
/*
* [REQUIRED] Server Information
*
* This section contains information about the services server.
*/
serverinfo
{
/*
* The hostname that Anope will be seen as, it must have no conflicts with any
* other server names on the rest of your IRC network. Note that it does not have
* to be an existing hostname, just one that isn't on your network already.
*/
name = "${services.host}"
/*
* The text which should appear as the server's information in /WHOIS and similar
* queries.
*/
description = "Anope IRC Statistics"
/*
* The local address that Anope will bind to before connecting to the remote
* server. This may be useful for multihomed hosts. If omitted, Anope will let
* the Operating System choose the local address. This directive is optional.
*
* If you don't know what this means or don't need to use it, just leave this
* directive commented out.
*/
#localhost = "nowhere."
/*
* What Server ID to use for this connection?
* Note: This should *ONLY* be used for TS6/P10 IRCds. Refer to your IRCd documentation
* to see if this is needed.
*/
#id = "00A"
/*
* The filename containing the Anope process ID. The path is relative to the
* data directory.
*/
pid = "anope.pid"
/*
* The filename containing the Message of the Day. The path is relative to the
* config directory.
*/
motd = "motd.txt"
}
/*
* [REQUIRED] Protocol module
*
* This directive tells Anope which IRCd Protocol to speak when connecting.
* You MUST modify this to match the IRCd you run.
*
* Supported:
* - hybrid
* - inspircd
* - ngircd
* - plexus
* - ratbox
* - solanum
* - unrealircd
*/
module
{
name = "inspircd"
}
/*
* [REQUIRED] Network Information
*
* This section contains information about the IRC network that Anope will be
* connecting to.
*/
networkinfo
{
/*
* This is the name of the network that Anope will be running on.
*/
networkname = "LocalNet"
/*
* Set this to the maximum allowed nick length on your network.
* Be sure to set this correctly, as setting this wrong can result in
* Anope being disconnected from the network. Defaults to 31.
*/
#nicklen = 31
/* Set this to the maximum allowed ident length on your network.
* Be sure to set this correctly, as setting this wrong can result in
* Anope being disconnected from the network. Defaults to 10.
*/
#userlen = 10
/* Set this to the maximum allowed hostname length on your network.
* Be sure to set this correctly, as setting this wrong can result in
* Anope being disconnected from the network. Defaults to 64.
*/
#hostlen = 64
/* Set this to the maximum allowed channel length on your network.
* Defaults to 64.
*/
#chanlen = 32
/* The maximum number of list modes settable on a channel (such as b, e, I).
* Comment out or set to 0 to disable.
*/
modelistsize = 100
/*
* The characters allowed in hostnames. This is used for validating hostnames given
* to services, such as BotServ bot hostnames and user vhosts. Changing this is not
* recommended unless you know for sure your IRCd supports whatever characters you are
* wanting to use. Telling services to set a vhost containing characters your IRCd
* disallows could potentially break the IRCd and/or Anope.
*
* It is recommended you DON'T change this.
*/
vhost_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-"
/*
* If enabled, allows vhosts to not contain dots (.).
* Newer IRCds generally do not have a problem with this, but the same warning as
* vhost_chars applies.
*
* It is recommended you DON'T change this.
*/
allow_undotted_vhosts = no
/*
* The characters that are not allowed to be at the very beginning or very ending
* of a vhost. The same warning as vhost_chars applies.
*
* It is recommended you DON'T change this.
*/
disallow_start_or_end = ".-"
}
/*
* [REQUIRED] Anope Options
*
* This section contains various options which determine how Anope will operate.
*/
options
{
/*
* On Linux/UNIX systems Anope can setuid and setgid to this user and group
* after starting up. This is useful if Anope has to bind to privileged ports.
*/
#user = "anope"
#group = "anope"
/*
* The case mapping used by services. This must be set to a valid locale name
* installed on your machine. Anope uses this case map to compare, with
* case insensitivity, things such as nick names, channel names, etc.
*
* We provide two special casemaps shipped with Anope, ascii and rfc1459.
*
* This value should be set to what your IRCd uses, which is probably rfc1459,
* however Anope has always used ascii for comparison, so the default is ascii.
*
* Changing this value once set is not recommended.
*/
casemap = "ascii"
/*
* Sets the timeout period for reading from the uplink.
*/
readtimeout = 5s
/*
* If set, Anope will only show /stats o to IRC Operators. This directive
* is optional.
*/
#hidestatso = yes
/*
* A space-separated list of U-lined servers on your network, it is assumed that
* the servers in this list are allowed to set channel modes and Anope will
* not attempt to reverse their mode changes.
*
* WARNING: Do NOT put your normal IRC user servers in this directive.
*
* This directive is optional.
*/
#ulineservers = "services.your.network"
/*
* How long to wait between connection retries with the uplink(s).
*/
retrywait = 60s
}
/*
* [RECOMMENDED] Logging Configuration
*
* This section is used for configuring what is logged and where it is logged to.
* You may have multiple log blocks if you wish. Remember to properly secure any
* channels you choose to have Anope log to!
*/
log
{
/*
* Target(s) to log to, which may be one of the following:
* - a channel name
* - a filename
* - globops
*/
target = "stats.log"
/* Log to both stats.log and the channel #stats
*
* Note that some older IRCds, such as Ratbox, require services to be in the
* log channel to be able to message it. To do this, configure service:channels to
* join your logging channel.
*/
#target = "stats.log #stats"
/*
* The source(s) to only accept log messages from. Leave commented to allow all sources.
* This can be a users name, a channel name, one of our clients (e.g. OperServ), or a server name.
*/
#source = ""
/*
* The bot used to log generic messages which have no predefined sender if there
* is a channel in the target directive.
*/
bot = "Global"
/*
* The number of days to keep log files, only useful if you are logging to a file.
* Set to 0 to never delete old log files.
*
* Note that Anope must run 24 hours a day for this feature to work correctly.
*/
logage = 7
/*
* What types of log messages should be logged by this block. There are nine general categories:
*
* servers - Server actions, linking, squitting, etc.
* channels - Actions in channels such as joins, parts, kicks, etc.
* users - User actions such as connecting, disconnecting, changing name, etc.
* other - All other messages without a category.
* rawio - Logs raw input and output from services
* debug - Debug messages (log files can become VERY large from this).
*
* These options determine what messages from the categories should be logged. Wildcards are accepted, and
* you can also negate values with a ~. For example, "~operserv/akill operserv/*" would log all operserv
* messages except for operserv/akill. Note that processing stops at the first matching option, which
* means "* ~operserv/*" would log everything because * matches everything.
*
* Valid server options are:
* connect, quit, sync, squit
*
* Valid channel options are:
* create, destroy, join, part, kick, leave, mode
*
* Valid user options are:
* connect, disconnect, quit, nick, ident, host, mode, maxusers, oper, away
*
* Rawio and debug are simple yes/no answers, there are no types for them.
*
* Note that modules may add their own values to these options.
*/
servers = "*"
#channels = "~mode *"
users = "connect disconnect nick"
other = "*"
rawio = no
debug = no
}
/*
* [REQUIRED] MySQL Database configuration.
*
* mysql
*
* This module allows other modules to use MySQL.
*/
module
{
name = "mysql"
mysql
{
/* The name of this service. */
name = "mysql/main"
database = "anope"
server = "127.0.0.1"
username = "anope"
password = "mypassword"
port = 3306
}
}
/*
* IRC2SQL Gateway
* This module collects data about users, channels and servers. It doesn't build stats
* itself, however, it gives you the database, it's up to you how you use it.
*
* Requires a MySQL Database and MySQL version 5.5 or higher
*/
include
{
type = "file"
name = "irc2sql.example.conf"
}
+1 -1
View File
@@ -22,9 +22,9 @@ contributions they have made, are:
* Michael Wobst <wobst.michael@web.de>
* Mark Summers <mark@goopler.net>
* Matt Schatz <genius3000@g3k.solutions>
* PeGaSuS <droider.pc@gmail.com>
* Daniel Vassdal <shutter@canternet.org>
* MatthewM <mcm@they-got.us>
* PeGaSuS <droider.pc@gmail.com>
* Sebastian V. <hal9000@denorastats.org>
* Alvaro Toledo <atoledo@keldon.org>
* Dragone2 <dragone2@risposteinformatiche.it>
+19
View File
@@ -1,3 +1,22 @@
Anope Version 2.1.16
--------------------
Added support for on-IRC code confirmation.
Added the ability for fantasy commands to be executable without the FANTASY privilege.
Added the ability to prepend to topics as well as appending to them.
Changed various fields to serialize to the database as a string not an integer.
Disabled db_flatfile by default in preparation for becoming import-only.
Fixed a memory leak in the db_json module.
Fixed building on OpenSSL 1.1.1 (for now).
Fixed removed and later re-added temporary bans being removed automatically.
Fixed sometimes sending malformed LMODE messages on InspIRCd.
Fixed the "did you mean" message suggesting unloaded commands.
Fixed various issues with the example config files.
Marked db_json as the recommended database module.
Moved the BAN, UNBAN, and KICK commands to the chanserv/management group.
Removed support for the 1.8-style seen command.
Reworked confirmation to allow confirmation of multiple account actions.
When dropping a display nickname the new display will now be the oldest in the group.
Anope Version 2.1.15
--------------------
Added a workaround to the jsonrpc module for JavaScript truncating big integers.
+13
View File
@@ -1,3 +1,16 @@
Anope Version 2.1.16
--------------------
Added fantasy:require_privilege (defaults to yes).
Added the nickserv/confirm/email command.
Added the nickserv/confirm/email oper privilege.
Added the nickserv/confirm/register command.
Added the ns_confirm module.
Added {ns_email}:changeexpire (defaults to 1 day).
Added {ns_resetpass}:resetexpire (defaults to 1 day).
Removed the irc2sql module (migrate to JSON-RPC instead).
Removed {ns_seen}:simple (1.8-style seen has been removed).
Renamed the nickserv/confirm oper privilege to nickserv/confirm/register.
Anope Version 2.1.15
--------------------
Added the ns_email module.
+1 -1
View File
@@ -164,7 +164,7 @@ public:
/* Unsaved data */
/** The display nick for this account. */
NickAlias *na = nullptr;
Serialize::Reference<NickAlias> na;
/* Number of channels registered by this account */
uint16_t channelcount = 0;
/* Users online now logged into this account */
+4 -2
View File
@@ -32,10 +32,12 @@ struct CommandInfo final
Anope::string permission;
/* Group this command is in */
Anope::string group;
/* whether or not to hide this command in help output */
/* Whether to hide this command in help and suggestions */
bool hide = false;
/* Only used with fantasy */
/* Whether to prepend the channel name (only used with fantasy) */
bool prepend_channel = false;
/* Whether to require the FANTASY privilege (only used with fantasy) */
bool require_privilege = true;
};
/* Where the replies from commands go to. User inherits from this and is the normal
+1 -2
View File
@@ -102,7 +102,7 @@ namespace Language
/* Commonly used language strings */
#define CONFIRM_DROP _("Please confirm that you want to drop \002%s\002 with \002%s\032%s\032%s\002")
#define SERVICE_UNAVAILABLE _("Sorry, %s is temporarily unavailable.")
#define MORE_INFO _("\002%s\002 for more information.")
#define MORE_INFO _("Type \002%s\002 for more information.")
#define BAD_USERHOST_MASK _("Mask must be in the form \037user\037@\037host\037.")
#define BAD_EXPIRY_TIME _("Invalid expiry time.")
#define USERHOST_MASK_TOO_WIDE _("%s coverage is too wide; Please use a more specific mask.")
@@ -134,7 +134,6 @@ namespace Language
#define NICK_CANNOT_BE_REGISTERED _("Nickname \002%s\002 may not be registered.")
#define NICK_ALREADY_REGISTERED _("Nickname \002%s\002 is already registered!")
#define NICK_SET_DISPLAY_CHANGED _("The new display is now \002%s\002.")
#define NICK_CONFIRM_INVALID _("Invalid passcode has been entered, please check the email again, and retry.")
#define CHAN_NOT_ALLOWED_TO_JOIN _("You are not permitted to be on this channel.")
#define CHAN_X_INVALID _("Channel %s is not a valid channel.")
#define CHAN_REACHED_CHANNEL_LIMIT _("Sorry, you have already reached your limit of \002%d\002 channels.")
-7
View File
@@ -15,13 +15,6 @@ enum NewsType
NEWS_OPER
};
struct NewsMessages final
{
NewsType type;
Anope::string name;
const char *msgs[10];
};
struct NewsItem
: Serializable
{
+1 -1
View File
@@ -6607,7 +6607,7 @@ msgid "Service"
msgstr "Server gefunden: %d"
#, fuzzy, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr "Die Services haben den Nicknamen wieder verfügbar gemacht."
#, fuzzy
+1 -1
View File
@@ -6832,7 +6832,7 @@ msgid "Service"
msgstr "Τα services είναι ενεργά για %s"
#, fuzzy, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr "Οι υπηρεσίες απελευθέρωααν το ψευδώνυμό σου."
#, fuzzy
+177 -178
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Anope\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-29 16:46+0100\n"
"PO-Revision-Date: 2025-05-29 16:46+0100\n"
"POT-Creation-Date: 2025-06-25 15:33+0100\n"
"PO-Revision-Date: 2025-06-25 15:33+0100\n"
"Last-Translator: Sadie Powell <sadie@witchery.services>\n"
"Language-Team: English\n"
"Language: en_US\n"
@@ -148,10 +148,6 @@ msgstr ""
msgid "%s does not wish to be added to channel access lists."
msgstr ""
#, c-format
msgid "%s for more information."
msgstr ""
#, c-format
msgid "%s has been invited to %s."
msgstr ""
@@ -448,6 +444,9 @@ msgstr ""
msgid "channel LOCK {ADD|DEL|SET|LIST} [what]"
msgstr ""
msgid "channel PREPEND topic"
msgstr ""
msgid "channel RESET"
msgstr ""
@@ -520,6 +519,9 @@ msgstr ""
msgid "channel {ON | OFF}"
msgstr ""
msgid "code"
msgstr ""
msgid "email"
msgstr ""
@@ -622,9 +624,6 @@ msgstr ""
msgid "option setting"
msgstr ""
msgid "passcode"
msgstr ""
msgid "password"
msgstr ""
@@ -646,6 +645,9 @@ msgstr ""
msgid "server [reason]"
msgstr ""
msgid "type parameters"
msgstr ""
msgid "user modes"
msgstr ""
@@ -939,10 +941,6 @@ msgstr ""
msgid "%s is a client on services."
msgstr ""
#, c-format
msgid "%s is a network service."
msgstr ""
#, c-format
msgid "%s is already covered by %s."
msgstr ""
@@ -1008,14 +1006,6 @@ msgstr ""
msgid "%s is notified when new memos arrive."
msgstr ""
#, c-format
msgid "%s is on the channel right now (as %s)!"
msgstr ""
#, c-format
msgid "%s is on the channel right now!"
msgstr ""
#, c-format
msgid "%s list for %s"
msgstr ""
@@ -1036,10 +1026,6 @@ msgstr ""
msgid "%s settings:"
msgstr ""
#, c-format
msgid "%s was last seen here %s ago."
msgstr ""
#, c-format
msgid "%s was not found on %s's auto join list."
msgstr ""
@@ -1095,8 +1081,8 @@ msgstr[0] ""
msgstr[1] ""
#, c-format
msgid "%zu nickname in the group."
msgid_plural "%zu nicknames in the group."
msgid "%zu nickname in the account."
msgid_plural "%zu nicknames in the account."
msgstr[0] ""
msgstr[1] ""
@@ -1156,6 +1142,9 @@ msgstr ""
msgid "<unknown>"
msgstr ""
msgid "@nickname"
msgstr ""
#, c-format
msgid "A confirmation email has been sent to %s. Follow the instructions in it to change your email address."
msgstr ""
@@ -1305,6 +1294,9 @@ msgstr ""
msgid "Activates your assigned vhost"
msgstr ""
msgid "Add a nick to an account"
msgstr ""
msgid "Add or delete oper information for a given nick or channel. This will show to opers in the respective info command for the nick or channel."
msgstr ""
@@ -1340,7 +1332,10 @@ msgstr ""
msgid "Additionally, Services Operators with the chanserv/drop/override permission can replace code with OVERRIDE to drop without a confirmation code."
msgstr ""
msgid "Additionally, Services Operators with the nickserv/confirm permission can replace passcode with a users nick to force validate them."
msgid "Additionally, Services Operators with the nickserv/confirm/email permission can specify @nickname instead of code to force confirm another user's change of email address."
msgstr ""
msgid "Additionally, Services Operators with the nickserv/confirm/register permission can specify @nickname instead of code to force confirm another user's account registration."
msgstr ""
msgid "Additionally, Services Operators with the nickserv/drop/override permission can replace code with OVERRIDE to drop without a confirmation code."
@@ -1406,7 +1401,7 @@ msgid "All user modes on %s have been synced."
msgstr ""
#, c-format
msgid "All vhosts in the group %s have been set to %s."
msgid "All vhosts for the account %s have been set to %s."
msgstr ""
msgid "Allowed to (de)halfop themself"
@@ -1981,10 +1976,10 @@ msgstr ""
msgid "Changed usermodes of %s to %s."
msgstr ""
msgid "Changes the display used to refer to the nickname group in services. The new display MUST be a nick of the group."
msgid "Changes the display nickname used to refer to the account. The new displaynickname must already be associated with the account."
msgstr ""
msgid "Changes the display used to refer to your nickname group in services. The new display MUST be a nick of your group."
msgid "Changes the display nickname used to refer to your account. The new display nickname must already be associated with your account."
msgstr ""
msgid "Changes the founder of a channel. The new nickname must be a registered one."
@@ -2214,7 +2209,28 @@ msgstr ""
msgid "Configures underlines kicker"
msgstr ""
msgid "Confirm a passcode"
msgid "Confirm a previous account registration"
msgstr ""
msgid "Confirm a previous action"
msgstr ""
msgid "Confirm a previous change of email address"
msgstr ""
msgid "Confirm a previous password reset"
msgstr ""
#, c-format
msgid "Confirms a password reset and identifies you to the specified account. You have %s after requesting a reset to do this before your request expires. Once you have done this you can set the password using %s."
msgstr ""
#, c-format
msgid "Confirms an account registration. You have %s after registration to do this before your registration expires."
msgstr ""
#, c-format
msgid "Confirms an change of email address. You have %s after requesting an email address change to do this before your request expires."
msgstr ""
msgid "Control modes and mode locks on a channel"
@@ -2517,10 +2533,10 @@ msgstr ""
msgid "Deletes the vhost assigned to the given nick from the database."
msgstr ""
msgid "Deletes the vhost for all nicks in a group"
msgid "Deletes the vhost for all nicks in an account"
msgstr ""
msgid "Deletes the vhost for all nicks in the same group as that of the given nick."
msgid "Deletes the vhost for all nicks in the same account as that of the given nick."
msgstr ""
#, c-format
@@ -2976,14 +2992,6 @@ msgstr ""
msgid "Hosts with at least %d sessions:"
msgstr ""
#, c-format
msgid "I don't know who %s is."
msgstr ""
#, c-format
msgid "I've never seen %s on this channel."
msgstr ""
msgid "ID"
msgstr ""
@@ -3045,12 +3053,6 @@ msgstr ""
msgid "Invalid limit %s, using %d."
msgstr ""
msgid "Invalid passcode has been entered, please check the email again, and retry."
msgstr ""
msgid "Invalid passcode."
msgstr ""
#, c-format
msgid "Invalid session limit. It must be a valid integer greater than or equal to zero and less than %d."
msgstr ""
@@ -3067,9 +3069,6 @@ msgstr ""
msgid "Italics kicker"
msgstr ""
msgid "Join a group"
msgstr ""
msgid "Keep modes"
msgstr ""
@@ -3126,9 +3125,6 @@ msgstr ""
msgid "LIST [nickname]"
msgstr ""
msgid "LOGONNEWS {ADD|DEL|LIST} [text|num]"
msgstr ""
#, c-format
msgid "Language changed to %s."
msgstr ""
@@ -3195,10 +3191,10 @@ msgid "List of entries matching %s:"
msgstr ""
#, c-format
msgid "List of nicknames in the group of %s:"
msgid "List of nicknames belonging to %s:"
msgstr ""
msgid "List of nicknames in your group:"
msgid "List of nicknames belonging to your account:"
msgstr ""
msgid "List the options"
@@ -3230,10 +3226,10 @@ msgid ""
"Channels that have the NOEXPIRE option set will be prefixed by an exclamation mark. The nickname parameter is limited to Services Operators."
msgstr ""
msgid "Lists all nicknames in your group"
msgid "Lists all nicknames in your account"
msgstr ""
msgid "Lists all nicks in your group."
msgid "Lists all nicknames that belong to your account."
msgstr ""
msgid "Lists all registered channels matching the given pattern"
@@ -3336,11 +3332,11 @@ msgid "Login to %s"
msgstr ""
#, c-format
msgid "Logon news item #%d deleted."
msgid "Logon news item #%s not found!"
msgstr ""
#, c-format
msgid "Logon news item #%s not found!"
msgid "Logon news item #%u deleted."
msgstr ""
msgid "Logon news items:"
@@ -3358,10 +3354,6 @@ msgstr ""
msgid "Logs you out from %s so you lose Services Operator privileges. This command is only useful if your oper block is configured with a password."
msgstr ""
#, c-format
msgid "Looking for yourself, eh %s?"
msgstr ""
#, c-format
msgid ""
"Mainly controls mode locks and mode access (which is different from channel access) on a channel.\n"
@@ -3492,7 +3484,7 @@ msgstr ""
msgid "Masks and unregistered users may not be on access lists."
msgstr ""
msgid "Matches and returns all users that registered using given email"
msgid "Matches and returns all users that registered using given email address"
msgstr ""
#, c-format
@@ -3685,10 +3677,6 @@ msgstr ""
msgid "Nick %s is already an operator."
msgstr ""
#, c-format
msgid "Nick %s is already confirmed."
msgstr ""
#, c-format
msgid "Nick %s is an illegal nickname and cannot be used."
msgstr ""
@@ -3733,6 +3721,10 @@ msgstr ""
msgid "Nick %s will not expire."
msgstr ""
#, c-format
msgid "Nick %s does not belong to your account."
msgstr ""
#, c-format
msgid "Nick %s doesn't have a memo from you."
msgstr ""
@@ -3749,10 +3741,6 @@ msgstr ""
msgid "Nick %s is currently suspended."
msgstr ""
#, c-format
msgid "Nick %s is not in your group."
msgstr ""
#, c-format
msgid "Nick %s is not suspended."
msgstr ""
@@ -3809,9 +3797,6 @@ msgstr ""
msgid "No limit is set on %s."
msgstr ""
msgid "No logon news items to delete!"
msgstr ""
#, c-format
msgid "No matches for %s found."
msgstr ""
@@ -3860,12 +3845,6 @@ msgstr ""
msgid "No oper block for your nick."
msgstr ""
msgid "No oper news items to delete!"
msgstr ""
msgid "No random news items to delete!"
msgstr ""
msgid "No records to display."
msgstr ""
@@ -3921,9 +3900,6 @@ msgstr ""
msgid "Number"
msgstr ""
msgid "OPERNEWS {ADD|DEL|LIST} [text|num]"
msgstr ""
msgid "Online from"
msgstr ""
@@ -3939,11 +3915,11 @@ msgid "Oper info list for %s is empty."
msgstr ""
#, c-format
msgid "Oper news item #%d deleted."
msgid "Oper news item #%s not found!"
msgstr ""
#, c-format
msgid "Oper news item #%s not found!"
msgid "Oper news item #%u deleted."
msgstr ""
msgid "Oper news items:"
@@ -4065,7 +4041,7 @@ msgstr ""
msgid "Pooled/Not Active"
msgstr ""
msgid "Prevent a bot from being assigned by non IRC operators"
msgid "Prevent a bot from being assigned by non Services Operators"
msgstr ""
msgid "Prevent a bot from being assigned to a channel"
@@ -4146,9 +4122,6 @@ msgstr ""
msgid "Puts an AKILL for every nick on the specified channel. It uses the entire real ident@host for every nick, and then enforces the AKILL."
msgstr ""
msgid "RANDOMNEWS {ADD|DEL|LIST} [text|num]"
msgstr ""
msgid "REGONLY enforced by "
msgstr ""
@@ -4159,11 +4132,11 @@ msgid "REVOKE server"
msgstr ""
#, c-format
msgid "Random news item #%d deleted."
msgid "Random news item #%s not found!"
msgstr ""
#, c-format
msgid "Random news item #%s not found!"
msgid "Random news item #%u deleted."
msgstr ""
msgid "Random news items:"
@@ -4215,11 +4188,11 @@ msgid "Registered"
msgstr ""
#, c-format
msgid "Registered channels: %zu entries, %zu buckets, longest chain is %zu"
msgid "Registered accounts: %zu entries, %zu buckets, longest chain is %zu"
msgstr ""
#, c-format
msgid "Registered nick groups: %zu entries, %zu buckets, longest chain is %zu"
msgid "Registered channels: %zu entries, %zu buckets, longest chain is %zu"
msgstr ""
#, c-format
@@ -4270,7 +4243,7 @@ msgstr ""
msgid "Reload services' configuration file"
msgstr ""
msgid "Remove a nick from a group"
msgid "Remove a nick from an account"
msgstr ""
msgid "Remove all bans preventing a user from entering a channel"
@@ -4448,10 +4421,10 @@ msgstr ""
msgid "Sender"
msgstr ""
msgid "Sends a memo and requests a read receipt"
msgid "Sends a confirmation code to the nickname's email address with instructions on how to reset their password. email must be the email address associated with the account."
msgstr ""
msgid "Sends a passcode to the nickname with instructions on how to reset their password. Email must be the email address associated to the nickname."
msgid "Sends a memo and requests a read receipt"
msgstr ""
msgid "Sends all registered users a memo containing memo-text."
@@ -4534,10 +4507,6 @@ msgstr ""
msgid "Service"
msgstr ""
#, c-format
msgid "Service's hold on %s has been released."
msgstr ""
msgid "Services Operator commands"
msgstr ""
@@ -4619,6 +4588,10 @@ msgstr ""
msgid "Services' configuration has been reloaded."
msgstr ""
#, c-format
msgid "Services' hold on %s has been released."
msgstr ""
msgid "Session"
msgstr ""
@@ -4657,7 +4630,7 @@ msgstr ""
msgid "Set the channel description"
msgstr ""
msgid "Set the display of your group in services"
msgid "Set the display nickname for your account"
msgstr ""
msgid "Set the founder of a channel"
@@ -4672,7 +4645,7 @@ msgstr ""
msgid "Set the successor for a channel"
msgstr ""
msgid "Set the vhost for all nicks in a group"
msgid "Set the vhost for all nicks in an account"
msgstr ""
msgid "Set the vhost of another user"
@@ -4772,9 +4745,9 @@ msgid ""
msgstr ""
msgid ""
"Sets the vhost for all nicks in the same group as that of the given nick. If your IRCD supports vidents, then using SETALL <nick> <ident>@<hostmask> will set idents for users as well as vhosts.\n"
"Sets the vhost for all nicks in the same account as that of the given nick. If your IRCD supports vidents, then using SETALL <nick> <ident>@<hostmask> will set idents for users as well as vhosts.\n"
"\n"
"* NOTE, this will not update the vhost for any nicks added to the group after this command was used."
"* NOTE, this will not update the vhost for any nicks added to the account after this command was used."
msgstr ""
msgid "Sets the vhost for the given nick to that of the given hostmask. If your IRCD supports vidents, then using SET <nick> <ident>@<hostmask> set idents for users as well as vhosts."
@@ -4965,7 +4938,7 @@ msgstr ""
msgid "Syncs all modes set on users on the channel with the modes they should have based on their access."
msgstr ""
msgid "Syncs the vhost for all nicks in a group"
msgid "Syncs the vhost for all nicks in an account"
msgstr ""
msgid "Syntax"
@@ -5157,7 +5130,7 @@ msgid ""
" Will remove all entries that were added within the last 30 minutes."
msgstr ""
msgid "The email parameter is optional and will set the email for your nick immediately. You may also wish to SETHIDE it after registering if it isn't the default setting already."
msgid "The email parameter is optional and will set the email address for your nick immediately. You may also wish to SETHIDE it after registering if it isn't the default setting already."
msgstr ""
#, c-format
@@ -5201,6 +5174,14 @@ msgid_plural "The email address %s has reached its usage limit of %u users."
msgstr[0] ""
msgstr[1] ""
#, c-format
msgid "The email address change confirmation code you specified for %s is incorrect."
msgstr ""
#, c-format
msgid "The email address change request for %s has expired."
msgstr ""
#, c-format
msgid "The email address of %s will now be hidden from %s INFO displays."
msgstr ""
@@ -5209,6 +5190,10 @@ msgstr ""
msgid "The email address of %s will now be shown in %s INFO displays."
msgstr ""
#, c-format
msgid "The email address of %s has been changed from %s to %s."
msgstr ""
#, c-format
msgid "The entry message list for %s is full."
msgstr ""
@@ -5267,11 +5252,11 @@ msgid "The network name you specified is incorrect. Did you mean to run %s on a
msgstr ""
#, c-format
msgid "The new display MUST be a nickname of the nickname group %s."
msgid "The new display is now %s."
msgstr ""
#, c-format
msgid "The new display is now %s."
msgid "The new display nickname must belong to the %s account."
msgstr ""
#, c-format
@@ -5289,6 +5274,18 @@ msgstr ""
msgid "The oper info list for %s is full."
msgstr ""
#, c-format
msgid "The password reset code you specified for %s is incorrect."
msgstr ""
#, c-format
msgid "The password reset request for %s has expired."
msgstr ""
#, c-format
msgid "The registration confirmation code you specified for %s is incorrect."
msgstr ""
#, c-format
msgid "The services access status of %s will now be hidden from %s INFO displays."
msgstr ""
@@ -5303,10 +5300,6 @@ msgstr ""
msgid "The user with your nick has been removed. Use this command again to release services's hold on your nick."
msgstr ""
#, c-format
msgid "There are %zu memos on channel %s."
msgstr ""
msgid "There are no bots available at this time. Ask a Services Operator to create one!"
msgstr ""
@@ -5317,7 +5310,7 @@ msgstr ""
msgid "There are no forbids of type %s."
msgstr ""
msgid "There are too many nicks in your group."
msgid "There are too many nicks in your account."
msgstr ""
#, c-format
@@ -5326,7 +5319,9 @@ msgstr ""
#, c-format
msgid "There is %zu memo on channel %s."
msgstr ""
msgid_plural "There are %zu memos on channel %s."
msgstr[0] ""
msgstr[1] ""
#, c-format
msgid "There is a new memo on channel %s. Type %s%s%zu to read it."
@@ -5336,15 +5331,27 @@ msgstr ""
msgid "There is no bot assigned to %s anymore."
msgstr ""
#, c-format
msgid "There is no email address change confirmation pending for %s."
msgstr ""
msgid "There is no logon news."
msgstr ""
msgid "There is no oper news."
msgstr ""
#, c-format
msgid "There is no password reset confirmation pending for %s."
msgstr ""
msgid "There is no random news."
msgstr ""
#, c-format
msgid "There is no registration confirmation pending for %s."
msgstr ""
#, c-format
msgid "There is no such configuration block %s."
msgstr ""
@@ -5384,19 +5391,11 @@ msgstr ""
msgid "This command allows users to set the vhost of their CURRENT nick to be the vhost for all nicks in the same group."
msgstr ""
msgid "This command also creates a new group for your nickname, that will allow you to register other nicks later sharing the same configuration, the same set of memos and the same channel privileges."
msgstr ""
#, c-format
msgid "This command is an alias to the command %s."
msgstr ""
msgid ""
"This command is used by several commands as a way to confirm changes made to your account.\n"
"\n"
"This is most commonly used to confirm your email address once you register or change it.\n"
"\n"
"This is also used after the RESETPASS command has been used to force identify you to your nick so you may change your password."
msgid "This command is used by several other commands as a way to confirm changes made to your account. type can be one of:"
msgstr ""
msgid "This command lists information about the specified loaded module."
@@ -5409,19 +5408,19 @@ msgid "This command loads the module named modname from the modules directory.
msgstr ""
msgid ""
"This command makes your nickname join the target nickname's group. password is the password of the target nickname. \n"
"This command makes your nickname join the target nickname's account. password is the password of the target nickname. \n"
"\n"
"Joining a group will allow you to share your configuration, memos, and channel privileges with all the nicknames in the group, and much more!\n"
"Joining an account will allow you to share your configuration, memos, and channel privileges with all the nicknames in the account, and much more!\n"
"\n"
"A group exists as long as it is useful. This means that even if a nick of the group is dropped, you won't lose the shared things described above, as long as there is at least one nick remaining in the group.\n"
"An account exists as long as it is useful. This means that even if a nick of the account is dropped, you won't lose the shared things described above, as long as there is at least one nick remaining in the account.\n"
"\n"
"You may be able to use this command even if you have not registered your nick yet. If your nick is already registered, you'll need to identify yourself before using this command. \n"
"\n"
"It is recommended to use this command with a non-registered nick because it will be registered automatically when using this command. You may use it with a registered nick (to change your group) only if your network administrators allowed it.\n"
"It is recommended to use this command with a non-registered nick because it will be registered automatically when using this command. You may use it with a registered nick (to change your account) only if your network administrators allowed it.\n"
"\n"
"You can only be in one group at a time. Group merging is not possible. \n"
"You can only be in one account at a time. Group merging is not possible. \n"
"\n"
"Note: all the nicknames of a group have the same password."
"Note: all the nicknames of an account have the same password."
msgstr ""
msgid "This command manages your auto join list. When you identify you will automatically join the channels on your auto join list. Services Operators may provide a nick to modify other users' auto join lists."
@@ -5448,7 +5447,7 @@ msgstr ""
msgid "This command tells you what a users access is on a channel and what access entries, if any, they match. Additionally it will tell you of any auto kick entries they match. Usage of this command is limited to users who have the ability to modify access entries on the channel."
msgstr ""
msgid "This command ungroups your nick, or if given, the specified nick, from the group it is in. The ungrouped nick keeps its registration time, password, email, greet, language, and url. Everything else is reset. You may not ungroup yourself if there is only one nick in your group."
msgid "This command ungroups your nick, or if given, the specified nick, from the account it is in. The ungrouped nick keeps its registration time, password, email, greet, language, and url. Everything else is reset. You may not ungroup yourself if there is only one nick in your account."
msgstr ""
msgid "This command unloads the module named modname."
@@ -5482,7 +5481,7 @@ msgstr ""
msgid "This option makes a channel unassignable. If a bot is already assigned to the channel, it is unassigned automatically when you enable it."
msgstr ""
msgid "This option prevents a bot from being assigned to a channel by users that aren't IRC Operators."
msgid "This option prevents a bot from being assigned to a channel by users that aren't Services Operators."
msgstr ""
#, c-format
@@ -5556,11 +5555,11 @@ msgid "Turns %s's privacy option on or off for your nick. With PRIVATE set, yo
msgstr ""
#, c-format
msgid "Turns automatic protection for the nick on or off. With protection on if a user tries to use a nickname from the nick's group they will be given some time to change their nick after which %s will forcibly change their nick."
msgid "Turns automatic protection for the nick on or off. With protection on if a user tries to use a nickname from the nick's account they will be given some time to change their nick after which %s will forcibly change their nick."
msgstr ""
#, c-format
msgid "Turns automatic protection for your account on or off. With protection on if another user tries to use a nickname from your group they will be given some time to change their nick after which %s will forcibly change their nick."
msgid "Turns automatic protection for your account on or off. With protection on if another user tries to use a nickname from your account they will be given some time to change their nick after which %s will forcibly change their nick."
msgstr ""
msgid "Turns chanstats channel statistics ON or OFF for this user."
@@ -5572,6 +5571,10 @@ msgstr ""
msgid "Type"
msgstr ""
#, c-format
msgid "Type %s for more information."
msgstr ""
#, c-format
msgid "Type %scommand for help on any of the above commands."
msgstr ""
@@ -5761,11 +5764,11 @@ msgid "VHost for %s has been rejected."
msgstr ""
#, c-format
msgid "VHost for group %s set to %s."
msgid "VHost for account %s set to %s."
msgstr ""
#, c-format
msgid "VHosts for group %s have been removed."
msgid "VHosts for account %s have been removed."
msgstr ""
msgid "VIEW host"
@@ -5812,9 +5815,9 @@ msgid ""
msgstr ""
msgid ""
"Without a parameter, lists all nicknames that are in your group.\n"
"Without a parameter, lists all nicknames that belong to your account.\n"
"\n"
"With a parameter, lists all nicknames that are in the group of the given nick.\n"
"With a parameter, lists all nicknames that belong to the account of the given nick.\n"
"\n"
"Specifying a nick is limited to Services Operators."
msgstr ""
@@ -5845,7 +5848,7 @@ msgid "Word"
msgstr ""
#, c-format
msgid "You are already a member of the group of %s."
msgid "You are already a member of the account of %s."
msgstr ""
msgid "You are already identified."
@@ -5873,17 +5876,17 @@ msgstr ""
msgid "You are now a super admin."
msgstr ""
msgid "You are now identified for your nick. Change your password now."
msgstr ""
#, c-format
msgid "You are now in the group of %s."
msgid "You are now identified as %s. Change your password now using %s."
msgstr ""
#, c-format
msgid "You are over your maximum number of memos (%d). You will be unable to receive any new memos until you delete some of your current ones."
msgstr ""
msgid "You can associate multiple nicknames with your account. All nicknames will share the same configuration, set of memos, and channel privileges."
msgstr ""
msgid "You can not NOOP services."
msgstr ""
@@ -5935,7 +5938,7 @@ msgstr ""
msgid "You cannot unassign bots while persist is set on the channel."
msgstr ""
msgid "You cannot unset the email on this network."
msgid "You cannot unset the email address on this network."
msgstr ""
msgid "You cannot use this command."
@@ -5980,10 +5983,6 @@ msgstr ""
msgid "You do not have the access to change %s's modes."
msgstr ""
#, c-format
msgid "You found me, %s!"
msgstr ""
#, c-format
msgid "You have %d new memo."
msgid_plural "You have %d new memos."
@@ -6037,17 +6036,17 @@ msgstr ""
msgid "You have regained control of %s."
msgstr ""
msgid "You may drop any nick within your group."
msgid "You may drop any nick within your account."
msgstr ""
#, c-format
msgid "You may not (un)lock mode %c."
msgstr ""
msgid "You may not change the email of an unconfirmed account."
msgid "You may not change the email address of an unconfirmed account."
msgstr ""
msgid "You may not change the email of other Services Operators."
msgid "You may not change the email address of other Services Operators."
msgstr ""
msgid "You may not change the password of other Services Operators."
@@ -6097,7 +6096,7 @@ msgid "You must have the %s(ME) privilege on the channel to use this command."
msgstr ""
#, c-format
msgid "You must now supply an email for your nick. This email will allow you to retrieve your password in case you forget it. Type %semail in order to set your email."
msgid "You must now supply an email address for your nick. This email address will allow you to recover your account in case you forget your password. Type %semail in order to set your email address."
msgstr ""
#, c-format
@@ -6178,32 +6177,32 @@ msgstr ""
msgid "Your account is already confirmed."
msgstr ""
msgid "Your account is not confirmed. To confirm it, follow the instructions that were emailed to you."
msgstr ""
#, c-format
msgid "Your account is not confirmed. To confirm it, type %s."
msgstr ""
#, c-format
msgid "Your account will expire, if not confirmed, in %s."
msgstr ""
#, c-format
msgid "Your email address has been changed to %s."
msgid "Your confirmation code has been re-sent to %s."
msgstr ""
#, c-format
msgid "Your email address has been updated to %s"
msgstr ""
#, c-format
msgid "Your email address has been updated to %s."
msgstr ""
msgid "Your email address is not allowed, choose a different one."
msgstr ""
msgid "Your email address is not confirmed. To confirm it, follow the instructions that were emailed to you."
msgstr ""
#, c-format
msgid "Your email address of %s has been confirmed."
msgstr ""
#, c-format
msgid "Your email has been updated to %s"
msgstr ""
#, c-format
msgid "Your email has been updated to %s."
msgstr ""
msgid "Your memo limit has been disabled."
msgstr ""
@@ -6231,15 +6230,15 @@ msgstr ""
msgid "Your message queue has been cleared."
msgstr ""
msgid "Your nick does not belong to an account, you can't ungroup it."
msgstr ""
msgid "Your nick has been logged out."
msgstr ""
msgid "Your nick is already registered."
msgstr ""
msgid "Your nick is not grouped to anything, you can't ungroup it."
msgstr ""
msgid "Your nick isn't registered."
msgstr ""
@@ -6247,11 +6246,11 @@ msgstr ""
msgid "Your nickname is now being changed to %s"
msgstr ""
msgid "Your oper block doesn't require logging in."
#, c-format
msgid "Your nickname now belongs to the account %s."
msgstr ""
#, c-format
msgid "Your passcode has been re-sent to %s."
msgid "Your oper block doesn't require logging in."
msgstr ""
#, c-format
@@ -6262,9 +6261,6 @@ msgstr ""
msgid "Your password is too short. It must be longer than %u characters."
msgstr ""
msgid "Your password reset request has expired."
msgstr ""
msgid "Your requested vhost has been approved."
msgstr ""
@@ -6372,6 +6368,9 @@ msgstr ""
msgid "[nickname]"
msgstr ""
msgid "[nickname] code"
msgstr ""
msgid "[parameter]"
msgstr ""
+1 -1
View File
@@ -6273,7 +6273,7 @@ msgid "Service"
msgstr "Servicio"
#, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr "El nick %s retenido por los servicios ha sido liberado."
#, fuzzy
+1 -1
View File
@@ -6155,7 +6155,7 @@ msgid "Service"
msgstr "Service"
#, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr "La tutelle des Services sur %s a été enlevée."
msgid "Services Operator commands"
+1 -1
View File
@@ -6146,7 +6146,7 @@ msgid "Service"
msgstr "Servizio"
#, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr "Il blocco dei servizi sul nick %s è stato rilasciato."
msgid "Services Operator commands"
+1 -1
View File
@@ -6182,7 +6182,7 @@ msgid "Service"
msgstr "Service"
#, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr "Nick %s werd vrijgegeven."
msgid "Services Operator commands"
+1 -1
View File
@@ -6123,7 +6123,7 @@ msgid "Service"
msgstr "Usługa"
#, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr "Serwisy właśnie zwolniły nicka %s."
msgid "Services Operator commands"
+1 -1
View File
@@ -5675,7 +5675,7 @@ msgid "Service"
msgstr ""
#, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr ""
msgid "Services Operator commands"
+1 -1
View File
@@ -6067,7 +6067,7 @@ msgid "Service"
msgstr "Servis"
#, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr "Servisin %s üzerindeki bekletmesi kaldırıldı."
msgid "Services Operator commands"
+39 -13
View File
@@ -12,6 +12,39 @@
#include "module.h"
#include "modules/botserv/badwords.h"
namespace
{
Anope::string TypeToString(BadWordType bw)
{
switch (bw)
{
case BW_ANY:
return "ANY";
case BW_SINGLE:
return "SINGLE";
case BW_START:
return "START";
case BW_END:
return "END";
}
return ""; // Should never happen.
}
BadWordType StringToType(const Anope::string &bw)
{
if (bw.equals_ci("ANY") || bw.equals_ci("0"))
return BW_ANY;
if (bw.equals_ci("SINGLE") || bw.equals_ci("1"))
return BW_SINGLE;
if (bw.equals_ci("START") || bw.equals_ci("2"))
return BW_START;
if (bw.equals_ci("END") || bw.equals_ci("3"))
return BW_END;
return BW_ANY; // Should never happen.
}
}
struct BadWordImpl final
: BadWord
, Serializable
@@ -33,7 +66,7 @@ struct BadWordTypeImpl final
const auto *bw = static_cast<const BadWordImpl *>(obj);
data.Store("ci", bw->chan);
data.Store("word", bw->word);
data.Store("type", bw->type);
data.Store("type", TypeToString(bw->type));
}
Serializable *Unserialize(Serializable *obj, Serialize::Data &) const override;
@@ -138,7 +171,7 @@ Serializable *BadWordTypeImpl::Unserialize(Serializable *obj, Serialize::Data &d
if (!ci)
return NULL;
unsigned int n;
Anope::string n;
data["type"] >> n;
BadWordImpl *bw;
@@ -148,7 +181,7 @@ Serializable *BadWordTypeImpl::Unserialize(Serializable *obj, Serialize::Data &d
bw = new BadWordImpl();
bw->chan = sci;
bw->word = sword;
bw->type = static_cast<BadWordType>(n);
bw->type = StringToType(n);
BadWordsImpl *bws = ci->Require<BadWordsImpl>("badwords");
if (!obj)
@@ -244,7 +277,7 @@ private:
ListFormatter::ListEntry entry;
entry["Number"] = Anope::ToString(Number);
entry["Word"] = b->word;
entry["Type"] = b->type == BW_SINGLE ? "(SINGLE)" : (b->type == BW_START ? "(START)" : (b->type == BW_END ? "(END)" : ""));
entry["Type"] = TypeToString(b->type);
this->list.AddEntry(entry);
}
}
@@ -263,7 +296,7 @@ private:
ListFormatter::ListEntry entry;
entry["Number"] = Anope::ToString(i + 1);
entry["Word"] = b->word;
entry["Type"] = b->type == BW_SINGLE ? "(SINGLE)" : (b->type == BW_START ? "(START)" : (b->type == BW_END ? "(END)" : ""));
entry["Type"] = TypeToString(b->type);
list.AddEntry(entry);
}
}
@@ -295,14 +328,7 @@ private:
{
Anope::string opt = word.substr(pos + 1);
if (!opt.empty())
{
if (opt.equals_ci("SINGLE"))
bwtype = BW_SINGLE;
else if (opt.equals_ci("START"))
bwtype = BW_START;
else if (opt.equals_ci("END"))
bwtype = BW_END;
}
bwtype = StringToType(opt);
realword = word.substr(0, pos);
}
+2 -2
View File
@@ -173,7 +173,7 @@ class CommandBSSetPrivate final
public:
CommandBSSetPrivate(Module *creator, const Anope::string &sname = "botserv/set/private") : Command(creator, sname, 2, 2)
{
this->SetDesc(_("Prevent a bot from being assigned by non IRC operators"));
this->SetDesc(_("Prevent a bot from being assigned by non Services Operators"));
this->SetSyntax(_("\037botname\037 {\037ON|OFF\037}"));
}
@@ -214,7 +214,7 @@ public:
source.Reply(" ");
source.Reply(_(
"This option prevents a bot from being assigned to a "
"channel by users that aren't IRC Operators."
"channel by users that aren't Services Operators."
));
return true;
}
+34 -1
View File
@@ -13,6 +13,9 @@
static Module *me;
class TempBan;
static std::vector<TempBan *> tempbans;
class TempBan final
: public Timer
{
@@ -28,6 +31,21 @@ public:
, mask(banmask)
, mode(mod)
{
tempbans.push_back(this);
}
~TempBan()
{
auto it = std::find(tempbans.begin(), tempbans.end(), this);
if (it != tempbans.end())
tempbans.erase(it);
}
bool Matches(Channel *chan, ChannelMode *cmode, const Anope::string &bmask) const
{
return chan->name.equals_ci(this->channel)
&& cmode->name == this->mode
&& bmask == this->mask;
}
void Tick() override
@@ -258,10 +276,25 @@ class CSBan final
CommandCSBan commandcsban;
public:
CSBan(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), commandcsban(this)
CSBan(const Anope::string &modname, const Anope::string &creator)
: Module(modname, creator, VENDOR)
, commandcsban(this)
{
me = this;
}
EventReturn OnChannelModeUnset(Channel *c, MessageSource &setter, ChannelMode *cmode, const Anope::string &param) override
{
for (const auto *tempban : tempbans)
{
if (tempban->Matches(c, cmode, param))
{
delete tempban;
break;
}
}
return EVENT_CONTINUE;
}
};
MODULE_INIT(CSBan)
+50 -86
View File
@@ -16,7 +16,6 @@ enum TypeInfo
NEW, NICK_TO, NICK_FROM, JOIN, PART, QUIT, KICK
};
static bool simple;
struct SeenInfo;
static SeenInfo *FindInfo(const Anope::string &nick);
typedef Anope::unordered_map<SeenInfo *> database_map;
@@ -53,12 +52,54 @@ struct SeenInfoType final
{
}
static Anope::string TypeToString(TypeInfo ti)
{
switch (ti)
{
case NEW:
return "NEW";
case NICK_TO:
return "NICK_TO";
case NICK_FROM:
return "NICK_FROM";
case JOIN:
return "JOIN";
case PART:
return "PART";
case QUIT:
return "QUIT";
case KICK:
return "KICK";
}
return ""; // Should never happen.
}
static TypeInfo StringToType(const Anope::string &ti)
{
if (ti.equals_ci("NEW") || ti.equals_ci("0"))
return NEW;
if (ti.equals_ci("NICK_TO") || ti.equals_ci("1"))
return NICK_TO;
if (ti.equals_ci("NICK_FROM") || ti.equals_ci("2"))
return NICK_FROM;
if (ti.equals_ci("JOIN") || ti.equals_ci("3"))
return JOIN;
if (ti.equals_ci("PART") || ti.equals_ci("4"))
return PART;
if (ti.equals_ci("QUIT") || ti.equals_ci("5"))
return QUIT;
if (ti.equals_ci("KICK") || ti.equals_ci("6"))
return KICK;
return NEW; // Should never happen.
}
void Serialize(Serializable *obj, Serialize::Data &data) const override
{
const auto *s = static_cast<const SeenInfo *>(obj);
data.Store("nick", s->nick);
data.Store("vhost", s->vhost);
data.Store("type", s->type);
data.Store("type", TypeToString(s->type));
data.Store("nick2", s->nick2);
data.Store("channel", s->channel);
data.Store("message", s->message);
@@ -84,9 +125,9 @@ struct SeenInfoType final
s->nick = snick;
data["vhost"] >> s->vhost;
unsigned int n;
Anope::string n;
data["type"] >> n;
s->type = static_cast<TypeInfo>(n);
s->type = StringToType(n);
data["nick2"] >> s->nick2;
data["channel"] >> s->channel;
data["message"] >> s->message;
@@ -199,76 +240,6 @@ public:
class CommandSeen final
: public Command
{
static void SimpleSeen(CommandSource &source, const std::vector<Anope::string> &params)
{
if (!source.c || !source.c->ci)
{
if (source.IsOper())
source.Reply("Seen in simple mode is designed as a fantasy command only!");
return;
}
BotInfo *bi = BotInfo::Find(params[0], true);
if (bi)
{
if (bi == source.c->ci->bi)
source.Reply(_("You found me, %s!"), source.GetNick().c_str());
else
source.Reply(_("%s is a network service."), bi->nick.c_str());
return;
}
NickAlias *na = NickAlias::Find(params[0]);
if (!na)
{
source.Reply(_("I don't know who %s is."), params[0].c_str());
return;
}
if (source.GetAccount() == na->nc)
{
source.Reply(_("Looking for yourself, eh %s?"), source.GetNick().c_str());
return;
}
User *target = User::Find(params[0], true);
if (target && source.c->FindUser(target))
{
source.Reply(_("%s is on the channel right now!"), target->nick.c_str());
return;
}
for (const auto &[_, uc] : source.c->users)
{
User *u = uc->user;
if (u->Account() == na->nc)
{
source.Reply(_("%s is on the channel right now (as %s)!"), params[0].c_str(), u->nick.c_str());
return;
}
}
AccessGroup ag = source.c->ci->AccessFor(na->nc);
time_t last = 0;
for (const auto &p : ag.paths)
{
if (p.empty())
continue;
ChanAccess *a = p[p.size() - 1];
if (a->GetAccount() == na->nc && a->last_seen > last)
last = a->last_seen;
}
if (last > Anope::CurTime || !last)
source.Reply(_("I've never seen %s on this channel."), na->nick.c_str());
else
source.Reply(_("%s was last seen here %s ago."), na->nick.c_str(), Anope::Duration(Anope::CurTime - last, source.GetAccount()).c_str());
}
public:
CommandSeen(Module *creator) : Command(creator, "chanserv/seen", 1, 2)
{
@@ -281,9 +252,6 @@ public:
{
const Anope::string &target = params[0];
if (simple)
return this->SimpleSeen(source, params);
if (target.length() > IRCD->MaxNick)
{
source.Reply(_("Nick too long, max length is %zu characters."), IRCD->MaxNick);
@@ -401,17 +369,13 @@ public:
{
}
void OnReload(Configuration::Conf &conf) override
{
simple = conf.GetModule(this).Get<bool>("simple");
}
void OnExpireTick() override
{
size_t previous_size = database.size();
time_t purgetime = Config->GetModule(this).Get<time_t>("purgetime");
auto purgetime = Config->GetModule(this).Get<time_t>("purgetime", "90d");
if (!purgetime)
purgetime = Anope::DoTime("30d");
return;
auto previous_size = database.size();
for (database_map::iterator it = database.begin(), it_end = database.end(); it != it_end;)
{
database_map::iterator cur = it;
@@ -461,7 +425,7 @@ public:
private:
static void UpdateUser(const User *u, const TypeInfo Type, const Anope::string &nick, const Anope::string &nick2, const Anope::string &channel, const Anope::string &message)
{
if (simple || !u->server->IsSynced())
if (!u->server->IsSynced())
return;
SeenInfo *&info = database[nick];
+1
View File
@@ -171,6 +171,7 @@ public:
if (descriptions.count(source.command))
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply("%s", Language::Translate(source.nc, descriptions[source.command].c_str()));
return true;
}
+10 -3
View File
@@ -132,14 +132,18 @@ class CommandCSTopic final
Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << (!topic.empty() ? "to change the topic to: " : "to unset the topic") << (!topic.empty() ? topic : "");
}
void Append(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
void Combine(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params, bool append)
{
const Anope::string &topic = params[2];
Anope::string new_topic;
if (!ci->c->topic.empty())
{
new_topic = ci->c->topic + " " + topic;
if (append)
new_topic = ci->c->topic + " " + topic;
else
new_topic = topic + " " + ci->c->topic;
ci->last_topic.clear();
}
else
@@ -155,6 +159,7 @@ public:
this->SetDesc(_("Manipulate the topic of the specified channel"));
this->SetSyntax(_("\037channel\037 [SET] [\037topic\037]"));
this->SetSyntax(_("\037channel\037 APPEND \037topic\037"));
this->SetSyntax(_("\037channel\037 PREPEND \037topic\037"));
this->SetSyntax(_("\037channel\037 [UNLOCK|LOCK]"));
}
@@ -174,7 +179,9 @@ public:
else if (!ci->c)
source.Reply(CHAN_X_NOT_IN_USE, ci->name.c_str());
else if (subcmd.equals_ci("APPEND") && params.size() > 2)
this->Append(source, ci, params);
this->Combine(source, ci, params, true);
else if (subcmd.equals_ci("PREPEND") && params.size() > 2)
this->Combine(source, ci, params, false);
else
{
Anope::string topic;
+3 -2
View File
@@ -512,7 +512,7 @@ private:
if (!forbid_service)
{
Log(this) << "Unable to convert forbidden email " << email << " as os_forbid is not loaded";
Log(this) << "Unable to convert forbidden email address " << email << " as os_forbid is not loaded";
return true;
}
@@ -625,6 +625,7 @@ private:
return true;
}
auto originalflags = flags;
Anope::string accessflags;
ApplyAccess(flags, 'A', accessflags, { "ACCESS_LIST" });
ApplyAccess(flags, 'a', accessflags, { "AUTOPROTECT", "PROTECT", "PROTECTME" });
@@ -647,7 +648,7 @@ private:
auto *access = accessprov->Create();
access->SetMask(mask, ci);
access->creator = setter;
access->description = "Imported from Atheme";
access->description = "Imported from Atheme: " + flags;
access->last_seen = modifiedtime;
access->created = modifiedtime;
access->AccessUnserialize(accessflags);
+180 -151
View File
@@ -20,23 +20,13 @@ namespace fs = std::filesystem;
// TODO:
// * forking into background
// * stable key order
inline Anope::string yyjson_mut_get_astr(yyjson_mut_val *val)
inline Anope::string yyjson_get_astr(yyjson_val *val)
{
const auto *str = yyjson_mut_get_str(val);
const auto *str = yyjson_get_str(val);
return str ? str : "";
}
inline void yyjson_mut_obj_upsert(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, yyjson_mut_val *val)
{
auto *oldval = yyjson_mut_obj_get(obj, key);
if (oldval)
yyjson_mut_obj_replace(obj, yyjson_mut_str(doc, key), val);
else
yyjson_mut_obj_add_val(doc, obj, key, val);
}
class Data final
: public Serialize::Data
{
@@ -48,37 +38,42 @@ public:
Anope::map<std::stringstream> data;
// Used when writing data.
Data() = default;
Data(Serialize::Type *s_type, Serializable *obj)
{
if (obj->id)
this->id = obj->id;
s_type->Serialize(obj, *this);
}
// Used when reading data.
Data(yyjson_mut_val *elem)
Data(yyjson_val *elem)
{
size_t idx, max;
yyjson_mut_val *key, *value;
yyjson_mut_obj_foreach(elem, idx, max, key, value)
yyjson_val *key, *value;
yyjson_obj_foreach(elem, idx, max, key, value)
{
if (yyjson_mut_get_type(key) != YYJSON_TYPE_STR)
if (yyjson_get_type(key) != YYJSON_TYPE_STR)
continue;
Anope::string akey(yyjson_mut_get_str(key));
auto akey = yyjson_get_astr(key);
if (akey.equals_ci("@id"))
{
this->id = yyjson_mut_get_uint(value);
this->id = yyjson_get_uint(value);
continue;
}
if (yyjson_mut_is_bool(value))
data[akey] << yyjson_mut_get_bool(value);
else if (yyjson_mut_is_int(value))
data[akey] << yyjson_mut_get_int(value);
else if (yyjson_mut_is_null(value))
if (yyjson_is_bool(value))
data[akey] << yyjson_get_bool(value);
else if (yyjson_is_int(value))
data[akey] << yyjson_get_int(value);
else if (yyjson_is_null(value))
data[akey];
else if (yyjson_mut_is_real(value))
data[akey] << yyjson_mut_get_real(value);
else if (yyjson_mut_is_str(value))
data[akey] << yyjson_mut_get_str(value);
else if (yyjson_mut_is_uint(value))
data[akey] << yyjson_mut_get_uint(value);
else if (yyjson_is_real(value))
data[akey] << yyjson_get_real(value);
else if (yyjson_is_str(value))
data[akey] << yyjson_get_astr(value);
else if (yyjson_is_uint(value))
data[akey] << yyjson_get_uint(value);
}
}
@@ -86,16 +81,29 @@ public:
{
return data[key];
}
size_t Hash() const override
{
size_t hash = 0;
for (const auto &[_, value] : this->data)
{
auto valuestr = value.str();
if (!valuestr.empty())
hash ^= Anope::hash_cs()(valuestr);
}
return hash;
}
};
class DBJSON final
: public Module
{
private:
using DBPair = std::pair<yyjson_mut_doc *, yyjson_mut_val *>;
// Multimap from serializable type to serializable data.
using DBData = Anope::multimap<Data>;
// The databases which have already been loaded from disk.
std::unordered_map<Module *, DBPair> databases;
std::unordered_map<Module *, DBData> databases;
// Whether OnLoadDatabase has been called yet.
bool loaded = false;
@@ -201,71 +209,131 @@ private:
CreateBackup(backupdir, dbpath, monthly_backups, "%Y-%m", "\?\?\?\?-\?\?");
}
void LoadType(Serialize::Type *s_type, yyjson_mut_val *data)
void LoadType(Serialize::Type *s_type, DBData &data)
{
auto *entries = yyjson_mut_obj_get(data, s_type->GetName().c_str());
if (!entries || !yyjson_mut_is_arr(entries))
auto entries = data.equal_range(s_type->GetName());
if (entries.first == entries.second)
return;
Log(LOG_DEBUG) << "Loading " << yyjson_mut_arr_size(entries) << " " << s_type->GetName() << " records";
size_t idx, max;
yyjson_mut_val *elem;
yyjson_mut_arr_foreach(entries, idx, max, elem)
{
Data ld(elem);
s_type->Unserialize(nullptr, ld);
}
for (auto it = entries.first; it != entries.second; ++it)
s_type->Unserialize(nullptr, it->second);
}
DBPair ReadDatabase(const Anope::string &dbname)
std::optional<DBData> ReadDatabase(const Anope::string &dbname)
{
yyjson_read_err errmsg;
const auto flags = YYJSON_READ_ALLOW_TRAILING_COMMAS | YYJSON_READ_ALLOW_INVALID_UNICODE;
auto *idoc = yyjson_read_file(dbname.c_str(), flags, nullptr, &errmsg);
if (!idoc)
auto *doc = yyjson_read_file(dbname.c_str(), flags, nullptr, &errmsg);
if (!doc)
{
Log(this) << "Unable to read " << dbname << ": error #" << errmsg.code << ": " << errmsg.msg;
return { nullptr, nullptr };
return std::nullopt;
}
// We operate on a mutable document because we need to write to it later.
auto *doc = yyjson_doc_mut_copy(idoc, nullptr);
yyjson_doc_free(idoc);
auto *root = yyjson_mut_doc_get_root(doc);
if (!yyjson_mut_is_obj(root))
auto *root = yyjson_doc_get_root(doc);
if (!yyjson_is_obj(root))
{
Log(this) << "Unable to read " << dbname << ": root element is not an object";
return { nullptr, nullptr };
return std::nullopt;
}
auto version = yyjson_mut_get_uint(yyjson_mut_obj_get(root, "version"));
auto version = yyjson_get_uint(yyjson_obj_get(root, "version"));
if (version && version != ANOPE_DATABASE_VERSION)
{
Log(this) << "Refusing to load an unsupported database version: " << version;
return { nullptr, nullptr };
return std::nullopt;
}
auto generator = yyjson_mut_get_astr(yyjson_mut_obj_get(root, "generator"));
auto updated = yyjson_mut_get_uint(yyjson_mut_obj_get(root, "updated"));
auto generator = yyjson_get_astr(yyjson_obj_get(root, "generator"));
auto updated = yyjson_get_uint(yyjson_obj_get(root, "updated"));
Log(LOG_DEBUG) << "Database " << dbname << " was generated on " << Anope::strftime(updated) << " by " << generator;
auto *data = yyjson_mut_obj_get(root, "data");
if (!data || !yyjson_mut_is_obj(data))
auto *data = yyjson_obj_get(root, "data");
if (!data || !yyjson_is_obj(data))
{
Log(this) << "Unable to read " << dbname << ": data element is missing or not an object";
return { nullptr, nullptr };
return std::nullopt;
}
return { doc, data };
DBData ret;
size_t idx, max;
yyjson_val *key, *val;
yyjson_obj_foreach(data, idx, max, key, val)
{
if (!yyjson_is_str(key))
{
Log(this) << "Unable to read part of " << dbname << ": key of data element #" << idx << " is not a string";
continue;
}
auto keystr = yyjson_get_astr(key);
if (!yyjson_is_arr(val))
{
Log(this) << "Unable to read part of " << dbname << ": " << keystr << " value of data element #" << idx << " is not an array";
continue;
}
Log(LOG_DEBUG) << "Loading " << yyjson_arr_size(val) << " " << keystr << " records";
size_t idx, max;
yyjson_val *elem;
yyjson_arr_foreach(val, idx, max, elem)
{
Data ld(elem);
ret.emplace(keystr, std::move(ld));
}
}
yyjson_doc_free(doc);
return ret;
}
static void UpdateMetadata(yyjson_mut_doc *doc, yyjson_mut_val *obj)
void SaveType(yyjson_mut_doc *doc, yyjson_mut_val *obj, const Anope::string &typestr, const Data &data)
{
const auto generator = "Anope " + Anope::Version() + " " + Anope::VersionBuildString();
yyjson_mut_obj_upsert(doc, obj, "generator", yyjson_mut_strncpy(doc, generator.c_str(), generator.length()));
yyjson_mut_obj_upsert(doc, obj, "version", yyjson_mut_uint(doc, ANOPE_DATABASE_VERSION));
yyjson_mut_obj_upsert(doc, obj, "updated", yyjson_mut_int(doc, Anope::CurTime));
auto *type = yyjson_mut_obj_getn(obj, typestr.c_str(), typestr.length());
if (!type || !yyjson_mut_is_arr(type))
{
// We haven't seen this element before.
type = yyjson_mut_arr(doc);
yyjson_mut_obj_add_val(doc, obj, typestr.c_str(), type);
}
auto *elem = yyjson_mut_obj(doc);
if (data.id)
yyjson_mut_obj_add_uint(doc, elem, "@id", data.id);
for (const auto &[key, value] : data.data)
{
yyjson_mut_val *v;
switch (data.GetType(key))
{
case Serialize::DataType::BOOL:
v = yyjson_mut_bool(doc, Anope::Convert<bool>(value.str(), false));
break;
case Serialize::DataType::FLOAT:
v = yyjson_mut_real(doc, Anope::Convert<double>(value.str(), 0.0));
break;
case Serialize::DataType::INT:
v = yyjson_mut_int(doc, Anope::Convert<int64_t>(value.str(), 0));
break;
case Serialize::DataType::TEXT:
{
auto str = value.str();
v = str.empty() ? yyjson_mut_null(doc) : yyjson_mut_strncpy(doc, str.c_str(), str.length());
break;
}
case Serialize::DataType::UINT:
v = yyjson_mut_uint(doc, Anope::Convert<uint64_t>(value.str(), 0));
break;
}
auto *k = yyjson_mut_strncpy(doc, key.c_str(), key.length());
yyjson_mut_obj_add(elem, k, v);
}
yyjson_mut_arr_add_val(type, elem);
}
public:
@@ -278,15 +346,15 @@ public:
{
auto dbname = GetDatabaseFile(nullptr);
auto [doc, data] = ReadDatabase(dbname);
if (!data)
auto db = ReadDatabase(dbname);
if (!db)
return EVENT_STOP;
for (const auto &type : Serialize::Type::GetTypeOrder())
{
auto *s_type = Serialize::Type::Find(type);
if (s_type && !s_type->GetOwner())
LoadType(s_type, data);
LoadType(s_type, db.value());
}
loaded = true;
@@ -295,111 +363,73 @@ public:
void OnSaveDatabase() override
{
std::set<Module *> updated;
for (const auto &[_, s_type] : Serialize::Type::GetTypes())
// Step 1: clear the old data.
for (const auto &type : Serialize::Type::GetTypeOrder())
{
auto *s_type = Serialize::Type::Find(type);
if (!s_type)
continue; // Provider has been unloaded.
auto it = databases.find(s_type->GetOwner());
if (it == databases.end())
{
auto *doc = yyjson_mut_doc_new(nullptr);
auto *root = yyjson_mut_obj(doc);
yyjson_mut_doc_set_root(doc, root);
UpdateMetadata(doc, root);
auto *data = yyjson_mut_obj(doc);
yyjson_mut_obj_add_val(doc, root, "data", data);
databases[s_type->GetOwner()] = { doc, data };
databases.emplace(s_type->GetOwner(), DBData());
continue; // We just need to create for this type.
}
else if (updated.find(s_type->GetOwner()) == updated.end())
{
auto *doc = it->second.first;
auto *root = yyjson_mut_doc_get_root(doc);
UpdateMetadata(doc, root);
updated.insert(s_type->GetOwner());
}
// As this type has been written before we need to clear the entries
// from the previous writes so we can update it in the next loop. We
// have to do it this way so we don't purge any entries for unloaded
// modules.
it->second.erase(s_type->GetName());
}
std::set<Serialize::Type *> seen;
// Step 2: store the new data.
for (auto *item : Serializable::GetItems())
{
auto *s_type = item->GetSerializableType();
if (!s_type)
continue; // Provider has been unloaded.
// This should always be found because we create it in the previous step.
auto it = databases.find(s_type->GetOwner());
if (it == databases.end())
continue; // Type has not been registered?
auto &[doc, data] = it->second;
// If the type object doesn't exist then create it. Otherwise, clear.
// all of the previous objects stored in it.
auto *type = yyjson_mut_obj_getn(data, s_type->GetName().c_str(), s_type->GetName().length());
if (!type || yyjson_mut_get_type(type) != YYJSON_TYPE_ARR)
{
// We haven't seen this element before.
type = yyjson_mut_arr(doc);
yyjson_mut_obj_add_val(doc, data, s_type->GetName().c_str(), type);
}
else if (seen.find(s_type) == seen.end())
{
// We are reusing an existing element, clear it.
yyjson_mut_arr_clear(type);
seen.insert(s_type);
}
auto *elem = yyjson_mut_arr_add_obj(doc, type);
if (item->id)
yyjson_mut_obj_add_uint(doc, elem, "@id", item->id);
Data sd;
s_type->Serialize(item, sd);
for (const auto &[key, value] : sd.data)
{
yyjson_mut_val *v;
switch (sd.GetType(key))
{
case Serialize::DataType::BOOL:
v = yyjson_mut_bool(doc, Anope::Convert<bool>(value.str(), false));
break;
case Serialize::DataType::FLOAT:
v = yyjson_mut_real(doc, Anope::Convert<double>(value.str(), 0.0));
break;
case Serialize::DataType::INT:
v = yyjson_mut_int(doc, Anope::Convert<int64_t>(value.str(), 0));
break;
case Serialize::DataType::TEXT:
{
auto str = value.str();
v = str.empty() ? yyjson_mut_null(doc) : yyjson_mut_strncpy(doc, str.c_str(), str.length());
break;
}
case Serialize::DataType::UINT:
v = yyjson_mut_uint(doc, Anope::Convert<uint64_t>(value.str(), 0));
break;
}
auto *k = yyjson_mut_strncpy(doc, key.c_str(), key.length());
yyjson_mut_obj_add(elem, k, v);
}
if (it != databases.end())
it->second.emplace(s_type->GetName(), Data(s_type, item));
}
// Step 3: serialize to JSON.
for (auto &[mod, database] : databases)
{
auto dbname = GetDatabaseFile(mod);
BackupDatabase(dbname);
auto *doc = yyjson_mut_doc_new(nullptr);
auto *root = yyjson_mut_obj(doc);
yyjson_mut_doc_set_root(doc, root);
const auto generator = "Anope " + Anope::Version() + " " + Anope::VersionBuildString();
yyjson_mut_obj_add_strncpy(doc, root, "generator", generator.c_str(), generator.length());
yyjson_mut_obj_add_uint(doc, root, "version", ANOPE_DATABASE_VERSION);
yyjson_mut_obj_add_int(doc, root, "updated", Anope::CurTime);
auto *data = yyjson_mut_obj(doc);
yyjson_mut_obj_add_val(doc, root, "data", data);
for (const auto &[name, items] : database)
SaveType(doc, data, name, items);
Log(LOG_DEBUG) << "Writing " << dbname;
yyjson_write_err errmsg;
const auto flags = YYJSON_WRITE_ALLOW_INVALID_UNICODE | YYJSON_WRITE_NEWLINE_AT_END | YYJSON_WRITE_PRETTY;
if (!yyjson_mut_write_file(dbname.c_str(), database.first, flags, nullptr, &errmsg))
if (!yyjson_mut_write_file(dbname.c_str(), doc, flags, nullptr, &errmsg))
{
Log(this) << "Unable to write " << dbname << ": error #" << errmsg.code << ": " << errmsg.msg;
// TODO: exit??? retry???
}
yyjson_mut_doc_free(doc);
}
}
@@ -415,14 +445,13 @@ public:
const auto dbname = GetDatabaseFile(s_type->GetOwner());
auto db = ReadDatabase(dbname);
if (!db.second)
if (!db)
return; // Not much we can do here.
it = databases.emplace(s_type->GetOwner(), db).first;
it = databases.emplace(s_type->GetOwner(), std::move(db.value())).first;
}
auto &[_, data] = it->second;
LoadType(s_type, data);
LoadType(s_type, it->second);
}
};
+2 -2
View File
@@ -638,7 +638,7 @@ public:
if (!packet)
return false;
Log(LOG_DEBUG_2) << "Resolver: Notifying slave " << packet->addr.addr();
Log(LOG_DEBUG_2) << "Resolver: Notifying secondary " << packet->addr.addr();
try
{
@@ -939,7 +939,7 @@ public:
void Notify(const Anope::string &zone) override
{
/* notify slaves of the update */
/* notify secondaries of the update */
for (const auto &[ip, port] : notify)
{
sockaddrs addr;
+3 -1
View File
@@ -12,7 +12,7 @@
#include "module.h"
#include "modules/ssl.h"
#define OPENSSL_API_COMPAT 0x10100000L
#define OPENSSL_API_COMPAT 0x10101000L
#define OPENSSL_NO_DEPRECATED
#include <openssl/bio.h>
@@ -126,7 +126,9 @@ public:
SSL_CTX_set_session_id_context(client_ctx, reinterpret_cast<const unsigned char *>(context_name.c_str()), context_name.length());
SSL_CTX_set_session_id_context(server_ctx, reinterpret_cast<const unsigned char *>(context_name.c_str()), context_name.length());
#ifdef OPENSSL_VERSION_STR
Log(this) << "Module was compiled against OpenSSL version " << OPENSSL_VERSION_STR << " and is running against version " << OpenSSL_version(OPENSSL_VERSION_STRING);
#endif
}
~SSLModule()
+1 -1
View File
@@ -188,7 +188,7 @@ public:
source.permission = info.permission;
AccessGroup ag = c->ci->AccessFor(u);
bool has_fantasy = ag.HasPriv("FANTASY") || source.HasPriv("botserv/fantasy");
bool has_fantasy = !info.require_privilege || ag.HasPriv("FANTASY") || source.HasPriv("botserv/fantasy");
EventReturn MOD_RESULT;
if (has_fantasy)
+4 -4
View File
@@ -57,7 +57,7 @@ class CommandHSDelAll final
public:
CommandHSDelAll(Module *creator) : Command(creator, "hostserv/delall", 1, 1)
{
this->SetDesc(_("Deletes the vhost for all nicks in a group"));
this->SetDesc(_("Deletes the vhost for all nicks in an account"));
this->SetSyntax(_("\037nick\037"));
}
@@ -80,8 +80,8 @@ public:
na = alias;
na->RemoveVHost();
}
Log(LOG_ADMIN, source, this) << "for all nicks in group " << nc->display;
source.Reply(_("VHosts for group \002%s\002 have been removed."), nc->display.c_str());
Log(LOG_ADMIN, source, this) << "for all nicks in account " << nc->display;
source.Reply(_("VHosts for account \002%s\002 have been removed."), nc->display.c_str());
}
else
source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
@@ -91,7 +91,7 @@ public:
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_("Deletes the vhost for all nicks in the same group as that of the given nick."));
source.Reply(_("Deletes the vhost for all nicks in the same account as that of the given nick."));
return true;
}
};
+2 -2
View File
@@ -39,7 +39,7 @@ public:
CommandHSGroup(Module *creator) : Command(creator, "hostserv/group", 0, 0)
{
this->SetDesc(_("Syncs the vhost for all nicks in a group"));
this->SetDesc(_("Syncs the vhost for all nicks in an account"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
@@ -54,7 +54,7 @@ public:
if (na && source.GetAccount() == na->nc && na->HasVHost())
{
this->Sync(na);
source.Reply(_("All vhosts in the group \002%s\002 have been set to \002%s\002."),
source.Reply(_("All vhosts for the account \002%s\002 have been set to \002%s\002."),
source.nc->display.c_str(), na->GetVHostMask().c_str());
}
else
+4 -4
View File
@@ -122,7 +122,7 @@ class CommandHSSetAll final
public:
CommandHSSetAll(Module *creator) : Command(creator, "hostserv/setall", 2, 2)
{
this->SetDesc(_("Set the vhost for all nicks in a group"));
this->SetDesc(_("Set the vhost for all nicks in an account"));
this->SetSyntax(_("\037nick\037 \037hostmask\037"));
}
@@ -193,7 +193,7 @@ public:
na->SetVHost(user, host, source.GetNick());
this->Sync(na);
FOREACH_MOD(OnSetVHost, (na));
source.Reply(_("VHost for group \002%s\002 set to \002%s\002."), nick.c_str(), na->GetVHostMask().c_str());
source.Reply(_("VHost for account \002%s\002 set to \002%s\002."), nick.c_str(), na->GetVHostMask().c_str());
}
bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
@@ -201,13 +201,13 @@ public:
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Sets the vhost for all nicks in the same group as that "
"Sets the vhost for all nicks in the same account as that "
"of the given nick. If your IRCD supports vidents, then "
"using SETALL <nick> <ident>@<hostmask> will set idents "
"for users as well as vhosts."
"\n\n"
"* NOTE, this will not update the vhost for any nicks "
"added to the group after this command was used."
"added to the account after this command was used."
));
return true;
}
-1
View File
@@ -1 +0,0 @@
build_subdir(${CMAKE_CURRENT_SOURCE_DIR})
-325
View File
@@ -1,325 +0,0 @@
/*
*
* (C) 2013-2025 Anope Team
* Contact us at team@anope.org
*
* Please read COPYING and README for further details.
*/
#include "irc2sql.h"
void IRC2SQL::OnShutdown()
{
// TODO: test if we really have to use blocking query here
// (sometimes mysql get unloaded before the other thread executed all queries)
if (this->sql)
SQL::Result r = this->sql->RunQuery(SQL::Query("CALL " + prefix + "OnShutdown()"));
quitting = true;
}
void IRC2SQL::OnReload(Configuration::Conf &conf)
{
const auto &block = Config->GetModule(this);
prefix = block.Get<const Anope::string>("prefix", "anope_");
GeoIPDB = block.Get<const Anope::string>("geoip_database");
ctcpuser = block.Get<bool>("ctcpuser", "no");
ctcpeob = block.Get<bool>("ctcpeob", "yes");
Anope::string engine = block.Get<const Anope::string>("engine");
this->sql = ServiceReference<SQL::Provider>("SQL::Provider", engine);
if (sql)
this->CheckTables();
else
Log() << "IRC2SQL: no database connection to " << engine;
const Anope::string &snick = block.Get<const Anope::string>("client");
if (snick.empty())
throw ConfigException(Module::name + ": <client> must be defined");
StatServ = BotInfo::Find(snick, true);
if (!StatServ)
throw ConfigException(Module::name + ": no bot named " + snick);
if (firstrun)
{
firstrun = false;
for (Anope::map<Server *>::const_iterator it = Servers::ByName.begin(); it != Servers::ByName.end(); ++it)
{
this->OnNewServer(it->second);
}
for (const auto &[_, c] : ChannelList)
{
this->OnChannelCreate(c);
}
for (const auto &[_, u] : UserListByNick)
{
bool exempt = false;
this->OnUserConnect(u, exempt);
for (const auto &[_, uc] : u->chans)
{
this->OnJoinChannel(u, uc->chan);
}
}
}
}
void IRC2SQL::OnNewServer(Server *server)
{
query = "INSERT DELAYED INTO `" + prefix + "server` (name, hops, comment, link_time, online, ulined) "
"VALUES (@name@, @hops@, @comment@, now(), 'Y', @ulined@) "
"ON DUPLICATE KEY UPDATE name=VALUES(name), hops=VALUES(hops), comment=VALUES(comment), "
"link_time=VALUES(link_time), online=VALUES(online), ulined=VALUES(ulined)";
query.SetValue("name", server->GetName());
query.SetValue("hops", server->GetHops());
query.SetValue("comment", server->GetDescription());
query.SetValue("ulined", server->IsULined() ? "Y" : "N");
this->RunQuery(query);
}
void IRC2SQL::OnServerQuit(Server *server)
{
if (quitting)
return;
query = "CALL " + prefix + "ServerQuit(@name@)";
query.SetValue("name", server->GetName());
this->RunQuery(query);
}
void IRC2SQL::OnUserConnect(User *u, bool &exempt)
{
if (!introduced_myself)
{
this->OnNewServer(Me);
introduced_myself = true;
}
query = "CALL " + prefix + "UserConnect(@nick@,@host@,@vhost@,@chost@,@realname@,@ip@,@ident@,@vident@,"
"@account@,@secure@,@fingerprint@,@signon@,@server@,@uuid@,@modes@,@oper@)";
query.SetValue("nick", u->nick);
query.SetValue("host", u->host);
query.SetValue("vhost", u->vhost);
query.SetValue("chost", u->chost);
query.SetValue("realname", u->realname);
query.SetValue("ip", u->ip.addr());
query.SetValue("ident", u->GetIdent());
query.SetValue("vident", u->GetVIdent());
query.SetValue("secure", u->IsSecurelyConnected() ? "Y" : "N");
query.SetValue("account", u->IsIdentified() ? u->Account()->display : "");
query.SetValue("fingerprint", u->fingerprint);
query.SetValue("signon", u->signon);
query.SetValue("server", u->server->GetName());
query.SetValue("uuid", u->GetUID());
query.SetValue("modes", u->GetModes());
query.SetValue("oper", u->HasMode("OPER") ? "Y" : "N");
this->RunQuery(query);
if (ctcpuser && (Me->IsSynced() || ctcpeob) && u->server != Me)
IRCD->SendPrivmsg(StatServ, u->GetUID(), Anope::FormatCTCP("VERSION"));
}
void IRC2SQL::OnUserQuit(User *u, const Anope::string &msg)
{
if (quitting || u->server->IsQuitting())
return;
query = "CALL " + prefix + "UserQuit(@nick@)";
query.SetValue("nick", u->nick);
this->RunQuery(query);
}
void IRC2SQL::OnUserNickChange(User *u, const Anope::string &oldnick)
{
query = "UPDATE `" + prefix + "user` SET nick=@newnick@ WHERE nick=@oldnick@";
query.SetValue("newnick", u->nick);
query.SetValue("oldnick", oldnick);
this->RunQuery(query);
}
void IRC2SQL::OnUserAway(User *u, const Anope::string &message)
{
query = "UPDATE `" + prefix + "user` SET away=@away@, awaymsg=@awaymsg@ WHERE nick=@nick@";
query.SetValue("away", (!message.empty()) ? "Y" : "N");
query.SetValue("awaymsg", message);
query.SetValue("nick", u->nick);
this->RunQuery(query);
}
void IRC2SQL::OnFingerprint(User *u)
{
query = "UPDATE `" + prefix + "user` SET secure=@secure@, fingerprint=@fingerprint@ WHERE nick=@nick@";
query.SetValue("secure", u->IsSecurelyConnected() ? "Y" : "N");
query.SetValue("fingerprint", u->fingerprint);
query.SetValue("nick", u->nick);
this->RunQuery(query);
}
void IRC2SQL::OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname)
{
query = "UPDATE `" + prefix + "user` SET modes=@modes@, oper=@oper@ WHERE nick=@nick@";
query.SetValue("nick", u->nick);
query.SetValue("modes", u->GetModes());
query.SetValue("oper", u->HasMode("OPER") ? "Y" : "N");
this->RunQuery(query);
}
void IRC2SQL::OnUserModeUnset(const MessageSource &setter, User *u, const Anope::string &mname)
{
this->OnUserModeSet(setter, u, mname);
}
void IRC2SQL::OnUserLogin(User *u)
{
query = "UPDATE `" + prefix + "user` SET account=@account@ WHERE nick=@nick@";
query.SetValue("nick", u->nick);
query.SetValue("account", u->IsIdentified() ? u->Account()->display : "");
this->RunQuery(query);
}
void IRC2SQL::OnNickLogout(User *u)
{
this->OnUserLogin(u);
}
void IRC2SQL::OnSetDisplayedHost(User *u)
{
query = "UPDATE `" + prefix + "user` "
"SET vhost=@vhost@ "
"WHERE nick=@nick@";
query.SetValue("vhost", u->GetDisplayedHost());
query.SetValue("nick", u->nick);
this->RunQuery(query);
}
void IRC2SQL::OnChannelCreate(Channel *c)
{
query = "INSERT INTO `" + prefix + "chan` (channel, topic, topicauthor, topictime, modes) "
"VALUES (@channel@,@topic@,@topicauthor@,@topictime@,@modes@) "
"ON DUPLICATE KEY UPDATE channel=VALUES(channel), topic=VALUES(topic),"
"topicauthor=VALUES(topicauthor), topictime=VALUES(topictime), modes=VALUES(modes)";
query.SetValue("channel", c->name);
query.SetValue("topic", c->topic);
query.SetValue("topicauthor", c->topic_setter);
if (c->topic_ts > 0)
query.SetValue("topictime", c->topic_ts);
else
query.SetValue("topictime", "NULL", false);
query.SetValue("modes", c->GetModes(true,true));
this->RunQuery(query);
}
void IRC2SQL::OnChannelDelete(Channel *c)
{
query = "DELETE FROM `" + prefix + "chan` WHERE channel=@channel@";
query.SetValue("channel", c->name);
this->RunQuery(query);
}
void IRC2SQL::OnJoinChannel(User *u, Channel *c)
{
Anope::string modes;
ChanUserContainer *cu = u->FindChannel(c);
if (cu)
modes = cu->status.Modes();
query = "CALL " + prefix + "JoinUser(@nick@,@channel@,@modes@)";
query.SetValue("nick", u->nick);
query.SetValue("channel", c->name);
query.SetValue("modes", modes);
this->RunQuery(query);
}
EventReturn IRC2SQL::OnChannelModeSet(Channel *c, MessageSource &setter, ChannelMode *mode, const ModeData &data)
{
if (mode->type == MODE_STATUS)
{
User *u = User::Find(data.value);
if (u == NULL)
return EVENT_CONTINUE;
ChanUserContainer *cc = u->FindChannel(c);
if (cc == NULL)
return EVENT_CONTINUE;
query = "UPDATE `" + prefix + "user` AS u, `" + prefix + "ison` AS i, `" + prefix + "chan` AS c"
" SET i.modes=@modes@"
" WHERE u.nick=@nick@ AND c.channel=@channel@"
" AND u.nickid = i.nickid AND c.chanid = i.chanid";
query.SetValue("nick", u->nick);
query.SetValue("modes", cc->status.Modes());
query.SetValue("channel", c->name);
this->RunQuery(query);
}
else
{
query = "UPDATE `" + prefix + "chan` SET modes=@modes@ WHERE channel=@channel@";
query.SetValue("channel", c->name);
query.SetValue("modes", c->GetModes(true,true));
this->RunQuery(query);
}
return EVENT_CONTINUE;
}
EventReturn IRC2SQL::OnChannelModeUnset(Channel *c, MessageSource &setter, ChannelMode *mode, const Anope::string &param)
{
this->OnChannelModeSet(c, setter, mode, param);
return EVENT_CONTINUE;
}
void IRC2SQL::OnLeaveChannel(User *u, Channel *c)
{
if (quitting)
return;
/*
* user is quitting, we already received a OnUserQuit()
* at this point the user is already removed from SQL and all channels
*/
if (u->Quitting())
return;
query = "CALL " + prefix + "PartUser(@nick@,@channel@)";
query.SetValue("nick", u->nick);
query.SetValue("channel", c->name);
this->RunQuery(query);
}
void IRC2SQL::OnTopicUpdated(User *source, Channel *c, const Anope::string &user, const Anope::string &topic)
{
query = "UPDATE `" + prefix + "chan` "
"SET topic=@topic@, topicauthor=@author@, topictime=FROM_UNIXTIME(@time@) "
"WHERE channel=@channel@";
query.SetValue("topic", c->topic);
query.SetValue("author", c->topic_setter);
query.SetValue("time", c->topic_ts);
query.SetValue("channel", c->name);
this->RunQuery(query);
}
void IRC2SQL::OnBotNotice(User *u, BotInfo *bi, Anope::string &message, const Anope::map<Anope::string> &tags)
{
if (bi != StatServ)
return;
Anope::string ctcpname, ctcpbody;
if (!Anope::ParseCTCP(message, ctcpname, ctcpbody) || ctcpname != "VERSION")
return;
if (u->HasExt("CTCPVERSION"))
return;
u->Extend<bool>("CTCPVERSION");
auto versionstr = Anope::NormalizeBuffer(ctcpbody);
if (versionstr.empty())
return;
query = "UPDATE `" + prefix + "user` "
"SET version=@version@ "
"WHERE nick=@nick@";
query.SetValue("version", versionstr);
query.SetValue("nick", u->nick);
this->RunQuery(query);
}
MODULE_INIT(IRC2SQL)
-88
View File
@@ -1,88 +0,0 @@
/*
*
* (C) 2013-2025 Anope Team
* Contact us at team@anope.org
*
* Please read COPYING and README for further details.
*/
#pragma once
#include "module.h"
#include "modules/sql.h"
class MySQLInterface final
: public SQL::Interface
{
public:
MySQLInterface(Module *o) : SQL::Interface(o) { }
void OnResult(const SQL::Result &r) override
{
}
void OnError(const SQL::Result &r) override
{
if (!r.GetQuery().query.empty())
Log(LOG_DEBUG) << "irc2sql: Error executing query " << r.finished_query << ": " << r.GetError();
else
Log(LOG_DEBUG) << "irc2sql: Error executing query: " << r.GetError();
}
};
class IRC2SQL final
: public Module
{
ServiceReference<SQL::Provider> sql;
MySQLInterface sqlinterface;
SQL::Query query;
std::vector<Anope::string> TableList, ProcedureList, EventList;
Anope::string prefix, GeoIPDB;
bool quitting, introduced_myself, ctcpuser, ctcpeob, firstrun;
BotInfo *StatServ;
PrimitiveExtensibleItem<bool> versionreply;
void RunQuery(const SQL::Query &q);
void GetTables();
bool HasTable(const Anope::string &table);
bool HasProcedure(const Anope::string &table);
bool HasEvent(const Anope::string &table);
void CheckTables();
public:
IRC2SQL(const Anope::string &modname, const Anope::string &creator) :
Module(modname, creator, EXTRA | VENDOR), sql("", ""), sqlinterface(this), versionreply(this, "CTCPVERSION")
{
firstrun = true;
quitting = false;
introduced_myself = false;
}
void OnShutdown() override;
void OnReload(Configuration::Conf &config) override;
void OnNewServer(Server *server) override;
void OnServerQuit(Server *server) override;
void OnUserConnect(User *u, bool &exempt) override;
void OnUserQuit(User *u, const Anope::string &msg) override;
void OnUserNickChange(User *u, const Anope::string &oldnick) override;
void OnUserAway(User *u, const Anope::string &message) override;
void OnFingerprint(User *u) override;
void OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname) override;
void OnUserModeUnset(const MessageSource &setter, User *u, const Anope::string &mname) override;
void OnUserLogin(User *u) override;
void OnNickLogout(User *u) override;
void OnSetDisplayedHost(User *u) override;
void OnChannelCreate(Channel *c) override;
void OnChannelDelete(Channel *c) override;
void OnLeaveChannel(User *u, Channel *c) override;
void OnJoinChannel(User *u, Channel *c) override;
EventReturn OnChannelModeSet(Channel *c, MessageSource &setter, ChannelMode *mode, const ModeData &data) override;
EventReturn OnChannelModeUnset(Channel *c, MessageSource &setter, ChannelMode *mode, const Anope::string &param) override;
void OnTopicUpdated(User *source, Channel *c, const Anope::string &user, const Anope::string &topic) override;
void OnBotNotice(User *u, BotInfo *bi, Anope::string &message, const Anope::map<Anope::string> &tags) override;
};
-343
View File
@@ -1,343 +0,0 @@
/*
*
* (C) 2013-2025 Anope Team
* Contact us at team@anope.org
*
* Please read COPYING and README for further details.
*/
#include "irc2sql.h"
void IRC2SQL::CheckTables()
{
Anope::string geoquery("");
if (firstrun)
{
/*
* reset some tables to make sure they are really empty
*/
this->sql->RunQuery("TRUNCATE TABLE " + prefix + "user");
this->sql->RunQuery("TRUNCATE TABLE " + prefix + "chan");
this->sql->RunQuery("TRUNCATE TABLE " + prefix + "ison");
this->sql->RunQuery("UPDATE `" + prefix + "server` SET currentusers=0, online='N'");
}
this->GetTables();
if (GeoIPDB.equals_ci("country"))
{
if (!this->HasTable(prefix + "geoip_country"))
{
query = "CREATE TABLE `" + prefix + "geoip_country` ("
"`start` INT UNSIGNED NOT NULL,"
"`end` INT UNSIGNED NOT NULL,"
"`countrycode` varchar(2),"
"`countryname` varchar(50),"
"PRIMARY KEY `end` (`end`),"
"KEY `start` (`start`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
}
else if (GeoIPDB.equals_ci("city"))
{
if (!this->HasTable(prefix + "geoip_city_blocks"))
{
query = "CREATE TABLE `" + prefix + "geoip_city_blocks` ("
"`start` INT UNSIGNED NOT NULL,"
"`end` INT UNSIGNED NOT NULL,"
"`locId` INT UNSIGNED NOT NULL,"
"PRIMARY KEY `end` (`end`),"
"KEY `start` (`start`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "geoip_city_location"))
{
query = "CREATE TABLE `" + prefix + "geoip_city_location` ("
"`locId` INT UNSIGNED NOT NULL,"
"`country` CHAR(2) NOT NULL,"
"`region` CHAR(2) NOT NULL,"
"`city` VARCHAR(50),"
"`latitude` FLOAT,"
"`longitude` FLOAT,"
"`areaCode` INT,"
"PRIMARY KEY (`locId`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "geoip_city_region"))
{ query = "CREATE TABLE `" + prefix + "geoip_city_region` ("
"`country` CHAR(2) NOT NULL,"
"`region` CHAR(2) NOT NULL,"
"`regionname` VARCHAR(100) NOT NULL,"
"PRIMARY KEY (`country`,`region`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
}
if (!this->HasTable(prefix + "server"))
{
query = "CREATE TABLE `" + prefix + "server` ("
"`id` int UNSIGNED NOT NULL AUTO_INCREMENT,"
"`name` varchar(64) NOT NULL,"
"`hops` tinyint NOT NULL,"
"`comment` varchar(255) NOT NULL,"
"`link_time` datetime DEFAULT NULL,"
"`split_time` datetime DEFAULT NULL,"
"`version` varchar(127) DEFAULT NULL,"
"`currentusers` int DEFAULT 0,"
"`online` enum('Y','N') NOT NULL DEFAULT 'Y',"
"`ulined` enum('Y','N') NOT NULL DEFAULT 'N',"
"PRIMARY KEY (`id`),"
"UNIQUE KEY `name` (`name`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "chan"))
{
query = "CREATE TABLE `" + prefix + "chan` ("
"`chanid` int UNSIGNED NOT NULL AUTO_INCREMENT,"
"`channel` varchar(255) NOT NULL,"
"`topic` varchar(512) DEFAULT NULL,"
"`topicauthor` varchar(255) DEFAULT NULL,"
"`topictime` datetime DEFAULT NULL,"
"`modes` varchar(512) DEFAULT NULL,"
"PRIMARY KEY (`chanid`),"
"UNIQUE KEY `channel`(`channel`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "user"))
{
query = "CREATE TABLE `" + prefix + "user` ("
"`nickid` int UNSIGNED NOT NULL AUTO_INCREMENT,"
"`nick` varchar(255) NOT NULL DEFAULT '',"
"`host` varchar(255) NOT NULL DEFAULT '',"
"`vhost` varchar(255) NOT NULL DEFAULT '',"
"`chost` varchar(255) NOT NULL DEFAULT '',"
"`realname` varchar(255) NOT NULL DEFAULT '',"
"`ip` varchar(255) NOT NULL DEFAULT '',"
"`ident` varchar(32) NOT NULL DEFAULT '',"
"`vident` varchar(32) NOT NULL DEFAULT '',"
"`modes` varchar(255) NOT NULL DEFAULT '',"
"`account` varchar(255) NOT NULL DEFAULT '',"
"`secure` enum('Y','N') NOT NULL DEFAULT 'N',"
"`fingerprint` varchar(128) NOT NULL DEFAULT '',"
"`signon` datetime DEFAULT NULL,"
"`server` varchar(255) NOT NULL DEFAULT '',"
"`servid` int UNSIGNED NOT NULL DEFAULT '0',"
"`uuid` varchar(32) NOT NULL DEFAULT '',"
"`oper` enum('Y','N') NOT NULL DEFAULT 'N',"
"`away` enum('Y','N') NOT NULL DEFAULT 'N',"
"`awaymsg` varchar(255) NOT NULL DEFAULT '',"
"`version` varchar(255) NOT NULL DEFAULT '',"
"`geocode` varchar(16) NOT NULL DEFAULT '',"
"`geocountry` varchar(64) NOT NULL DEFAULT '',"
"`georegion` varchar(100) NOT NULL DEFAULT '',"
"`geocity` varchar(128) NOT NULL DEFAULT '',"
"`locId` INT UNSIGNED,"
"PRIMARY KEY (`nickid`),"
"UNIQUE KEY `nick` (`nick`),"
"KEY `servid` (`servid`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "ison"))
{
query = "CREATE TABLE `" + prefix + "ison` ("
"`nickid` int unsigned NOT NULL default '0',"
"`chanid` int unsigned NOT NULL default '0',"
"`modes` varchar(255) NOT NULL default '',"
"PRIMARY KEY (`nickid`,`chanid`),"
"KEY `modes` (`modes`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "maxusers"))
{
query = "CREATE TABLE `" + prefix + "maxusers` ("
"`name` VARCHAR(255) NOT NULL,"
"`maxusers` int NOT NULL,"
"`maxtime` DATETIME NOT NULL,"
"`lastused` DATETIME NOT NULL,"
"UNIQUE KEY `name` (`name`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
if (this->HasProcedure(prefix + "UserConnect"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "UserConnect"));
if (GeoIPDB.equals_ci("country"))
geoquery = "UPDATE `" + prefix + "user` AS u "
"JOIN ( SELECT `countrycode`, `countryname` "
"FROM `" + prefix + "geoip_country` "
"WHERE INET_ATON(ip_) <= `end` "
"AND `start` <= INET_ATON(ip_) "
"ORDER BY `end` ASC LIMIT 1 ) as c "
"SET u.geocode = c.countrycode, u.geocountry = c.countryname "
"WHERE u.nick = nick_; ";
else if (GeoIPDB.equals_ci("city"))
geoquery = "UPDATE `" + prefix + "user` as u "
"JOIN ( SELECT * FROM `" + prefix + "geoip_city_location` "
"WHERE `locID` = ( SELECT `locID` "
"FROM `" + prefix + "geoip_city_blocks` "
"WHERE INET_ATON(ip_) <= `end` "
"AND `start` <= INET_ATON(ip_) "
"ORDER BY `end` ASC LIMIT 1 ) "
") as l "
"SET u.geocode = l.country, "
"u.geocity = l.city, "
"u.locID = l.locID, "
"u.georegion = ( SELECT `regionname` "
"FROM `" + prefix + "geoip_city_region` "
"WHERE `country` = l.country "
"AND `region` = l.region )"
"WHERE u.nick = nick_;";
query = "CREATE PROCEDURE `" + prefix + "UserConnect`"
"(nick_ varchar(255), host_ varchar(255), vhost_ varchar(255), "
"chost_ varchar(255), realname_ varchar(255), ip_ varchar(255), "
"ident_ varchar(255), vident_ varchar(255), account_ varchar(255), "
"secure_ enum('Y','N'), fingerprint_ varchar(255), signon_ int, "
"server_ varchar(255), uuid_ varchar(32), modes_ varchar(255), "
"oper_ enum('Y','N')) "
"BEGIN "
"DECLARE cur int;"
"DECLARE max int;"
"INSERT INTO `" + prefix + "user` "
"(nick, host, vhost, chost, realname, ip, ident, vident, account, "
"secure, fingerprint, signon, server, uuid, modes, oper) "
"VALUES (nick_, host_, vhost_, chost_, realname_, ip_, ident_, vident_, "
"account_, secure_, fingerprint_, FROM_UNIXTIME(signon_), server_, "
"uuid_, modes_, oper_) "
"ON DUPLICATE KEY UPDATE host=VALUES(host), vhost=VALUES(vhost), "
"chost=VALUES(chost), realname=VALUES(realname), ip=VALUES(ip), "
"ident=VALUES(ident), vident=VALUES(vident), account=VALUES(account), "
"secure=VALUES(secure), fingerprint=VALUES(fingerprint), signon=VALUES(signon), "
"server=VALUES(server), uuid=VALUES(uuid), modes=VALUES(modes), "
"oper=VALUES(oper);"
"UPDATE `" + prefix + "user` as `u`, `" + prefix + "server` as `s`"
"SET u.servid = s.id, "
"s.currentusers = s.currentusers + 1 "
"WHERE s.name = server_ AND u.nick = nick_;"
"SELECT `currentusers` INTO cur FROM `" + prefix + "server` WHERE name=server_;"
"SELECT `maxusers` INTO max FROM `" + prefix + "maxusers` WHERE name=server_;"
"IF found_rows() AND cur <= max THEN "
"UPDATE `" + prefix + "maxusers` SET lastused=now() WHERE name=server_;"
"ELSE "
"INSERT INTO `" + prefix + "maxusers` (name, maxusers, maxtime, lastused) "
"VALUES ( server_, cur, now(), now() ) "
"ON DUPLICATE KEY UPDATE "
"name=VALUES(name), maxusers=VALUES(maxusers),"
"maxtime=VALUES(maxtime), lastused=VALUES(lastused);"
"END IF;"
+ geoquery +
"END";
this->RunQuery(query);
if (this->HasProcedure(prefix + "ServerQuit"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "ServerQuit"));
query = "CREATE PROCEDURE " + prefix + "ServerQuit(sname_ varchar(255)) "
"BEGIN "
/* 1.
* remove all users on the splitting server from the ison table
*/
"DELETE i FROM `" + prefix + "ison` AS i "
"INNER JOIN `" + prefix + "server` AS s "
"INNER JOIN `" + prefix + "user` AS u "
"WHERE i.nickid = u.nickid "
"AND u.servid = s.id "
"AND s.name = sname_;"
/* 2.
* remove all users on the splitting server from the user table
*/
"DELETE u FROM `" + prefix + "user` AS u "
"INNER JOIN `" + prefix + "server` AS s "
"WHERE s.id = u.servid "
"AND s.name = sname_;"
/* 3.
* on the splitting server, set usercount = 0, split_time = now(), online = 'N'
*/
"UPDATE `" + prefix + "server` SET currentusers = 0, split_time = now(), online = 'N' "
"WHERE name = sname_;"
"END;"; // end of the procedure
this->RunQuery(query);
if (this->HasProcedure(prefix + "UserQuit"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "UserQuit"));
query = "CREATE PROCEDURE `" + prefix + "UserQuit`"
"(nick_ varchar(255)) "
"BEGIN "
/* decrease usercount on the server where the user was on */
"UPDATE `" + prefix + "user` AS `u`, `" + prefix + "server` AS `s` "
"SET s.currentusers = s.currentusers - 1 "
"WHERE u.nick=nick_ AND u.servid = s.id; "
/* remove from all channels where the user was on */
"DELETE i FROM `" + prefix + "ison` AS i "
"INNER JOIN `" + prefix + "user` as u "
"WHERE u.nick = nick_ "
"AND i.nickid = u.nickid;"
/* remove the user from the user table */
"DELETE FROM `" + prefix + "user` WHERE nick = nick_; "
"END";
this->RunQuery(query);
if (this->HasProcedure(prefix + "ShutDown"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "ShutDown"));
query = "CREATE PROCEDURE `" + prefix + "ShutDown`()"
"BEGIN "
"UPDATE `" + prefix + "server` "
"SET currentusers=0, online='N', split_time=now();"
"TRUNCATE TABLE `" + prefix + "user`;"
"TRUNCATE TABLE `" + prefix + "chan`;"
"TRUNCATE TABLE `" + prefix + "ison`;"
"END";
this->RunQuery(query);
if (this->HasProcedure(prefix + "JoinUser"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "JoinUser"));
query = "CREATE PROCEDURE `"+ prefix + "JoinUser`"
"(nick_ varchar(255), channel_ varchar(255), modes_ varchar(255)) "
"BEGIN "
"DECLARE cur int;"
"DECLARE max int;"
"INSERT INTO `" + prefix + "ison` (nickid, chanid, modes) "
"SELECT u.nickid, c.chanid, modes_ "
"FROM " + prefix + "user AS u, " + prefix + "chan AS c "
"WHERE u.nick=nick_ AND c.channel=channel_;"
"SELECT count(i.chanid) INTO cur "
"FROM `" + prefix + "chan` AS c, " + prefix + "ison AS i "
"WHERE i.chanid = c.chanid AND c.channel=channel_;"
"SELECT `maxusers` INTO max FROM `" + prefix + "maxusers` WHERE name=channel_;"
"IF found_rows() AND cur <= max THEN "
"UPDATE `" + prefix + "maxusers` SET lastused=now() WHERE name=channel_;"
"ELSE "
"INSERT INTO `" + prefix + "maxusers` (name, maxusers, maxtime, lastused) "
"VALUES ( channel_, cur, now(), now() ) "
"ON DUPLICATE KEY UPDATE "
"name=VALUES(name), maxusers=VALUES(maxusers),"
"maxtime=VALUES(maxtime), lastused=VALUES(lastused);"
"END IF;"
"END";
this->RunQuery(query);
if (this->HasProcedure(prefix + "PartUser"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "PartUser"));
query = "CREATE PROCEDURE `" + prefix + "PartUser`"
"(nick_ varchar(255), channel_ varchar(255)) "
"BEGIN "
"DELETE i FROM `" + prefix + "ison` AS i "
"INNER JOIN `" + prefix + "user` AS u "
"INNER JOIN `" + prefix + "chan` AS c "
"WHERE i.nickid = u.nickid "
"AND u.nick = nick_ "
"AND i.chanid = c.chanid "
"AND c.channel = channel_;"
"END";
this->RunQuery(query);
}
-68
View File
@@ -1,68 +0,0 @@
/*
*
* (C) 2013-2025 Anope Team
* Contact us at team@anope.org
*
* Please read COPYING and README for further details.
*/
#include "irc2sql.h"
void IRC2SQL::RunQuery(const SQL::Query &q)
{
if (sql)
sql->Run(&sqlinterface, q);
}
void IRC2SQL::GetTables()
{
TableList.clear();
ProcedureList.clear();
EventList.clear();
if (!sql)
return;
SQL::Result r = this->sql->RunQuery(this->sql->GetTables(prefix));
for (int i = 0; i < r.Rows(); ++i)
{
const std::map<Anope::string, Anope::string> &map = r.Row(i);
for (std::map<Anope::string, Anope::string>::const_iterator it = map.begin(); it != map.end(); ++it)
TableList.push_back(it->second);
}
query = "SHOW PROCEDURE STATUS WHERE `Db` = Database();";
r = this->sql->RunQuery(query);
for (int i = 0; i < r.Rows(); ++i)
{
ProcedureList.push_back(r.Get(i, "Name"));
}
query = "SHOW EVENTS WHERE `Db` = Database();";
r = this->sql->RunQuery(query);
for (int i = 0; i < r.Rows(); ++i)
{
EventList.push_back(r.Get(i, "Name"));
}
}
bool IRC2SQL::HasTable(const Anope::string &table)
{
for (std::vector<Anope::string>::const_iterator it = TableList.begin(); it != TableList.end(); ++it)
if (*it == table)
return true;
return false;
}
bool IRC2SQL::HasProcedure(const Anope::string &table)
{
for (std::vector<Anope::string>::const_iterator it = ProcedureList.begin(); it != ProcedureList.end(); ++it)
if (*it == table)
return true;
return false;
}
bool IRC2SQL::HasEvent(const Anope::string &table)
{
for (std::vector<Anope::string>::const_iterator it = EventList.begin(); it != EventList.end(); ++it)
if (*it == table)
return true;
return false;
}
+1 -1
View File
@@ -161,7 +161,7 @@ public:
u->Account()->email = email;
BotInfo *NickServ = Config->GetClient("NickServ");
if (NickServ)
u->SendMessage(NickServ, _("Your email has been updated to \002%s\002"), email.c_str());
u->SendMessage(NickServ, _("Your email address has been updated to \002%s\002"), email.c_str());
Log(this->owner) << "Updated email address for " << u->nick << " (" << u->Account()->display << ") to " << email;
}
}
+3 -4
View File
@@ -187,10 +187,9 @@ public:
{
if (c->ci && !c->ci->memos.memos->empty() && c->ci->AccessFor(u).HasPriv("MEMO"))
{
if (c->ci->memos.memos->size() == 1)
u->SendMessage(MemoServ, _("There is \002%zu\002 memo on channel %s."), c->ci->memos.memos->size(), c->ci->name.c_str());
else
u->SendMessage(MemoServ, _("There are \002%zu\002 memos on channel %s."), c->ci->memos.memos->size(), c->ci->name.c_str());
auto memocount = c->ci->memos.memos->size();
u->SendMessage(MemoServ, memocount, N_("There is \002%zu\002 memo on channel %s.", "There are \002%zu\002 memos on channel %s."),
memocount, c->ci->name.c_str());
}
}
+7 -8
View File
@@ -361,7 +361,7 @@ public:
void OnDelCore(NickCore *nc) override
{
Log(NickServ, "nick") << "Deleting nickname group " << nc->display;
Log(NickServ, "nick") << "Deleting account " << nc->display;
/* Clean up this nick core from any users online */
for (std::list<User *>::iterator it = nc->users.begin(); it != nc->users.end();)
@@ -377,7 +377,7 @@ public:
void OnChangeCoreDisplay(NickCore *nc, const Anope::string &newdisplay) override
{
Log(LOG_NORMAL, "nick", NickServ) << "Changing " << nc->display << " nickname group display to " << newdisplay;
Log(LOG_NORMAL, "nick", NickServ) << "Changing " << nc->display << " account display nickname to " << newdisplay;
}
void OnNickIdentify(User *u) override
@@ -401,10 +401,9 @@ public:
if (block.Get<bool>("forceemail", "yes") && u->Account()->email.empty())
{
u->SendMessage(NickServ, _(
"You must now supply an email for your nick. "
"This email will allow you to retrieve your password in "
"case you forget it. "
"Type \002%s\032\037email\037\002 in order to set your email."
"You must now supply an email address for your nick. This email address will "
"allow you to recover your account in case you forget your password. Type "
"\002%s\032\037email\037\002 in order to set your email address."
),
NickServ->GetQueryCommand("nickserv/set/email").c_str());
}
@@ -492,7 +491,7 @@ public:
IRCD->SendLogin(u, na);
if (!Config->GetModule("nickserv").Get<bool>("nonicknameownership") && na->nc == u->Account() && !na->nc->HasExt("UNCONFIRMED"))
u->SetMode(NickServ, "REGISTERED");
Log(u, "", NickServ) << u->GetMask() << " automatically identified for group " << u->Account()->display;
Log(u, "", NickServ) << u->GetMask() << " automatically identified for account " << u->Account()->display;
}
if (!u->nick.equals_ci(oldnick) && old_na)
@@ -617,7 +616,7 @@ public:
if (expire)
{
Log(LOG_NORMAL, "nickserv/expire", NickServ) << "Expiring nickname " << na->nick << " (group: " << na->nc->display << ") (email: " << (na->nc->email.empty() ? "none" : na->nc->email) << ")";
Log(LOG_NORMAL, "nickserv/expire", NickServ) << "Expiring nickname " << na->nick << " (account: " << na->nc->display << ") (email: " << (na->nc->email.empty() ? "none" : na->nc->email) << ")";
FOREACH_MOD(OnNickExpire, (na));
delete na;
}
+90
View File
@@ -0,0 +1,90 @@
/* NickServ core functions
*
* (C) 2003-2025 Anope Team
* Contact us at team@anope.org
*
* Please read COPYING and README for further details.
*
* Based on the original code of Epona by Lara.
* Based on the original code of Services by Andy Church.
*/
#include "module.h"
class CommandNSConfirm final
: public Command
{
public:
CommandNSConfirm(Module *creator)
: Command(creator, "nickserv/confirm", 1, 3)
{
this->AllowUnregistered(true);
this->SetDesc(_("Confirm a previous action"));
this->SetSyntax(_("\037type\037 \037parameters\037"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
this->OnSyntaxError(source, "");
return;
}
bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"This command is used by several other commands as a way to confirm changes made "
"to your account. \037type\037 can be one of:"
));
auto this_name = source.command;
auto hide_privileged_commands = Config->GetBlock("options").Get<bool>("hideprivilegedcommands");
auto hide_registered_commands = Config->GetBlock("options").Get<bool>("hideregisteredcommands");
HelpWrapper help;
for (const auto &[c_name, info] : source.service->commands)
{
if (c_name.find_ci(this_name + " ") == 0)
{
if (info.hide)
continue;
ServiceReference<Command> c("Command", info.name);
if (!c)
continue;
else if (hide_registered_commands && !c->AllowUnregistered() && !source.GetAccount())
continue;
else if (hide_privileged_commands && !info.permission.empty() && !source.HasCommand(info.permission))
continue;
source.command = c_name;
c->OnServHelp(source, help);
}
}
help.SendTo(source);
source.Reply(_("Type \002%s\032\037option\037\002 for more information on a specific option."),
source.service->GetQueryCommand("generic/help", this_name).c_str());
return true;
}
};
class NSConfirm final
: public Module
{
private:
CommandNSConfirm commandnsconfirm;
public:
NSConfirm(const Anope::string &modname, const Anope::string &creator)
: Module(modname, creator, VENDOR)
, commandnsconfirm(this)
{
}
};
MODULE_INIT(NSConfirm)
+2 -2
View File
@@ -79,7 +79,7 @@ public:
FOREACH_MOD(OnNickDrop, (source, na));
Log(!is_mine ? LOG_ADMIN : LOG_COMMAND, source, this) << "to drop nickname " << na->nick << " (group: " << na->nc->display << ") (email: " << (!na->nc->email.empty() ? na->nc->email : "none") << ")";
Log(!is_mine ? LOG_ADMIN : LOG_COMMAND, source, this) << "to drop nickname " << na->nick << " (account: " << na->nc->display << ") (email: " << (!na->nc->email.empty() ? na->nc->email : "none") << ")";
delete na;
source.Reply(_("Nickname \002%s\002 has been dropped."), nick.c_str());
@@ -98,7 +98,7 @@ public:
source.Reply(" ");
if (!source.HasPriv("nickserv/drop"))
source.Reply(_("You may drop any nick within your group."));
source.Reply(_("You may drop any nick within your account."));
else
source.Reply(_("As a Services Operator, you may drop any nick."));
+141 -31
View File
@@ -68,6 +68,133 @@ namespace
}
}
struct EmailChange final
{
Anope::string code;
Anope::string email;
time_t requested = Anope::CurTime;
};
class CommandNSConfirmEmail final
: public Command
{
private:
PrimitiveExtensibleItem<EmailChange> &ns_set_email;
public:
CommandNSConfirmEmail(Module *creator, PrimitiveExtensibleItem<EmailChange> &nse)
: Command(creator, "nickserv/confirm/email", 1, 2)
, ns_set_email(nse)
{
this->SetDesc(_("Confirm a previous change of email address"));
this->SetSyntax(_("\037code\037"));
this->SetSyntax(_("@\037nickname\037"), [](auto &source) { return source.HasPriv("nickserv/confirm/email"); });
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
auto has_priv = source.HasPriv("nickserv/confirm/email");
Anope::string code;
NickAlias *na;
if (params[0] == '@')
{
if (!has_priv)
{
source.Reply(ACCESS_DENIED);
return;
}
auto nick = params[0].substr(0);
na = NickAlias::Find(nick);
if (!na)
{
source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
return;
}
}
else
{
code = params[0];
na = source.GetAccount()->na;
}
NickCore *nc = na->nc;
if (nc->HasExt("NS_SUSPENDED"))
{
source.Reply(NICK_X_SUSPENDED, na->nick.c_str());
return;
}
auto *nse = ns_set_email.Get(nc);
if (!nse)
{
source.Reply(_("There is no email address change confirmation pending for %s."),
na->nick.c_str());
return;
}
if (!has_priv)
{
if (!code.equals_cs(nse->code))
{
source.Reply(_("The email address change confirmation code you specified for %s is incorrect."),
na->nick.c_str());
return;
}
auto changeexpire = Config->GetModule(owner).Get<time_t>("changeexpire", "1d");
if (nse->requested < Anope::CurTime - changeexpire)
{
ns_set_email.Unset(nc);
source.Reply(_("The email address change request for %s has expired."),
na->nick.c_str());
return;
}
}
if (!CheckLimitReached(source, nse->email, true))
{
ns_set_email.Unset(nc);
return;
}
auto old_email = nc->email;
nc->email = nse->email;
ns_set_email.Unset(nc);
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to confirm the email address change of "
<< nc->display << " from " << old_email << " to " << nc->email;
source.Reply(_("The email address of %s has been changed from \002%s\002 to \002%s\002."),
na->nick.c_str(), old_email.c_str(), nc->email.c_str());
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
auto changeexpire = Config->GetModule(owner).Get<time_t>("changeexpire", "1d");
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Confirms an change of email address. You have %s after requesting an email "
"address change to do this before your request expires."
),
Anope::Duration(changeexpire, source.GetAccount()).c_str());
if (source.HasPriv("nickserv/confirm/email"))
{
source.Reply(" ");
source.Reply(_(
"Additionally, Services Operators with the \037nickserv/confirm/email\037 "
"permission can specify @\037nickname\037 instead of \037code\037 to force "
"confirm another user's change of email address."
));
}
return true;
}
};
class CommandNSGetEmail final
: public Command
{
@@ -75,7 +202,7 @@ public:
CommandNSGetEmail(Module *creator)
: Command(creator, "nickserv/getemail", 1, 1)
{
this->SetDesc(_("Matches and returns all users that registered using given email"));
this->SetDesc(_("Matches and returns all users that registered using given email address"));
this->SetSyntax(_("\037email\037"));
}
@@ -118,18 +245,16 @@ class CommandNSSetEmail
{
static bool SendConfirmMail(User *u, NickCore *nc, BotInfo *bi, const Anope::string &new_email)
{
Anope::string code = Anope::Random(Config->GetBlock("options").Get<size_t>("codelength", "15"));
std::pair<Anope::string, Anope::string> *n = nc->Extend<std::pair<Anope::string, Anope::string> >("ns_set_email");
n->first = new_email;
n->second = code;
auto *nse = nc->Extend<EmailChange>("ns_set_email");
nse->code = Anope::Random(Config->GetBlock("options").Get<size_t>("codelength", "15"));
nse->email = new_email;
Anope::map<Anope::string> vars = {
{ "old_email", nc->email },
{ "new_email", new_email },
{ "account", nc->display },
{ "network", Config->GetBlock("networkinfo").Get<const Anope::string>("networkname") },
{ "code", code },
{ "code", nse->code },
};
auto subject = Anope::Template(Config->GetBlock("mail").Get<const Anope::string>("emailchange_subject"), vars);
@@ -167,18 +292,18 @@ public:
if (nc->HasExt("UNCONFIRMED"))
{
source.Reply(_("You may not change the email of an unconfirmed account."));
source.Reply(_("You may not change the email address of an unconfirmed account."));
return;
}
if (param.empty() && Config->GetModule("nickserv").Get<bool>("forceemail", "yes"))
{
source.Reply(_("You cannot unset the email on this network."));
source.Reply(_("You cannot unset the email address on this network."));
return;
}
else if (Config->GetModule("nickserv").Get<bool>("secureadmins", "yes") && source.nc != nc && nc->IsServicesOper())
{
source.Reply(_("You may not change the email of other Services Operators."));
source.Reply(_("You may not change the email address of other Services Operators."));
return;
}
else if (!param.empty() && !Mail::Validate(param))
@@ -199,7 +324,7 @@ public:
{
if (SendConfirmMail(source.GetUser(), source.GetAccount(), source.service, param))
{
Log(LOG_COMMAND, source, this) << "to request changing the email of " << nc->display << " to " << param;
Log(LOG_COMMAND, source, this) << "to request changing the email address of " << nc->display << " to " << param;
source.Reply(_("A confirmation email has been sent to \002%s\002. Follow the instructions in it to change your email address."), param.c_str());
}
}
@@ -207,13 +332,13 @@ public:
{
if (!param.empty())
{
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change the email of " << nc->display << " to " << param;
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change the email address of " << nc->display << " to " << param;
nc->email = param;
source.Reply(_("Email address for \002%s\002 changed to \002%s\002."), nc->display.c_str(), param.c_str());
}
else
{
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to unset the email of " << nc->display;
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to unset the email address of " << nc->display;
nc->email.clear();
source.Reply(_("Email address for \002%s\002 unset."), nc->display.c_str());
}
@@ -266,16 +391,18 @@ class NSEmail final
: public Module
{
private:
CommandNSConfirmEmail commandnsconfirmemail;
CommandNSGetEmail commandnsgetemail;
CommandNSSetEmail commandnssetemail;
CommandNSSASetEmail commandnssasetemail;
/* email, passcode */
PrimitiveExtensibleItem<std::pair<Anope::string, Anope::string>> ns_set_email;
PrimitiveExtensibleItem<EmailChange> ns_set_email;
public:
NSEmail(const Anope::string &modname, const Anope::string &creator)
: Module(modname, creator, VENDOR)
, commandnsconfirmemail(this, ns_set_email)
, commandnsgetemail(this)
, commandnssetemail(this)
, commandnssasetemail(this)
@@ -292,23 +419,6 @@ public:
EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector<Anope::string> &params) override
{
NickCore *uac = source.nc;
if (command->name == "nickserv/confirm" && !params.empty() && uac)
{
std::pair<Anope::string, Anope::string> *n = ns_set_email.Get(uac);
if (n)
{
if (params[0] == n->second)
{
uac->email = n->first;
Log(LOG_COMMAND, source, command) << "to confirm their email address change to " << uac->email;
source.Reply(_("Your email address has been changed to \002%s\002."), uac->email.c_str());
ns_set_email.Unset(uac);
return EVENT_STOP;
}
}
}
if (!source.IsOper() && command->name == "nickserv/register")
{
if (CheckLimitReached(source, params.size() > 1 ? params[1] : "", false))
+29 -31
View File
@@ -70,8 +70,8 @@ public:
FOREACH_MOD(OnNickGroup, (u, target));
}
Log(LOG_COMMAND, source, cmd) << "to make " << nick << " join group of " << target->nick << " (" << target->nc->display << ") (email: " << (!target->nc->email.empty() ? target->nc->email : "none") << ")";
source.Reply(_("You are now in the group of \002%s\002."), target->nick.c_str());
Log(LOG_COMMAND, source, cmd) << "to make " << nick << " join account of of " << target->nick << " (" << target->nc->display << ") (email: " << (!target->nc->email.empty() ? target->nc->email : "none") << ")";
source.Reply(_("Your nickname now belongs to the account \002%s\002."), target->nick.c_str());
if (u)
u->lastnickreg = Anope::CurTime;
@@ -99,7 +99,7 @@ class CommandNSGroup final
public:
CommandNSGroup(Module *creator) : Command(creator, "nickserv/group", 0, 2)
{
this->SetDesc(_("Join a group"));
this->SetDesc(_("Add a nick to an account"));
this->SetSyntax(_("\037[target]\037 \037[password]\037"));
this->AllowUnregistered(true);
}
@@ -162,17 +162,17 @@ public:
}
else if (target->nc->HasExt("NS_SUSPENDED"))
{
Log(LOG_COMMAND, source, this) << "and tried to group to SUSPENDED nick " << target->nick;
Log(LOG_COMMAND, source, this) << "and tried to group to SUSPENDED account " << target->nick;
source.Reply(NICK_X_SUSPENDED, target->nick.c_str());
}
else if (na && Config->GetModule(this->owner).Get<bool>("nogroupchange"))
source.Reply(_("Your nick is already registered."));
else if (na && *target->nc == *na->nc)
source.Reply(_("You are already a member of the group of \002%s\002."), target->nick.c_str());
source.Reply(_("You are already a member of the account of \002%s\002."), target->nick.c_str());
else if (na && na->nc != source.GetAccount())
source.Reply(NICK_IDENTIFY_REQUIRED);
else if (maxaliases && target->nc->aliases->size() >= maxaliases && !target->nc->IsServicesOper())
source.Reply(_("There are too many nicks in your group."));
source.Reply(_("There are too many nicks in your account."));
else if (nickserv && nickserv->IsGuestNick(source.GetNick()))
source.Reply(NICK_CANNOT_BE_REGISTERED, source.GetNick().c_str());
else
@@ -209,16 +209,16 @@ public:
source.Reply(" ");
source.Reply(_(
"This command makes your nickname join the \037target\037 nickname's "
"group. \037password\037 is the password of the target nickname. "
"account. \037password\037 is the password of the target nickname. "
"\n\n"
"Joining a group will allow you to share your configuration, "
"Joining an account will allow you to share your configuration, "
"memos, and channel privileges with all the nicknames in the "
"group, and much more!"
"account, and much more!"
"\n\n"
"A group exists as long as it is useful. This means that even "
"if a nick of the group is dropped, you won't lose the "
"An account exists as long as it is useful. This means that even "
"if a nick of the account is dropped, you won't lose the "
"shared things described above, as long as there is at "
"least one nick remaining in the group."
"least one nick remaining in the account."
"\n\n"
"You may be able to use this command even if you have not registered "
"your nick yet. If your nick is already registered, you'll "
@@ -227,13 +227,13 @@ public:
"It is recommended to use this command with a non-registered "
"nick because it will be registered automatically when "
"using this command. You may use it with a registered nick (to "
"change your group) only if your network administrators allowed "
"change your account) only if your network administrators allowed "
"it."
"\n\n"
"You can only be in one group at a time. Group merging is "
"You can only be in one account at a time. Group merging is "
"not possible. "
"\n\n"
"\037Note\037: all the nicknames of a group have the same password."
"\037Note\037: all the nicknames of an account have the same password."
));
return true;
}
@@ -245,7 +245,7 @@ class CommandNSUngroup final
public:
CommandNSUngroup(Module *creator) : Command(creator, "nickserv/ungroup", 0, 1)
{
this->SetDesc(_("Remove a nick from a group"));
this->SetDesc(_("Remove a nick from an account"));
this->SetSyntax(_("[\037nick\037]"));
}
@@ -255,11 +255,11 @@ public:
NickAlias *na = NickAlias::Find(!nick.empty() ? nick : source.GetNick());
if (source.GetAccount()->aliases->size() == 1)
source.Reply(_("Your nick is not grouped to anything, you can't ungroup it."));
source.Reply(_("Your nick does not belong to an account, you can't ungroup it."));
else if (!na)
source.Reply(NICK_X_NOT_REGISTERED, !nick.empty() ? nick.c_str() : source.GetNick().c_str());
else if (na->nc != source.GetAccount())
source.Reply(_("Nick %s is not in your group."), na->nick.c_str());
source.Reply(_("Nick %s does not belong to your account."), na->nick.c_str());
else
{
NickCore *oldcore = na->nc;
@@ -268,8 +268,8 @@ public:
if (it != oldcore->aliases->end())
oldcore->aliases->erase(it);
if (na->nick.equals_ci(oldcore->display))
oldcore->SetDisplay(oldcore->aliases->front());
if (oldcore->na == na)
oldcore->SetDisplay(nullptr);
auto *nc = new NickCore(na->nick);
na->nc = nc;
@@ -280,7 +280,7 @@ public:
nc->email = oldcore->email;
nc->language = oldcore->language;
Log(LOG_COMMAND, source, this) << "to make " << na->nick << " leave group of " << oldcore->display << " (email: " << (!oldcore->email.empty() ? oldcore->email : "none") << ")";
Log(LOG_COMMAND, source, this) << "to make " << na->nick << " leave account of " << oldcore->display << " (email: " << (!oldcore->email.empty() ? oldcore->email : "none") << ")";
source.Reply(_("Nick %s has been ungrouped from %s."), na->nick.c_str(), oldcore->display.c_str());
User *user = User::Find(na->nick, true);
@@ -296,10 +296,10 @@ public:
source.Reply(" ");
source.Reply(_(
"This command ungroups your nick, or if given, the specified nick, "
"from the group it is in. The ungrouped nick keeps its registration "
"from the account it is in. The ungrouped nick keeps its registration "
"time, password, email, greet, language, and url. Everything else "
"is reset. You may not ungroup yourself if there is only one nick in "
"your group."
"your account."
));
return true;
}
@@ -311,7 +311,7 @@ class CommandNSGList final
public:
CommandNSGList(Module *creator) : Command(creator, "nickserv/glist", 0, 1)
{
this->SetDesc(_("Lists all nicknames in your group"));
this->SetDesc(_("Lists all nicknames in your account"));
this->SetSyntax(_("[\037nickname\037]"), [](auto &source) { return source.IsServicesOper(); });
}
@@ -361,14 +361,14 @@ public:
list.AddEntry(entry);
}
source.Reply(!nick.empty() ? _("List of nicknames in the group of \002%s\002:") : _("List of nicknames in your group:"), nc->display.c_str());
source.Reply(!nick.empty() ? _("List of nicknames belonging to \002%s\002:") : _("List of nicknames belonging to your account:"), nc->display.c_str());
std::vector<Anope::string> replies;
list.Process(replies);
for (const auto &reply : replies)
source.Reply(reply);
source.Reply(nc->aliases->size(), N_("%zu nickname in the group.", "%zu nicknames in the group."), nc->aliases->size());
source.Reply(nc->aliases->size(), N_("%zu nickname in the account.", "%zu nicknames in the account."), nc->aliases->size());
}
bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
@@ -377,18 +377,16 @@ public:
if (source.IsServicesOper())
{
source.Reply(_(
"Without a parameter, lists all nicknames that are in "
"your group."
"Without a parameter, lists all nicknames that belong to your account."
"\n\n"
"With a parameter, lists all nicknames that are in the "
"group of the given nick."
"With a parameter, lists all nicknames that belong to the account of the given nick."
"\n\n"
"Specifying a nick is limited to \002Services Operators\002."
));
}
else
{
source.Reply(_("Lists all nicks in your group."));
source.Reply(_("Lists all nicknames that belong to your account."));
}
return true;
+1 -1
View File
@@ -55,7 +55,7 @@ public:
if (na->HasExt("HELD"))
{
nickserv->Release(na);
source.Reply(_("Service's hold on \002%s\002 has been released."), na->nick.c_str());
source.Reply(_("Services' hold on \002%s\002 has been released."), na->nick.c_str());
}
else if (!u)
{
+163 -136
View File
@@ -11,119 +11,24 @@
#include "module.h"
namespace
{
Anope::string *GetCode(NickCore *nc)
{
auto *code = nc->GetExt<Anope::string>("passcode");
if (!code)
{
code = nc->Extend<Anope::string>("passcode");
*code = Anope::Random(Config->GetBlock("options").Get<size_t>("codelength", "15"));
}
return code;
}
}
static bool SendRegmail(User *u, const NickAlias *na, BotInfo *bi);
static ServiceReference<NickServService> nickserv("NickServService", "NickServ");
class CommandNSConfirm final
: public Command
{
public:
CommandNSConfirm(Module *creator) : Command(creator, "nickserv/confirm", 1, 2)
{
this->SetDesc(_("Confirm a passcode"));
this->SetSyntax(_("\037passcode\037"));
this->AllowUnregistered(true);
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
Anope::string *code = source.nc ? source.nc->GetExt<Anope::string>("passcode") : NULL;
bool confirming_other = !code || *code != params[0];
if (source.nc && (!source.nc->HasExt("UNCONFIRMED") || (source.IsOper() && confirming_other)) && source.HasPriv("nickserv/confirm"))
{
const Anope::string &nick = params[0];
NickAlias *na = NickAlias::Find(nick);
if (na == NULL)
source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
else if (!na->nc->HasExt("UNCONFIRMED"))
source.Reply(_("Nick \002%s\002 is already confirmed."), na->nick.c_str());
else
{
na->nc->Shrink<bool>("UNCONFIRMED");
FOREACH_MOD(OnNickConfirm, (source.GetUser(), na->nc));
Log(LOG_ADMIN, source, this) << "to confirm nick " << na->nick << " (" << na->nc->display << ")";
source.Reply(_("Nick \002%s\002 has been confirmed."), na->nick.c_str());
/* Login the users online already */
for (std::list<User *>::iterator it = na->nc->users.begin(); it != na->nc->users.end(); ++it)
{
User *u = *it;
IRCD->SendLogin(u, na);
NickAlias *u_na = NickAlias::Find(u->nick);
/* Set +r if they're on a nick in the group */
if (!Config->GetModule("nickserv").Get<bool>("nonicknameownership") && u_na && *u_na->nc == *na->nc)
u->SetMode(source.service, "REGISTERED");
}
}
}
else if (source.nc)
{
const Anope::string &passcode = params[0];
if (code != NULL && *code == passcode)
{
NickCore *nc = source.nc;
nc->Shrink<Anope::string>("passcode");
Log(LOG_COMMAND, source, this) << "to confirm their email";
source.Reply(_("Your email address of \002%s\002 has been confirmed."), source.nc->email.c_str());
nc->Shrink<bool>("UNCONFIRMED");
FOREACH_MOD(OnNickConfirm, (source.GetUser(), nc));
if (source.GetUser())
{
NickAlias *na = NickAlias::Find(source.GetNick());
if (na)
{
IRCD->SendLogin(source.GetUser(), na);
if (!Config->GetModule("nickserv").Get<bool>("nonicknameownership") && na->nc == source.GetAccount() && !na->nc->HasExt("UNCONFIRMED"))
source.GetUser()->SetMode(source.service, "REGISTERED");
}
}
}
else
source.Reply(_("Invalid passcode."));
}
else
source.Reply(NICK_IDENTIFY_REQUIRED);
return;
}
bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"This command is used by several commands as a way to confirm "
"changes made to your account."
"\n\n"
"This is most commonly used to confirm your email address once "
"you register or change it."
"\n\n"
"This is also used after the RESETPASS command has been used to "
"force identify you to your nick so you may change your password."
));
if (source.HasPriv("nickserv/confirm"))
{
source.Reply(_(
"Additionally, Services Operators with the \037nickserv/confirm\037 permission can "
"replace \037passcode\037 with a users nick to force validate them."
));
}
return true;
}
void OnSyntaxError(CommandSource &source, const Anope::string &subcommand) override
{
source.Reply(NICK_CONFIRM_INVALID);
}
};
class CommandNSRegister final
: public Command
{
@@ -245,7 +150,7 @@ public:
Log(LOG_COMMAND, source, this) << "to register " << na->nick << " (email: " << (!na->nc->email.empty() ? na->nc->email : "none") << ")";
source.Reply(_("Nickname \002%s\002 registered."), u_nick.c_str());
if (nsregister.equals_ci("admin"))
if (nsregister.equals_ci("admin") || nsregister.equals_ci("code"))
{
nc->Extend<bool>("UNCONFIRMED");
}
@@ -270,8 +175,14 @@ public:
{
if (nsregister.equals_ci("admin"))
source.Reply(_("All new accounts must be validated by an administrator. Please wait for your registration to be confirmed."));
else if (nsregister.equals_ci("code"))
{
const auto *code = GetCode(na->nc);
source.Reply(_("Your account is not confirmed. To confirm it, type \002%s\002."),
source.service->GetQueryCommand("nickserv/confirm/register", *code).c_str());
}
else if (nsregister.equals_ci("mail"))
source.Reply(_("Your email address is not confirmed. To confirm it, follow the instructions that were emailed to you."));
source.Reply(_("Your account is not confirmed. To confirm it, follow the instructions that were emailed to you."));
}
}
}
@@ -308,19 +219,130 @@ public:
{
source.Reply(" ");
source.Reply(_(
"The \037email\037 parameter is optional and will set the email "
"for your nick immediately. You may also wish to \002SET\032HIDE\002 it "
"after registering if it isn't the default setting already."
"The \037email\037 parameter is optional and will set the email address for your "
"nick immediately. You may also wish to \002SET\032HIDE\002 it after registering "
"if it isn't the default setting already."
));
}
if (!Config->GetModule("nickserv").Get<bool>("nonicknameownership"))
{
source.Reply(" ");
source.Reply(_(
"You can associate multiple nicknames with your account. All nicknames will "
"share the same configuration, set of memos, and channel privileges."
));
}
return true;
}
};
class CommandNSConfirmRegister final
: public Command
{
public:
CommandNSConfirmRegister(Module *creator)
: Command(creator, "nickserv/confirm/register", 1, 2)
{
this->SetDesc(_("Confirm a previous account registration"));
this->SetSyntax(_("\037code\037"));
this->SetSyntax(_("@\037nickname\037"), [](auto &source) { return source.HasPriv("nickserv/confirm/register"); });
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
auto has_priv = source.HasPriv("nickserv/confirm/register");
Anope::string code;
NickAlias *na;
if (params[0] == '@')
{
if (!has_priv)
{
source.Reply(ACCESS_DENIED);
return;
}
auto nick = params[0].substr(0);
na = NickAlias::Find(nick);
if (!na)
{
source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
return;
}
}
else
{
code = params[0];
na = source.GetAccount()->na;
}
NickCore *nc = na->nc;
if (nc->HasExt("NS_SUSPENDED"))
{
source.Reply(NICK_X_SUSPENDED, na->nick.c_str());
return;
}
auto *passcode = nc->GetExt<Anope::string>("passcode");
if (!passcode)
{
source.Reply(_("There is no registration confirmation pending for %s."),
na->nick.c_str());
return;
}
if (has_priv || !code.equals_cs(*passcode))
{
source.Reply(_("The registration confirmation code you specified for %s is incorrect."),
na->nick.c_str());
return;
}
na->nc->Shrink<bool>("UNCONFIRMED");
FOREACH_MOD(OnNickConfirm, (source.GetUser(), nc));
auto nonicknameownership = Config->GetModule("nickserv").Get<bool>("nonicknameownership");
for (auto *u : nc->users)
{
IRCD->SendLogin(u, na);
if (!nonicknameownership)
continue;
const auto &aliases = *nc->aliases;
auto it = std::find_if(aliases.begin(), aliases.end(), [&u](const auto *na) {
return na->nick.equals_ci(u->nick);
});
if (it != aliases.end())
u->SetMode(source.service, "REGISTERED"); // nick is in the group
}
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to confirm the registration of " << nc->display;
source.Reply(_("Nick \002%s\002 has been confirmed."), na->nick.c_str());
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
auto unconfirmedexpire = Config->GetModule(owner).Get<time_t>("unconfirmedexpire", "1d");
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"This command also creates a new group for your nickname, "
"that will allow you to register other nicks later sharing "
"the same configuration, the same set of memos and the "
"same channel privileges."
));
"Confirms an account registration. You have %s after registration to do this "
"before your registration expires."
),
Anope::Duration(unconfirmedexpire, source.GetAccount()).c_str());
if (source.HasPriv("nickserv/confirm/register"))
{
source.Reply(" ");
source.Reply(_(
"Additionally, Services Operators with the \037nickserv/confirm/register\037 "
"permission can specify @\037nickname\037 instead of \037code\037 to force "
"confirm another user's account registration."
));
}
return true;
}
};
@@ -355,11 +377,11 @@ public:
else if (SendRegmail(source.GetUser(), na, source.service))
{
na->nc->lastmail = Anope::CurTime;
source.Reply(_("Your passcode has been re-sent to %s."), na->nc->email.c_str());
Log(LOG_COMMAND, source, this) << "to resend registration verification code";
source.Reply(_("Your confirmation code has been re-sent to %s."), na->nc->email.c_str());
Log(LOG_COMMAND, source, this) << "to resend registration confirmation code";
}
else
Log(this->owner) << "Unable to resend registration verification code for " << source.GetNick();
Log(this->owner) << "Unable to resend registration confirmation code for " << source.GetNick();
}
return;
@@ -387,16 +409,20 @@ class NSRegister final
: public Module
{
CommandNSRegister commandnsregister;
CommandNSConfirm commandnsconfirm;
CommandNSResend commandnsrsend;
CommandNSConfirmRegister commandnsconfirmregister;
CommandNSResend commandnsresend;
SerializableExtensibleItem<bool> unconfirmed;
SerializableExtensibleItem<Anope::string> passcode;
public:
NSRegister(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
commandnsregister(this), commandnsconfirm(this), commandnsrsend(this), unconfirmed(this, "UNCONFIRMED"),
passcode(this, "passcode")
NSRegister(const Anope::string &modname, const Anope::string &creator)
: Module(modname, creator, VENDOR)
, commandnsregister(this)
, commandnsconfirmregister(this)
, commandnsresend(this)
, unconfirmed(this, "UNCONFIRMED")
, passcode(this, "passcode")
{
if (Config->GetModule(this).Get<const Anope::string>("registration").equals_ci("disable"))
throw ModuleException("Module " + this->name + " will not load with registration disabled.");
@@ -410,8 +436,15 @@ public:
const Anope::string &nsregister = Config->GetModule(this).Get<const Anope::string>("registration");
if (nsregister.equals_ci("admin"))
u->SendMessage(NickServ, _("All new accounts must be validated by an administrator. Please wait for your registration to be confirmed."));
else
u->SendMessage(NickServ, _("Your email address is not confirmed. To confirm it, follow the instructions that were emailed to you."));
else if (nsregister.equals_ci("code"))
{
const auto *code = GetCode(u->Account());
u->SendMessage(NickServ, _("Your account is not confirmed. To confirm it, type \002%s\002."),
NickServ->GetQueryCommand("nickserv/confirm/register", *code).c_str());
}
else if (nsregister.equals_ci("mail"))
u->SendMessage(NickServ, _("Your account is not confirmed. To confirm it, follow the instructions that were emailed to you."));
const NickAlias *this_na = u->AccountNick();
time_t registered = Anope::CurTime - this_na->registered;
time_t unconfirmed_expire = Config->GetModule(this).Get<time_t>("unconfirmedexpire", "1d");
@@ -434,13 +467,7 @@ public:
static bool SendRegmail(User *u, const NickAlias *na, BotInfo *bi)
{
NickCore *nc = na->nc;
Anope::string *code = na->nc->GetExt<Anope::string>("passcode");
if (code == NULL)
{
code = na->nc->Extend<Anope::string>("passcode");
*code = Anope::Random(Config->GetBlock("options").Get<size_t>("codelength", "15"));
}
auto *code = GetCode(na->nc);
Anope::map<Anope::string> vars = {
{ "nick", na->nick },
+109 -58
View File
@@ -38,7 +38,7 @@ public:
{
if (SendResetEmail(source.GetUser(), na, source.service))
{
Log(LOG_COMMAND, source, this) << "for " << na->nick << " (group: " << na->nc->display << ")";
Log(LOG_COMMAND, source, this) << "for " << na->nick << " (account: " << na->nc->display << ")";
source.Reply(_("Password reset email for \002%s\002 has been sent."), na->nick.c_str());
}
}
@@ -51,9 +51,9 @@ public:
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Sends a passcode to the nickname with instructions on how to "
"reset their password. Email must be the email address associated "
"to the nickname."
"Sends a confirmation code to the nickname's email address with instructions on "
"how to reset their password. \037email\037 must be the email address associated "
"with the account."
));
return true;
}
@@ -65,71 +65,122 @@ struct ResetInfo final
time_t time;
};
class CommandNSConfirmResetPass final
: public Command
{
private:
PrimitiveExtensibleItem<ResetInfo> &reset;
public:
CommandNSConfirmResetPass(Module *creator, PrimitiveExtensibleItem<ResetInfo> &r)
: Command(creator, "nickserv/confirm/resetpass", 1, 2)
, reset(r)
{
this->AllowUnregistered(true);
this->SetDesc(_("Confirm a previous password reset"));
this->SetSyntax(_("[\037nickname\037] \037code\037"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
Anope::string code, nick;
if (params.size() > 1)
{
nick = params[0];
code = params[1];
}
else
{
code = params[0];
nick = source.GetNick();
}
auto *na = NickAlias::Find(nick);
if (!na)
{
source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
return;
}
NickCore *nc = na->nc;
if (nc->HasExt("NS_SUSPENDED"))
{
source.Reply(NICK_X_SUSPENDED, na->nick.c_str());
return;
}
auto *ri = reset.Get(nc);
if (!ri)
{
source.Reply(_("There is no password reset confirmation pending for %s."),
na->nick.c_str());
return;
}
if (!code.equals_cs(ri->code))
{
source.Reply(_("The password reset code you specified for %s is incorrect."),
na->nick.c_str());
return;
}
auto resetexpire = Config->GetModule(owner).Get<time_t>("resetexpire", "1d");
if (ri->time < Anope::CurTime - resetexpire)
{
reset.Unset(nc);
source.Reply(_("The password reset request for %s has expired."),
na->nick.c_str());
return;
}
reset.Unset(nc);
if (Config->GetModule("nickserv").Get<const Anope::string>("registration").equals_ci("mail"))
nc->Shrink<bool>("UNCONFIRMED");
if (source.GetUser())
source.GetUser()->Identify(na);
Log(LOG_COMMAND, source, this) << "to reset their password and forcibly identify as " << na->nick;
source.Reply(_("You are now identified as %s. Change your password now using %s."),
na->nick.c_str(), source.service->GetQueryCommand("nickserv/set/password").c_str());
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
auto resetexpire = Config->GetModule(owner).Get<time_t>("resetexpire", "1d");
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Confirms a password reset and identifies you to the specified account. You have "
"%s after requesting a reset to do this before your request expires. Once you "
"have done this you can set the password using %s."
),
Anope::Duration(resetexpire, source.GetAccount()).c_str(),
source.service->GetQueryCommand("nickserv/set/password").c_str()
);
return true;
}
};
class NSResetPass final
: public Module
{
private:
CommandNSConfirmResetPass commandnsconfirmpassword;
CommandNSResetPass commandnsresetpass;
PrimitiveExtensibleItem<ResetInfo> reset;
public:
NSResetPass(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
commandnsresetpass(this), reset(this, "reset")
NSResetPass(const Anope::string &modname, const Anope::string &creator)
: Module(modname, creator, VENDOR)
, commandnsconfirmpassword(this, reset)
, commandnsresetpass(this)
, reset(this, "reset")
{
if (!Config->GetBlock("mail").Get<bool>("usemail"))
throw ModuleException("Not using mail.");
}
EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector<Anope::string> &params) override
{
if (command->name == "nickserv/confirm" && params.size() > 1)
{
if (Anope::ReadOnly)
{
source.Reply(READ_ONLY_MODE);
return EVENT_STOP;
}
NickAlias *na = NickAlias::Find(params[0]);
ResetInfo *ri = na ? reset.Get(na->nc) : NULL;
if (na && ri)
{
NickCore *nc = na->nc;
if (nc->HasExt("NS_SUSPENDED"))
{
source.Reply(NICK_X_SUSPENDED, nc->display.c_str());
return EVENT_STOP;
}
const Anope::string &passcode = params[1];
if (ri->time < Anope::CurTime - 3600)
{
reset.Unset(nc);
source.Reply(_("Your password reset request has expired."));
}
else if (passcode.equals_cs(ri->code))
{
reset.Unset(nc);
nc->Shrink<bool>("UNCONFIRMED");
Log(LOG_COMMAND, source, &commandnsresetpass) << "to confirm RESETPASS and forcefully identify as " << na->nick;
if (source.GetUser())
{
source.GetUser()->Identify(na);
}
source.Reply(_("You are now identified for your nick. Change your password now."));
}
else
return EVENT_CONTINUE;
return EVENT_STOP;
}
}
return EVENT_CONTINUE;
}
};
static bool SendResetEmail(User *u, const NickAlias *na, BotInfo *bi)
+6 -6
View File
@@ -439,7 +439,7 @@ class CommandNSSetDisplay
public:
CommandNSSetDisplay(Module *creator, const Anope::string &sname = "nickserv/set/display", size_t min = 1) : Command(creator, sname, min, min + 1)
{
this->SetDesc(_("Set the display of your group in services"));
this->SetDesc(_("Set the display nickname for your account"));
this->SetSyntax(_("\037new-display\037"));
}
@@ -465,7 +465,7 @@ public:
}
else if (!na || *na->nc != *user_na->nc)
{
source.Reply(_("The new display MUST be a nickname of the nickname group %s."), user_na->nc->display.c_str());
source.Reply(_("The new display nickname must belong to the %s account."), user_na->nc->display.c_str());
return;
}
@@ -498,8 +498,8 @@ public:
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Changes the display used to refer to your nickname group in "
"services. The new display MUST be a nick of your group."
"Changes the display nickname used to refer to your account. The new display "
"nickname must already be associated with your account."
));
return true;
}
@@ -525,8 +525,8 @@ public:
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Changes the display used to refer to the nickname group in "
"services. The new display MUST be a nick of the group."
"Changes the display nickname used to refer to the account. The new display"
"nickname must already be associated with the account."
));
return true;
}
+1
View File
@@ -167,6 +167,7 @@ public:
if (descriptions.count(source.command))
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply("%s", Language::Translate(source.nc, descriptions[source.command].c_str()));
return true;
}
+4 -5
View File
@@ -100,10 +100,9 @@ public:
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Turns automatic protection for your account on or off. With "
"protection on if another user tries to use a nickname from "
"your group they will be given some time to change their nick "
"after which %s will forcibly change their nick."
"Turns automatic protection for your account on or off. With protection on if "
"another user tries to use a nickname from your account they will be given some "
"time to change their nick after which %s will forcibly change their nick."
),
source.service->nick.c_str());
return true;
@@ -133,7 +132,7 @@ public:
source.Reply(_(
"Turns automatic protection for the nick on or off. With "
"protection on if a user tries to use a nickname from the "
"nick's group they will be given some time to change their "
"nick's account they will be given some time to change their "
"nick after which %s will forcibly change their nick."
),
source.service->nick.c_str());
+42 -45
View File
@@ -12,6 +12,40 @@
#include "module.h"
#include "modules/operserv/forbid.h"
namespace
{
Anope::string TypeToString(ForbidType ft)
{
switch (ft)
{
case FT_NICK:
return "NICK";
case FT_CHAN:
return "CHAN";
case FT_EMAIL:
return "EMAIL";
case FT_REGISTER:
return "REGISTER";
default:
return "UNKNOWN"; // Should never happen.
}
}
ForbidType StringToType(const Anope::string &ft)
{
if (ft.equals_ci("NICK") || ft.equals_ci("1"))
return FT_NICK;
if (ft.equals_ci("CHAN") || ft.equals_ci("2"))
return FT_CHAN;
if (ft.equals_ci("EMAIL") || ft.equals_ci("3"))
return FT_EMAIL;
if (ft.equals_ci("REGISTER") || ft.equals_ci("4"))
return FT_REGISTER;
return FT_SIZE; // Should never happen.
}
}
static ServiceReference<NickServService> nickserv("NickServService", "NickServ");
struct ForbidDataImpl final
@@ -41,7 +75,7 @@ void ForbidDataTypeImpl::Serialize(Serializable *obj, Serialize::Data &data) con
data.Store("reason", fb->reason);
data.Store("created", fb->created);
data.Store("expires", fb->expires);
data.Store("type", fb->type);
data.Store("type", TypeToString(fb->type));
}
Serializable *ForbidDataTypeImpl::Unserialize(Serializable *obj, Serialize::Data &data) const
@@ -60,11 +94,11 @@ Serializable *ForbidDataTypeImpl::Unserialize(Serializable *obj, Serialize::Data
data["reason"] >> fb->reason;
data["created"] >> fb->created;
data["expires"] >> fb->expires;
unsigned int t;
Anope::string t;
data["type"] >> t;
fb->type = static_cast<ForbidType>(t);
fb->type = StringToType(t);
if (t > FT_SIZE - 1)
if (fb->type == FT_SIZE)
return NULL;
if (!obj)
@@ -81,27 +115,7 @@ class MyForbidService final
void Expire(ForbidData *fd, unsigned ft, size_t idx)
{
Anope::string typestr;
switch (ft)
{
case FT_NICK:
typestr = "nick";
break;
case FT_CHAN:
typestr = "chan";
break;
case FT_EMAIL:
typestr = "email";
break;
case FT_REGISTER:
typestr = "register";
break;
default:
typestr = "unknown";
break;
}
Log(LOG_NORMAL, "expire/forbid", Config->GetClient("OperServ")) << "Expiring forbid for " << fd->mask << " type " << typestr;
Log(LOG_NORMAL, "expire/forbid", Config->GetClient("OperServ")) << "Expiring forbid for " << fd->mask << " type " << TypeToString(static_cast<ForbidType>(ft));
this->forbids(ft).erase(this->forbids(ft).begin() + idx);
delete fd;
}
@@ -208,16 +222,7 @@ public:
const Anope::string &command = params[0];
const Anope::string &subcommand = params.size() > 1 ? params[1] : "";
ForbidType ftype = FT_SIZE;
if (subcommand.equals_ci("NICK"))
ftype = FT_NICK;
else if (subcommand.equals_ci("CHAN"))
ftype = FT_CHAN;
else if (subcommand.equals_ci("EMAIL"))
ftype = FT_EMAIL;
else if (subcommand.equals_ci("REGISTER"))
ftype = FT_REGISTER;
auto ftype = StringToType(subcommand);
if (command.equals_ci("ADD") && params.size() > 3 && ftype != FT_SIZE)
{
const Anope::string &expiry = params[2][0] == '+' ? params[2] : "";
@@ -404,16 +409,8 @@ public:
if (ftype != FT_SIZE && ftype != forbid->type)
continue;
Anope::string stype;
if (forbid->type == FT_NICK)
stype = "NICK";
else if (forbid->type == FT_CHAN)
stype = "CHAN";
else if (forbid->type == FT_EMAIL)
stype = "EMAIL";
else if (forbid->type == FT_REGISTER)
stype = "REGISTER";
else
auto stype = TypeToString(forbid->type);
if (stype == FT_SIZE)
continue;
ListFormatter::ListEntry entry;
+46 -19
View File
@@ -17,11 +17,37 @@
# pragma GCC diagnostic ignored "-Wformat-security"
#endif
/* List of messages for each news type. This simplifies message sending. */
namespace
{
Anope::string TypeToString(NewsType nt)
{
switch (nt)
{
case NEWS_LOGON:
return "LOGON";
case NEWS_RANDOM:
return "RANDOM";
case NEWS_OPER:
return "OPER";
}
return ""; // Should never happen.
}
NewsType StringToType(const Anope::string &nt)
{
if (nt.equals_ci("LOGON") || nt.equals_ci("0"))
return NEWS_LOGON;
if (nt.equals_ci("RANDOM") || nt.equals_ci("1"))
return NEWS_RANDOM;
if (nt.equals_ci("OPER") || nt.equals_ci("2"))
return NEWS_OPER;
return NEWS_LOGON; // Should never happen.
}
}
enum
{
MSG_SYNTAX,
MSG_NEWS_SHORT,
MSG_NEWS_LONG,
MSG_LIST_HEADER,
@@ -29,45 +55,46 @@ enum
MSG_ADDED,
MSG_DEL_NOT_FOUND,
MSG_DELETED,
MSG_DEL_NONE,
MSG_DELETED_ALL
MSG_DELETED_ALL,
MSG_END,
};
struct NewsMessages final
{
NewsType type;
Anope::string name;
const char *msgs[MSG_END];
};
struct NewsMessages msgarray[] = {
{NEWS_LOGON, "LOGON",
{_("LOGONNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"),
_("[\002Logon News\002] %s"),
{_("[\002Logon News\002] %s"),
_("[\002Logon News\002 - %s] %s"),
_("Logon news items:"),
_("There is no logon news."),
_("Added new logon news item."),
_("Logon news item #%s not found!"),
_("Logon news item #%d deleted."),
_("No logon news items to delete!"),
_("Logon news item #%u deleted."),
_("All logon news items deleted.")}
},
{NEWS_OPER, "OPER",
{_("OPERNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"),
_("[\002Oper News\002] %s"),
{_("[\002Oper News\002] %s"),
_("[\002Oper News\002 - %s] %s"),
_("Oper news items:"),
_("There is no oper news."),
_("Added new oper news item."),
_("Oper news item #%s not found!"),
_("Oper news item #%d deleted."),
_("No oper news items to delete!"),
_("Oper news item #%u deleted."),
_("All oper news items deleted.")}
},
{NEWS_RANDOM, "RANDOM",
{_("RANDOMNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"),
_("[\002Random News\002] %s"),
{_("[\002Random News\002] %s"),
_("[\002Random News\002 - %s] %s"),
_("Random news items:"),
_("There is no random news."),
_("Added new random news item."),
_("Random news item #%s not found!"),
_("Random news item #%d deleted."),
_("No random news items to delete!"),
_("Random news item #%u deleted."),
_("All random news items deleted.")}
}
};
@@ -83,7 +110,7 @@ struct NewsItemType final
void Serialize(Serializable *obj, Serialize::Data &data) const override
{
const auto *ni = static_cast<const NewsItem *>(obj);
data.Store("type", ni->type);
data.Store("type", TypeToString(ni->type));
data.Store("text", ni->text);
data.Store("who", ni->who);
data.Store("time", ni->time);
@@ -100,9 +127,9 @@ struct NewsItemType final
else
ni = new NewsItem();
unsigned int t;
Anope::string t;
data["type"] >> t;
ni->type = static_cast<NewsType>(t);
ni->type = StringToType(t);
data["text"] >> ni->text;
data["who"] >> ni->who;
data["time"] >> ni->time;
+1 -1
View File
@@ -182,7 +182,7 @@ private:
source.Reply(_("Registered nicknames: %zu entries, %zu buckets, longest chain is %zu"), entries, buckets, max_chain);
GetHashStats(*NickCoreList, entries, buckets, max_chain);
source.Reply(_("Registered nick groups: %zu entries, %zu buckets, longest chain is %zu"), entries, buckets, max_chain);
source.Reply(_("Registered accounts: %zu entries, %zu buckets, longest chain is %zu"), entries, buckets, max_chain);
if (session_service)
{
+1 -1
View File
@@ -88,7 +88,7 @@ public:
{
na->nc->email = email;
if (user && NickServ)
user->SendMessage(NickServ, _("Your email has been updated to \002%s\002."), email.c_str());
user->SendMessage(NickServ, _("Your email address has been updated to \002%s\002."), email.c_str());
}
req->Success(me);
+4 -3
View File
@@ -311,7 +311,7 @@ Anope::string BotInfo::GetQueryCommand(const Anope::string &command, const Anope
if (!command.empty())
{
Anope::string actual_command = "\036(MISSING)\036";
Anope::string actual_command;
for (const auto &[c_name, info] : this->commands)
{
if (info.name != command)
@@ -322,8 +322,9 @@ Anope::string BotInfo::GetQueryCommand(const Anope::string &command, const Anope
break; // Keep going to find a non-hidden alternative.
}
if (!actual_command.empty())
buf.append(" ").append(actual_command);
if (actual_command.empty())
return "\036(MISSING)\036";
buf.append(" ").append(actual_command);
}
if (!extra.empty())
+1 -1
View File
@@ -405,7 +405,7 @@ void Channel::SetMode(BotInfo *bi, ChannelMode *cm, const ModeData &data, bool e
// We build a mode data which has more than what the caller gives us.
ModeData mdata;
mdata.set_at = data.set_at ? data.set_at : Anope::CurTime;
mdata.set_by = data.set_by.empty() && bi ? bi->nick : data.set_by;
mdata.set_by = data.set_by.empty() ? (bi ? bi->nick : Me->GetName()) : data.set_by;
mdata.value = data.value;
/* Don't set modes already set */
+7 -3
View File
@@ -215,7 +215,7 @@ void Command::OnSyntaxError(CommandSource &source, const Anope::string &subcomma
// The help command may not be called HELP.
return cmd.second.name == "generic/help";
});
if (it == source.service->commands.end())
if (it != source.service->commands.end())
source.Reply(MORE_INFO, source.service->GetQueryCommand("generic/help", source.command).c_str());
}
@@ -229,12 +229,16 @@ namespace
auto umessage = message.upper();
for (const auto &[command, info] : source.service->commands)
{
if (info.hide || command == message)
continue; // Don't suggest a hidden alias or a missing command.
if (info.hide)
continue; // Don't suggest a hidden alias.
size_t dist = Anope::Distance(umessage, command);
if (dist < distance)
{
ServiceReference<Command> cmd("Command", info.name);
if (!cmd)
continue; // Don't suggest an unloaded command.
distance = dist;
similar = command;
}
+3 -4
View File
@@ -491,8 +491,6 @@ Conf::Conf() : Block("")
&service = fantasy.Get<const Anope::string>("command"),
&permission = fantasy.Get<const Anope::string>("permission"),
&group = fantasy.Get<const Anope::string>("group");
bool hide = fantasy.Get<bool>("hide"),
prepend_channel = fantasy.Get<bool>("prepend_channel", "yes");
ValidateNotEmpty("fantasy", "name", nname);
ValidateNotEmptyOrSpaces("fantasy", "command", service);
@@ -501,8 +499,9 @@ Conf::Conf() : Block("")
c.name = service;
c.permission = permission;
c.group = group;
c.hide = hide;
c.prepend_channel = prepend_channel;
c.hide = fantasy.Get<bool>("hide");
c.prepend_channel = fantasy.Get<bool>("prepend_channel", "yes");
c.require_privilege = fantasy.Get<bool>("require_privilege", "yes");
}
for (int i = 0; i < this->CountBlock("command_group"); ++i)
+4 -4
View File
@@ -69,8 +69,8 @@ NickAlias::~NickAlias()
else
{
/* Display updating stuff */
if (this->nick.equals_ci(this->nc->display))
this->nc->SetDisplay(this->nc->aliases->front());
if (this->nc->na == this)
this->nc->SetDisplay(nullptr);
}
}
@@ -200,8 +200,8 @@ Serializable *NickAlias::Type::Unserialize(Serializable *obj, Serialize::Data &d
if (na->nc->aliases->empty())
delete na->nc;
else if (na->nick.equals_ci(na->nc->display))
na->nc->SetDisplay(na->nc->aliases->front());
if (na->nc->na == na)
na->nc->SetDisplay(nullptr);
na->nc = core;
core->aliases->push_back(na);
+11 -5
View File
@@ -190,15 +190,21 @@ Serializable *NickCore::Type::Unserialize(Serializable *obj, Serialize::Data &da
void NickCore::SetDisplay(NickAlias *na)
{
if (na->nc != this || na->nick == this->display)
// If no nick is specified then pick the oldest one.
if (!na)
{
for (auto *alias : *this->aliases)
{
if (!na || alias->registered < na->registered)
na = alias;
}
}
if (!na || na->nc != this || na->nick == this->display)
return;
FOREACH_MOD(OnChangeCoreDisplay, (this, na->nick));
/* this affects the serialized aliases */
for (auto *alias : *aliases)
alias->QueueUpdate();
/* Remove the core from the list */
NickCoreList->erase(this->display);
-3
View File
@@ -41,9 +41,6 @@ if(NOT WIN32)
install (PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/anoperc
DESTINATION ${BIN_DIR}
)
install (PROGRAMS geoipupdate.sh
DESTINATION ${BIN_DIR}
)
endif()
# On non-Windows platforms, if RUNGROUP is set, change the permissions of the tools directory
-81
View File
@@ -1,81 +0,0 @@
#!/bin/bash
# This script is a helper script for the irc2sql module.
# It downloads the configured geoip databases and inserts
# them into existing mysql tables. The tables are created
# by the irc2sql module on the first load.
# Don't forget to rename this file or your changes
# will be overwritten on the next 'make install'
############################
# Config
############################
geoip_database="country" # available options: "country" and "city"
mysql_host="localhost"
mysql_user="anope"
mysql_password="anope"
mysql_database="anope"
prefix="anope_"
die="yes"
###########################
# The GeoIP data is created by MaxMind, available from www.maxmind.com.
geoip_country_source="https://mirrors-cdn.liferay.com/geolite.maxmind.com/download/geoip/database/GeoIPCountryCSV.zip"
geoip_city_source="https://mirrors-cdn.liferay.com/geolite.maxmind.com/download/geoip/database/GeoLiteCity-latest.tar.xz"
geoip_region_source="https://www.maxmind.com/download/geoip/misc/region_codes.csv"
###########################
LOGIN="--host=$mysql_host --user=$mysql_user --password=$mysql_password $mysql_database"
PARAMS="--delete --local --fields-terminated-by=, --fields-enclosed-by=\" --lines-terminated-by=\n $LOGIN"
download() {
local url=$1
local desc=$2
echo -n " $desc "
wget --progress=dot $url 2>&1 | grep --line-buffered "%" | sed -u -e "s,\.,,g" | awk '{printf("\b\b\b\b%4s", $2)}'
echo -ne " Done\n"
}
if test $die = "yes"; then
echo "You have to edit and configure this script first."
exit
fi
if test $geoip_database = "country"; then
echo "Downloading..."
download "$geoip_country_source" "Country Database:"
echo "Unpacking..."
unzip -jo GeoIPCountryCSV.zip
rm GeoIPCountryCSV.zip
echo "Converting to UTF-8..."
iconv -f ISO-8859-1 -t UTF-8 GeoIPCountryWhois.csv -o $prefix"geoip_country.csv"
rm GeoIPCountryWhois.csv
echo "Importing..."
mysqlimport --columns=@x,@x,start,end,countrycode,countryname $PARAMS $prefix"geoip_country.csv"
rm $prefix"geoip_country.csv" $prefix"geoip_country6.csv"
echo "Done..."
elif test $geoip_database = "city"; then
echo "Downloading..."
download "$geoip_city_source" "City Database:"
download "$geoip_region_source" "Region Database:"
echo "Unpacking..."
tar -xf GeoLiteCity-latest.tar.xz --strip-components 1
rm GeoLiteCity-latest.tar.xz
echo "Converting to utf-8..."
iconv -f ISO-8859-1 -t UTF-8 GeoLiteCity-Blocks.csv -o $prefix"geoip_city_blocks.csv"
iconv -f ISO-8859-1 -t UTF-8 GeoLiteCity-Location.csv -o $prefix"geoip_city_location.csv"
iconv -f ISO-8859-1 -t UTF-8 region_codes.csv -o $prefix"geoip_city_region.csv"
rm GeoLiteCity-Blocks.csv GeoLiteCity-Location.csv region_codes.csv
echo "Importing..."
mysqlimport --columns=start,end,locID --ignore-lines=2 $PARAMS $prefix"geoip_city_blocks.csv"
mysqlimport --columns=locID,country,region,city,@x,latitude,longitude,@x,areaCode --ignore-lines=2 $PARAMS $prefix"geoip_city_location.csv"
mysqlimport --columns=country,region,regionname $PARAMS $prefix"geoip_city_region.csv"
rm $prefix"geoip_city_blocks.csv" $prefix"geoip_city_location.csv" $prefix"geoip_city_region.csv"
echo "Done..."
fi
+1 -1
View File
@@ -2,5 +2,5 @@
VERSION_MAJOR=2
VERSION_MINOR=1
VERSION_PATCH=15
VERSION_PATCH=16
VERSION_EXTRA=""