Skip to content

Sample Configuration#

armour.conf.sample#

The below is the current version of armour.conf.sample

``` tcl linenums="1"

------------------------------------------------------------------------------------------------#

_#

/ \ _ __ _ __ ___ ___ _ _ _ __#

/ _ \ | '| '_ ` _ \ / _ | | | | '|#

/ ___ | | | | | | | | () | || | |#

// __| || || ||___/ _,|_|#

#

#

Abuse and channel management script for eggdrop bots on IRC networks.#

#

https://armour.bot#

#

Armour automatic onjoin scanning be be placed in one of three modes:#

ON : Automatic scans upon client channel /join#

SECURE : Restricted channel modes (e.g., +Dm) with actions post-scan#

OFF : Manual scans only#

#

------------------------------------------------------------------------------------------------#

SUPPORTING#

------------------------------------------------------------------------------------------------#

#

whitelists#

blacklists#

dnsbl scans : dnswl exemptions and remote 'bounce' bot supported#

port scans : remote 'bounce' bot supported#

onchan scans : remote 'bounce' bot supported#

#

------------------------------------------------------------------------------------------------#

WHITELIST & BLACKLIST TYPES:#

------------------------------------------------------------------------------------------------#

#

user : authenticated username#

host : hostmask, hostname, IP and CIDR notation accepted#

regex : nick!user@host/rname#

text : channel text matches#

country : geo location ip lookup#

asn : autonomous system number lookup#

chan : onchan #channel#

rname : realname#

#

------------------------------------------------------------------------------------------------#

WHITELIST ACTIONS:#

------------------------------------------------------------------------------------------------#

#

A: Accept User (exempt scans) -- default#

O: Op User#

V: Voice User#

#

------------------------------------------------------------------------------------------------#

BLACKLIST ACTIONS#

------------------------------------------------------------------------------------------------#

#

B: KickBan User#

#

------------------------------------------------------------------------------------------------#

#

Code by : Empus @ Undernet - empus@undernet.org#

Last modified : 2024-08-31#

#

Please contact me should you need any assistence, but I hope most config options are explanatory.#

#

Documentation can be found at: https://armour.bot#

#

Contact me at #armour on Undernet#

#

------------------------------------------------------------------------------------------------#

namespace eval arm {

------------------------------------------------------------------------------------------------#

------------------------------------------------------------------------------------------------#

GENERAL SETTINGS#

------------------------------------------------------------------------------------------------#

-- bot name variable#

-- used to automate db filename and assist script updates#

-- ensure this *.conf file is named as BOTNAME.conf#

-- if multiple Armour bots run from the same eggdrop directory, each MUST have a different value#

set cfg(botname) "EDITME"

-- alert users if they don't have a password set? (0|1) - [0]#

set cfg(alert:nopass) 1

-- debug type? (putlog|putloglev) - [putloglev]#

putlog will log everything to partyline (very verbose!)#

putloglev allows debug levels to be used by enabling each level (1-5) with .console +N#

.console +123#

... etc#

set cfg(debug:type) "putloglev"

-- file logging level (0-4) - [0]#

-- data logged to ./armour/logs/.log#

-- levels:#

0: most important information#

1: gentle verbosity#

2: extra information#

3: useful for debugging#

4: extremely verbose#

set cfg(log:level) 1

------------------------------------------------------------------------------------------------#

UPDATE SETTINGS#

------------------------------------------------------------------------------------------------#

-- run hourly check for new script updates? (0|1) - [1]#

-- notes are sent to global level 500 users when updates are found#

set cfg(update) 1

-- send notes to global 500 users when new script versions are found? (0|1) - [1]#

set cfg(update:notes) 1

-- automatically update script when new updates are found? (0|1) - [0]#

-- notes are sent to global level 500 users when updates are installed#

-- requires cfg(update) to be enabled#

set cfg(update:auto) 0

-- github branch to check and apply updates from - [master]#

set cfg(update:branch) "master"

-- automatically remove old script backups after N days - [7]#

set cfg(update:flush) 7

-- enable debug mode to download and stage new updates but do not execute (0|1) - 0#

set cfg(update:debug) 0

------------------------------------------------------------------------------------------------#

USER REGISTRATION SETTINGS#

------------------------------------------------------------------------------------------------#

-- allow users to self-register bot usernames? (0|1) - [1]#

set cfg(register) 1

-- if allowed, users must be in which channels to self-register usernames?#

space delimited; can be empty to allow in all chans#

set cfg(register:inchan) "#chan1 #chan2"

-- what global access level to give to newusers? - [1]#

-- applies to 'newuser' command when no global level given, and to 'register' command#

set cfg(register:level) 1

-- send notes to global level 500 users when newuser is created with 'register' cmd? (0|1) - [1]#

set cfg(note:register) 1

-- random password string length#

set cfg(randpass) 10

------------------------------------------------------------------------------------------------#

NETWORK SETTINGS#

------------------------------------------------------------------------------------------------#

-- ircd type [1-2] - [1]#

1: ircu (e.g., Undernet, Quakenet)#

2: IRCnet/EFnet#

3: InspIRCd/Solanum/ircd-seven#

4: Unreal#

set cfg(ircd) 1

-- connected via ZNC bouncer? (0|1) - [0]#

this changes 'jump' command behaviour#

set cfg(znc) 0

-- add automatic blacklist host entry upon g-line? (0|1) - [1]#

set cfg(gline:auto) 1

-- required G-Line reason mask for above to apply (wildcards) - [G-Lined *]#

set cfg(gline:mask) "G-Lined *"

-- mask for which G-Lines do NOT add blacklist entry (wildcard) - [AUTO *]#

set cfg(gline:nomask) "G-lined (AUTO *"

-- bot realname on IRC#

set cfg(realname) "Armour - https://armour.bot"

-- hostname of services - [undernet.org]#

set cfg(servicehost) "undernet.org"

------------------------------------------------------------------------------------------------#

BOT SETTINGS#

------------------------------------------------------------------------------------------------#

-- command prefix [c]#

If prefix is a letter, command follows (i.e. c op )#

If prefix is a control char, command is embedded (i.e. !op )#

prefix cannot be *#

set cfg(prefix) "c"

-- allow use of nickname (with or without nick completion char :) as control char? (0|1) - [1]#

set cfg(char:nick) 1

-- require tab completion char ':' on nickname prefix? (0|1) - [1]#

set cfg(char:tab) 1

-- allow global prefix char '*' (0|1) - [0]#

set cfg(char:glob) 1

-- allow command shortcuts? (0|1) - [1]#

k = kick kb = kickban b = black#

a = add v = view r = rem#

e = exempt s = stats t = topic#

o = op d = deop u = userlist#

set cfg(cmd:short) 1

-- default mode on load? (on|off|secure) - [on]#

set cfg(mode) "on"

-- automatically change mode based on changing of mode +D and -D? (0|1) - [1]#

set cfg(mode:auto) 1

-- allow jump command to specify server? (0|1) - [1]#

set cfg(jump) 1

-- send help and syntax responses from public commands via /notice? (0|1) - [0]#

set cfg(help:notc) 0

-- also sent web documentation links in help command responses? (0|1)? - [1]#

set cfg(help:web) 1

-- only output usage and web links in help responses? (0|1)? - [1]#

set cfg(help:webonly) 1

-- stack voice modes when in secure mode? - [0|1]#

-- this will slow down voicing of new users, by the time set in cfg(stack:secs)#

set cfg(stackvoice) 1

-- stackvoice timer (secs) - [5]#

set cfg(stack:secs) 5

-- default whitelist reason - [regular]#

set cfg(def:wreason) "regular"

-- default blacklist reason - [abuse]#

set cfg(def:breason) "abuse"

-- auto add blacklist entries on floodnet?#

set cfg(auto:black) 0

-- netsplit memory timeout (mins) - [60]#

set cfg(split:mem) 60

-- lockdown eggdrop dcc commands? (0|1) - [1]#

set cfg(lockegg) 1

-- if locked egg, what flag is required for all commands? - [n]#

set cfg(lockegg:flag) "n"

-- default temporary exemptions last for how long? (mins) - [5]#

- via 'exempt' command, time can be overriden#

set cfg(exempt:time) 5

-- allow users to set their own channel greetings? (0|1) - [1]#

set cfg(greet:self) 1

-- add cronjob for bots created with 'deploy' command? (0|1) - [1]#

set cfg(deploy:cron) 1

------------------------------------------------------------------------------------------------#

NOTE SETTINGS#

------------------------------------------------------------------------------------------------#

-- allow user notes? (0|1) - [1]#

set cfg(note) 1

-- global command level to send notes to all users? - [450]#

set cfg(note:glob) 450

-- send users note read receipts? (0|1) - [1]#

set cfg(note:receipt) 1

-- send users notes when added to channels? (0|1) - [1]#

set cfg(note:adduser) 1

-- send users notes when removed from channels? (0|1) - [1]#

set cfg(note:remuser) 1

-- send users notes when channel level is modified? (0|1) - [1]#

set cfg(note:level) 1

------------------------------------------------------------------------------------------------#

CHANNEL SETTINGS#

------------------------------------------------------------------------------------------------#

--- channels to NOT operate on. comma separated#

-- command chans (comma delimited). leaeve empy to allow on all channels#

set cfg(chan:nocmd) "#foo1 #foo2"

-- autologin /who cycle (secs) - [60]#

-- how frequently to check the channel list for people who authenticated to X after joining#

- only if enabled above#

set cfg(autologin:cycle) 60

-- default command chan (for applicable privmsg & dcc commands)#

set cfg(chan:def) "EDITCHAN"

-- reporting channel (backchan)#

-- useful for command logging and other diagnostics#

set cfg(chan:report) ""

-- how long to disable floodnet detection after chanmode '+d' (secs) - [60]#

- will be automatically re-enabled on chanmode '-d' anyway#

set cfg(time:moded) 60

-- how many recently joined hostnames to track? (num) - [600]#

- for fast blacklists using type 'last'#

set cfg(lasthosts) 600

-- append the username that set a topic, when set via bot? (0|1) - [1]#

set cfg(topic:who) 0

-- the frequency in mins to check if topic should be reset when 'autotopic' is enabled - [60]#

set cfg(atopic:period) 60

------------------------------------------------------------------------------------------------#

FLOAT LIMIT SETTINGS#

------------------------------------------------------------------------------------------------#

-- automatically enable FLOATLIM with default values for new channels? (0|1) - [1]#

set cfg(float:auto) 1

-- the default frequency in seconds to check if limit should be adjusted - [60]#

-- can be overridden with: modchan floatperiod #

set cfg(float:period) 60

-- the default margin to set limit above currentUser count - [10]#

-- can be overridden with: modchan floatmargin #

set cfg(float:margin) 10

-- the grace limit to trigger limit adjustment - [3]#

-- can be overridden with: modchan floatgrace #

set cfg(float:grace) 3

------------------------------------------------------------------------------------------------#

SERVICE AUTHENTICATION SETTINGS#

------------------------------------------------------------------------------------------------#

-- method to use for channel actions? (1-3) - [1]#

1: use server for all actions#

2: use X for BAN, UNBAN, and KICK, but server for everything else#

3: use X for everything: BAN, UNBAN, KICK, MODE, TOPIC, OP, DEOP, VOICE, DEVOICE, INVITE#

set cfg(chan:method) "1"

-- authentication username#

leave blank if login not used or handled in another script#

leave blank if using NickServ auth mechanism below#

set cfg(auth:user) ""

-- authentication password#

leave blank if login not used or handled in another script#

set cfg(auth:pass) ""

-- TOTP (2FA) secret base32 key#

leave blank to disable 2FA authentication#

note that this currently requires 'oathtool' to be installed on the server#

set cfg(auth:totp) ""

-- mechanism for authentication (gnuworld|nickserv) - [gnuworld]#

set to "gnuworld" for Undernet#

set to "nickserv" for networks with NickServ#

set cfg(auth:mech) "gnuworld"

-- auth service nickname - [X]#

set to "X" for Undernet#

set to "NickServ" for networks with NickServ#

set cfg(auth:serv:nick) "X"

-- auth service host - [channels.undernet.org]#

-- ie. /msg x@channels.undernet.org login #

if using NickServ, change to black of the NickServ services host as needed#

set cfg(auth:serv:host) "channels.undernet.org"

-- extension for umode +x registered users - [users.undernet.org]#

-- change with care to suit your own network#

set cfg(xhost:ext) "users.undernet.org"

-- use umode +x? (host hiding) (0|1) - [1]#

set cfg(auth:hide) 1

-- frequency for authentication retries (mins) - [5]#

set cfg(auth:retry) 5

-- use a random nickname until authenticated? (0|1) - 0#

set cfg(auth:rand) 0

-- wait to join channels until authenticated? (0|1) - 0#

set cfg(auth:wait) 0

------------------------------------------------------------------------------------------------#

KICKBAN SETTINGS#

------------------------------------------------------------------------------------------------#

-- X ban level (if using X) - [100]#

set cfg(ban:level) "100"

-- how long to set temp bans? - [3d]#

units supported:#

s = seconds#

m = minutes#

h = hours#

d = days#

set cfg(ban:time) "3d"

-- autoban on blacklist entry addition? (0|1) - [1]#

set cfg(ban:auto) 1

-- include 'userid' in banmask where ~ not present in ident and user not authed to services? (0|1) - [1]#

-- note that idents with ~ will still always ban as !~@host#

0: !@host#

1: *!user@host#

set cfg(ban:idents) 1

-- how long to set temp adaptive regex bans? - [7d]#

set cfg(ban:time:adapt) "7d"

-- channel modes to set when channel banlist is full - [+r]#

set cfg(ban:full:modes) "+r"

-- how long (secs) to remember 'newcomers' for (for stringent public chatter rules) - [60]#

-- stringent rules apply to newcomers after first joining channel:#

- excessive word repeat#

- profanity (badwords)#

- spam (channels / website)#

- coloured text#

set cfg(time:newjoin) "60"

-- badword masks for public chatter (space delimited with wildcards)#

-- note: more powerful string now matching supported by 'text' type blacklist entries#

set cfg(badwords) "fuck shit"

-- time to allow automatic removal of recent bans by specifying blacklist id (secs) - [3600]#

- also provides memory used for optional blacklist removals based on unbans#

set cfg(id:unban:time) 3600

-- automatically remove blacklist entries when channel ban is removed? (0|1) - [1]#

- user must have 'rem' access; unban must occur within cfg(id:unban:time) of ban#

set cfg(black:unban:rem) 1

------------------------------------------------------------------------------------------------#

SCANNER SETTINGS#

------------------------------------------------------------------------------------------------#

-- include blacklist entry value in kick messages? (0|1) - [0]#

set cfg(black:kick:value) 0

-- include blacklist reason in kick messages? (0|1) - [1]#

set cfg(black:kick:reason) 1

-- auto convert hostnames to IP addresses when using 'add' command for type 'host'? (0|1) - [0]#

set cfg(list:host:convert) 0

-- run blacklist scans on type 'country' when user has resolved ident? - [0]#

-- whitelist scans will always run#

set cfg(country:ident) 0

-- enable dnsbl scans? (0|1) - [1]#

set cfg(dnsbl) 1

-- enable botnet (remote) dnsbl scans? (0|1) - [1]#

set cfg(dnsbl:remote) 0

-- dnsbl, description and score#

last parameter (0|1) determines if RBL is only used for manual scans (scanrbl cmd) or not#

#

- score must be + or - with a score of >0 constituting a hit#

- if 'onlymanual' is set, RBL is only checked when 'scanrbl' command is used but not in auto scans#

format to add:#

set addrbl() "{} "#

set addrbl(dnsbl.dronebl.org) "DroneBL +1.0 0"; # -- auto scanner (and manual) set addrbl(sbl.cymru.com) "{Cymru SBL} +1.0 0"; # -- auto scanner (and manual) set addrbl(pbl.cymru.com) "{Cymru PBL} +1.0 0"; # -- auto scanner (and manual) set addrbl(mbl.cymru.com) "{Cymru MBL} +1.0 0"; # -- auto scanner (and manual) set addrbl(bbl.cymru.com) "{Cymru BBL} +1.0 0"; # -- auto scanner (and manual) set addrbl(rbl.ircbl.org) "IRCBL +1.0 1"; # -- only manual scans as Undernet uses network-wide

set addrbl(rbl.undernet.org) "{Undernet} +1.0 1"; # -- only manual scans as Undernet uses network-wide#

set addrbl(zen.spamhaus.org) "{Spamhaus Zen} +1.0 0"; # -- auto scanner (and manual); eggdrop box must not use public nameservers#

-- enable port scanner? (0|1) [1]#

set cfg(portscan) 1

-- port scan kick reason#

set cfg(portscan:reason) "Armour: possible insecure host unintended for IRC -- please install identd"

-- enable botnet (remote) port scans? (0|1) - [1]#

set cfg(portscan:remote) 0

-- scanports and description#

-- add an 'addport' line for each port to scan, in the format of:#

set addport() ""#

set addport(21) "ftp" set addport(22) "ssh" set addport(23) "telnet" set addport(25) "smtp" set addport(80) "www" set addport(7070) "realserver" set addport(31337) "31337/tcp" set addport(10000) "webmin"

-- minimum port match before action - [1]#

set cfg(portscan:min) 2

-- enable /whois lookups? (0|1) - [1]#

- note: required for 'chan' list entries#

set cfg(whois) 1

-- enable botnet (remote) /whois lookups? (0|1) - [0]#

- not required, but lowers server queue to improve response time on busy channels#

- if enabled, remote bot mut be connected via botnet and have remotescan.tcl (only) loaded#

set cfg(whois:remote) 0

-- if any scanner enabled, scan all? (1) or only unresolved idents? (0) - [0]#

set cfg(scans:all) 0

-- botnet nick of remote bots, if enabled above#

-- ensure this bot is connected to botnet#

set cfg(bot:remote:dnsbl) "remote-bot" set cfg(bot:remote:port) "remote-bot" set cfg(bot:remote:whois) "remote-bot"

------------------------------------------------------------------------------------------------#

CAPTCHA SETTINGS#

-- requires channel mode to be 'secure'#

-- web CAPTCHA requires 'armour-verify' web application @ https://github.com/empus/armour-verify#

------------------------------------------------------------------------------------------------#

CAPTCHA used to help validate suspicious joining clients#

-- enable CAPTCHA for suspicious clients? (0|1) - [1]#

set cfg(captcha) 0

-- CAPTCHA type (web|text) - [text]#

-- note that "web" requires the armour-verify web application#

set cfg(captcha:type) "text"

-- only if cfg(captcha:type) is "web", what is the URL to the web CAPTCHA application?#

-- note that "web" requires the armour-verify web application#

set cfg(captcha:web:url) "https://verify.armour.bot"

-- www.textcaptcha.com ID#

-- only required if cfg(captcha:type) is "text"#

set cfg(captcha:id) "your@emailaddress.com"

-- send CAPTCHA challenge to user via notice or privmsg? (notice|privmsg) - [notice]#

set cfg(captcha:method) "notice"

-- send an opnotice when a question is sent to a user? (0|1) - [1]#

set cfg(captcha:opnotc) 1

-- how long to wait (in secs) for CAPTCHA responses? - [180]#

set cfg(captcha:time) 180

-- action to take for expired responses (manual|kick|kickban) - [kickban]#

set cfg(captcha:expired) "kickban"

-- kick message for expired responses#

set cfg(captcha:expired:kick) "Armour: your lack of CAPTCHA response is disturbing. Please try again or login to X."

-- kick message from web CAPTCHA when kick history is identified#

set cfg(captcha:kick:kick) "Armour: you have a history of abuse."

-- kick message from web CAPTCHA when IP is rate-limited#

set cfg(captcha:ratelimit:kick) "Armour: you have been CAPTCHA rate-limited. Please come back later or login to X."

-- send notice to channel operators when a captcha expectation expires? (0|1) - 1#

set cfg(captcha:expired:ops) 1

-- how many incorrect attempts are allowed before action? - 1#

set cfg(captcha:wrong:attempts) 1

-- action to take for incorrect responses (manual|kick|kickban) - [kickban]#

-- manual: send opnotice to ask ops for manual review#

set cfg(captcha:wrong) "kickban"

-- bantime duration for incorrect responses? - [10m]#

set cfg(captcha:wrong:bantime) "10m"

-- bantime duration for expired responses - [1h]#

set cfg(captcha:expired:bantime) "10m"

-- kick message for incorrect responses#

set cfg(captcha:wrong:kick) "Incorrect response! Please give it some more thought next time."

-- send acknowledgement for correct answers? (0|1) - [1]#

set cfg(captcha:ack) 1

-- what acknwledgement message to send?#

set cfg(captcha:ack:msg) "Correct! You can now speak in the channel."

-- send acknowledgement to channel ops from correct answers? (0|1) - [1]#

set cfg(captcha:ack:ops) 1

------------------------------------------------------------------------------------------------#

IP QUALITY SCORE SETTINGS#

------------------------------------------------------------------------------------------------#

This section relates to the use of ipqualityscore.com lookups for potentially malicious clients#

-- IPQS enable? (0|1) - [0]#

set cfg(ipqs) 0

-- IPQS API key#

set cfg(ipqs:key) "YOUR-KEY-HERE"

-- IPQS only match clients with ~ in ident? (0|1) - [1]#

set cfg(ipqs:onlynoident) 1

-- IPQS minimum fraud score required before action is taken (0-100) - [85]#

set cfg(ipqs:minscore) 90

-- IPQS action (in mode=secure) -- kick|kickban|warn - [kick]#

set cfg(ipqs:action:secure) "kick"

-- IPQS action (in mode=on) -- kick|kickban|warn - [kickban]#

set cfg(ipqs:action:on) "kickban"

-- IPQS ban duration (secs) - [3600]#

set cfg(ipqs:duration) 3600

------------------------------------------------------------------------------------------------#

IRCBL INTEGRATION SETTINGS#

------------------------------------------------------------------------------------------------#

This section relates to the use of IRCBL integration for for insertion & deletion#

-- ircbl.org enable#

set cfg(ircbl) 0;

-- ircbl.org key#

set cfg(ircbl:key) "YOUR-KEY-HERE";

-- ircbl.org default addition type - [11]#

set cfg(ircbl:type) 11;

-- ircbl.org generic comment#

set cfg(ircbl:add:comment) "IRC drone detected";

-- ircbl.org manual addition global level required#

set cfg(ircbl:lvl) 500;

------------------------------------------------------------------------------------------------#

TEXT MATCH BLACKLIST ENTRY SETTINGS#

------------------------------------------------------------------------------------------------#

This section relates to blacklist entries matching channel text (wildcard or regex)#

-- exempt from matching if they are opped? (0|1) - [1]#

set cfg(text:exempt:op) 1

-- exempt from matching if they are voiced? (0|1) - [0]#

set cfg(text:exempt:voice) 0

-- only match clients who have recently joined the channel? (0|1) - [0]#

this relates to above variable cfg(time:newjoin)#

set cfg(text:newcomer) 0

-- add automatic blacklist entries for users who trigger text matches? (0|1) - [0]#

set cfg(text:autoblack) 0

-- if automatic blacklist entries are enabled, what reason to use?#

set cfg(text:autoblack:reason) "(auto) blacklisted from text match violation"

-- if threshold is >1, display warning to user on first occurrence? (0|1) - [0]#

set cfg(text:warn) 1

-- if warning is enabled, send via channel (chan) or /notice (notc)? (chan|notc) - [notc]#

set cfg(text:warn:type) "notc"

-- if warning is enabled, what should the message say?#

set cfg(text:warn:msg) "Please avoid using such language in #channel. Consider this a warning."

-- if sending report to ops/debugchan, what should it say?#

substitution:#

%N% nickname#

%I% ID of entry#

set cfg(text:warn:report) "Armour: %N% has been warned about use of language. [id: %I%]"

------------------------------------------------------------------------------------------------#

LINE FLOOD SETTINGS#

------------------------------------------------------------------------------------------------#

This section relates to line flood handling (from individual clients, or the entire channel)#

-- how many lines to allow from a single client in how many seconds before acting? (lines:secs) - [5:2]#

-- leave config value blank to disable nickname based line flood tracking#

set cfg(flood:line:nicks) "5:2"

-- how many lines to allow from the whole channel in how many seconds before acting? (lines:secs) - [10:2]#

-- NOTE: this should be set based on the expected activity of the channel (to avoid false positives)#

-- leave config value blank to disable channel based line flood tracking#

set cfg(flood:line:chan) "10:2"

-- modes to temporarily lockdown channel with, during entire channel lineflood - [+mr]#

set cfg(flood:line:chan:mode) "+mr"

-- temporary lockdown time when whole channel encounters lineflood (secs) - [60]#

set cfg(flood:line:chan:lock) 60

-- exempt from matching if they are opped? (0|1) - [1]#

set cfg(flood:line:exempt:op) 1

-- exempt from matching if they are voiced? (0|1) - [1]#

set cfg(flood:line:exempt:voice) 1

-- only match clients who have recently joined the channel? (0|1) - [1]#

this relates to above variable cfg(time:newjoin)#

set cfg(flood:line:newcomer) 1

-- kickban reason when user reaches line limit#

set cfg(flood:line:reason) "Armour: flood detected"

-- add automatic blacklist entries for users who trigger text matches? (0|1) - [0]#

set cfg(flood:line:autoblack) 0

-- if automatic blacklist entries are enabled, what reason to use?#

set cfg(flood:line:autoblack:reason) "(auto) blacklisted from line flood"

------------------------------------------------------------------------------------------------#

ADAPTIVE REGEX FLOODNET DETECTION#

------------------------------------------------------------------------------------------------#

-- enable adaptive regex floodnet detection? - [1]#

set cfg(adapt) 1

-- adaptive regex types on join? (space delimited) - [n i]#

n : nickname#

ni : nick!ident#

i : ident#

set cfg(adapt:types:join) "n i"

-- adaptive regex types after /who (space delimited) - [r]#

-- used in secure mode or once rname known#

n : nickname#

ni : nick!ident#

nir : nick!ident/rname#

nr : nick/rname#

i : ident#

ir : ident/rname#

r : rname#

#

use types involving rname and do not duplicate those done above in cfg(adapt:types:join)#

this will prevent double handling for a given pattern (ie. false positives)#

set cfg(adapt:types:who) "r"

-- adaptive regex max join rate (clients:seconds:retain) - [2:1:5]#

clients: client joins#

seconds: .. in N seconds#

retain: & retain common pattern for N seconds (for further joins)#

set cfg(adapt:rate) "2:1:5"

------------------------------------------------------------------------------------------------#

REPORT SETTINGS#

------------------------------------------------------------------------------------------------#

-- include blacklist entry value in kick messages? (0|1) - [0]#

set cfg(report:value) 0

-- include blacklist entry reason in kick messages? (0|1) - [1]#

set cfg(report:reason) 1

-- send user notices for whitelist entries? (0|1) - [0]#

set cfg(notc:white) 0

-- send user notices for blacklist entries? (0|1) - [0]#

set cfg(notc:black) 0

-- send op notices for whitelist entries? (0|1) - [1]#

set cfg(opnotc:white) 1

-- send op notices for blacklist entries? (0|1) - [1]#

set cfg(opnotc:black) 1

-- send op notices for IRC Operator autoops using channel operop setting? (0|1) - [1]#

set cfg(opnotc:operop) 1

-- send op notices for blacklist text warnings? (0|1) - [1]#

set cfg(opnotc:text) 1

-- send debug chan notices for whitelist entries? (0|1) - [1]#

set cfg(dnotc:white) 1

-- send debug chan notices for blacklist entries? (0|1) - [1]#

set cfg(dnotc:black) 1

-- send debug chan notices for blacklist text warnings? (0|1) - [1]#

set cfg(dnotc:text) 1

-- send debug chan notices for IRC Operator autoops using channel operop setting? (0|1) - [1]#

set cfg(dnotc:operop) 1

------------------------------------------------------------------------------------------------#

CUSTOM QUEUE SETTINGS#

------------------------------------------------------------------------------------------------#

-- frequency (in secs) to find hidden users when in secure mode (chanmode +Dm) - [5]#

set cfg(queue:secure) 5

------------------------------------------------------------------------------------------------#

FLOODNET SETTINGS#

------------------------------------------------------------------------------------------------#

-- channel lock modes during floodnet - [+mr]#

set cfg(chanlock:mode) "+mr"

-- channel lock time expiry (secs) - [5]#

-- note: lock will still only be removed after server queue is cleared (ie. kicks)#

set cfg(chanlock:time) "60"

------------------------------------------------------------------------------------------------#

TRAKKA SETTINGS#

------------------------------------------------------------------------------------------------#

-- channel to send trakka alerts to? prefix with @ for opnotice#

-- defaults to channel the client joined, if empty#

set cfg(trakka:alertchan) ""

-- routinely add points every how many mins? - [360]#

- every 6 hours (4 points per day)#

set cfg(trakka:routine) 360

-- routinely save trakka scores to db every N minutes? - [60]#

set cfg(trakka:autosave) 60

-- remove how many points daily from those not in the channel? - [1]#

set cfg(trakka:subtract) 1

-- initial newcomer score gain after how many seconds in chan? - [3600]#

set cfg(trakka:init) 3600

-- kickban user if user still has no score, is not opped, or voiced after a wait period (0|1) - [1]#

-- voice will not be factored if channel is in secure mode#

set cfg(trakka:kb:enable) 1

-- if kickban is enabled, how long should the bot wait before removing? (secs) - [300]#

-- NOTE: this needs to be less than trakka:init value above#

set cfg(trakka:kb:wait) 1800

-- if kickban is enabled, what kick reason to use?#

set cfg(trakka:kb:reason) "Armour: you appear to be unknown and have therefore been removed (trakka)"

-- reason for kick message when nudge command is used#

set cfg(trakka:nudge:reason) "Armour: you appear to be unknown and have therefore been removed (trakka)"

-- duration for kickban when nudge command is used - [1h]#

set cfg(trakka:nudge:time) "1h"

------------------------------------------------------------------------------------------------#

EXTERNAL SCRIPT INTEGRATION#

------------------------------------------------------------------------------------------------#

-- pass user data after negative scans to external scripts with args: nick uhost hand chan#

-- use this so that other scripts can process joining users but only after Armour clears them#

- space delimited#

set cfg(integrate:procs) ""

------------------------------------------------------------------------------------------------#

BOT PROTECTION#

------------------------------------------------------------------------------------------------#

-- list of /silence masks#

- provides server level flood protection#

- leave empty for no /SILENCE#

-- WARNING: if CAPTCHA is being used, SILENCE should be disabled (to allow captcha responses)#

set cfg(silence) "+!@,+~!@.users.undernet.org"; # -- only allow messages from authed users#

set cfg(silence) ""

------------------------------------------------------------------------------------------------#

SECURE MODE (PARANOIA) SETTINGS#

------------------------------------------------------------------------------------------------#

-- when in mode 'secure'#

- number of clones to mark for manual review (no autovoice) [2]#

set cfg(paranoid:clone) 2

-- when in mode 'secure'#

- server connection time must be older than N seconds for autovoice [10]#

set cfg(paranoid:signon) 10

-- send CTCP VERSION to clients to help validate when in 'secure' mode? (0|1) - 1#

set cfg(paranoid:ctcp) 1

-- if enabled, how long to wait for CTCP version response? (secs) - 3#

set cfg(paranoid:ctcp:wait) 3

-- if CTCP reply not received, what action to take? (manual|captcha|kick|kickban) - manual#

set cfg(paranoid:ctcp:action) "manual"

-- if action is set to kick or kickban, what kick message should be used?#

set cfg(paranoid:ctcp:kickmsg) "Armour: your presence in this channel is suspicious."

-- how many times can a client be kicked in what period (secs), before being kickbanned? - [1:330]#

this will prevent a client from continuously auto-rejoining when actions only kick#

the next time a kick is attempted, it will instead revert to kickban#

ideally this should be longer than double both cfg(queue:secure) and cfg(captcha:time) combined (if captcha is used)#

set cfg(paranoid:klimit) "1:670"

------------------------------------------------------------------------------------------------#

DRONEBL#

------------------------------------------------------------------------------------------------#

-- enable DroneBL to support RBL additions and removals?#

-- note that this is not required only to do RBL lookups, which can be done via set scan:rrbls#

set cfg(dronebl) 0

-- DroneBL RPC Key (www.dronebl.org)#

- caution: you are responsible for DroneBL submissions with this key#

- allocate access with care#

set cfg(dronebl:key) ""

-- level required for DroneBL submission [500]#

set cfg(dronebl:lvl) 500

-- default submission type [17]#

set cfg(dronebl:type) 17

------------------------------------------------------------------------------------------------#

QUOTE PLUGIN -- settings only relevant if optional plugin is loaded: quote#

------------------------------------------------------------------------------------------------#

-- who can use the seen command? (1-5) - [2]#

1: all channel users#

2: all voiced, opped, and authed users#

3: only voiced when not secure mode, opped, and authed users#

4: only opped and authed channel users#

5: only authed users with command access#

set cfg(quote:allow) 2

-- level required to delete quotes that are not your own - [200]#

set cfg(quote:cmd:del) 200

-- level required to recall quote stats - [200]#

set cfg(quote:cmd:stats) 200

-- how long to remember last spoken lines in a channel? (mins) - 180#

set cfg(quote:lastspeak:mins) 180

-- with 'quote last', recently spoken lines in the last N seconds will be ignored (secs) - 2#

set cfg(quote:lastspeak:ts) 2

------------------------------------------------------------------------------------------------#

SEEN PLUGIN -- settings only relevant if optional plugin is loaded: seen#

------------------------------------------------------------------------------------------------#

-- who can use the seen command? (1-5) - [2]#

1: all channel users#

2: all voiced, opped, and authed users#

3: only voiced when not secure mode, opped, and authed users#

4: only opped and authed channel users#

5: only authed users with command access#

set cfg(seen:allow) 3

------------------------------------------------------------------------------------------------#

WEATHER PLUGIN -- settings only relevant if optional plugin is loaded: weather#

https://www.openweathermap.org#

------------------------------------------------------------------------------------------------#

-- who can use the weather command? (1-5) - [2]#

1: all channel users#

2: all voiced, opped, and authed users#

3: only voiced when not secure mode, opped, and authed users#

4: only opped and authed channel users#

5: only authed users with command access#

set cfg(weather:allow) 2

-- API key for weather data (www.openweathermap.org)#

set cfg(weather:key) "YOUR-KEY-HERE"

-- units for weather output (metric, imperial, or both) - [both]#

set cfg(weather:units) "both"

-- specify celsius temperature and windspeed with decimal precision? (0|1) - [0]#

set cfg(weather:precise) 0

------------------------------------------------------------------------------------------------#

NINJAS PLUGIN -- settings only relevant if optional plugin is loaded: ninjas#

commands: joke, dad, history, fact, chuck, cocktail#

https://www.api-ninjas.com#

------------------------------------------------------------------------------------------------#

-- who can use the commands: joke, dad, history, fact, chuck, cocktail (1-5) - [3]#

1: all channel users#

2: all voiced, opped, and authed users#

3: only voiced when not secure mode, opped, and authed users#

4: only opped and authed channel users#

5: only authed users with command access#

set cfg(ninjas:allow) 3

-- API Key (www.api-ninjas.com)#

set cfg(ninjas:key) "YOUR-KEY-HERE"

------------------------------------------------------------------------------------------------#

HUMOUR PLUGIN -- settings only relevant if optional plugin is loaded: humour#

commands: meme, gif, praise, insult#

https://www.humorapi.com#

------------------------------------------------------------------------------------------------#

-- who can use the commands: gif, meme, praise, insult (1-5) - [2]#

1: all channel users#

2: all voiced, opped, and authed users#

3: only voiced when not secure mode, opped, and authed users#

4: only opped and authed channel users#

5: only authed users with command access#

set cfg(humour:allow) 3

-- API Key (www.humor-api.com)#

set cfg(humour:key) "YOUR-KEY-HERE"

------------------------------------------------------------------------------------------------#

OPENAI#

-- requires 'openai' plugin to be loaded#

-- requires 'openai' per channel setting to be enabled#

------------------------------------------------------------------------------------------------#

-- AI service#

-- perplexity: perplexity.ai#

-- openai: openai.com#

set cfg(ask:service) "openai"

-- AI model:#

openai: https://platform.openai.com/docs/models#

https://openai.com/api/pricing#

perplexity: https://docs.perplexity.ai/docs/model-cards#

https://docs.perplexity.ai/docs/pricing#

set cfg(ask:model) "gpt-4o-mini"

-- AI API token#

OpenAI: https://platform.openai.com/account/api-keys#

Perplexity: https://www.perplexity.ai/settings/api#

set cfg(ask:token) "YOUR-TOKEN-HERE"

-- OpenAI ChatGPT API organization#

NOTE: This is only required if cfg(ask:service) is set to 'openai'#

https://platform.openai.com/account/org-settings#

set cfg(ask:org) "YOUR-ORG-HERE"

-- API query timeout (secs) - [60]#

set cfg(ask:timeout) 60

-- API Max Answer Lines#

set cfg(ask:lines) "3"

-- API temperature#

The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more#

random, while lower values like 0.2 will make it more focused and deterministic. If set to 0,#

the model will use log probability to automatically increase the temperature until certain#

thresholds are hit.#

set cfg(ask:temp) "0.7"

-- Chatbot description#

Optional instruction to the chatbot to influence its responses#

set cfg(ask:system) "You are $cfg(botname). Be fun and helpful, pretending to be a sentient robot created by Empus."

-- API system behaviour for all user requests#

notes:#

* setting is always applied to all requests#

* per user per chan behaviour can be set with 'askmode' command#

* restricting output line count can help avoid floods or throttlings#

examples:#

"Answer humorously."#

"Answer sarcastically."#

"Pretend to be a sarcastic politician."#

"Act like an idio."#

set cfg(ask:prefix) "Answer in 3 lines or less."

-- channels to apply blacklisted request strings in (space delimited)#

note: set to "" to disable blacklist on all channels#

set cfg(ask:blacklist:chans) ""

-- string masks blacklisted for use in queries#

note: this list is only applied to channels listed in cfg(ask:blacklist:chans)#

set cfg(ask:blacklist) "badword1 another example2"

-- AI conversation memory after initial question (mins) - [60]#

set cfg(ask:mem) 60

-- send all queries to 'ask' when botnick is used? (0|1) - [1]#

NOTE: this will prevent all other commands from using the botnick as a command prefix#

set cfg(ask:cmdnick) 1

-- who can send queries? (1-5) - [3]#

1: all channel users#

2: all voiced, opped, and authed users#

3: only voiced when not secure mode, opped, and authed users#

4: only opped and authed channel users#

5: only authed users#

set cfg(ask:allow) 3

-- DALL-E image creation enable? (0|1) - [1]#

set cfg(ask:image) 1

-- DALL-E image size (pixels) - "1024x1024"#

-- smaller images are faster to generate (512x512, 1024x1024)#

-- note that dall-e-3 does not support 512x512#

set cfg(ask:image:size) "1024x1024"

-- DALL-E image model - [dall-e-3]#

set cfg(ask:image:model) "dall-e-3"

-- DALL-E image overlay with requesting nick and prompt? (0|1) - [1]#

note: ImageMagick7 must be installed on machine (using binary 'convert')#

set cfg(ask:image:overlay) 0

-- DALL-E image creation rate limiting (req:mins:hold) - [5:5:30]#

-- triggered at 5 requests per 5 mins, then retaining rate limit for 30 mins#

-- set to "" to disable rate limiting#

set cfg(ask:image:rate) "5:5:30"

-- DALL-E image creation rate limiting action#

-- set to "" to temporarily prevent all image requests#

-- set value to alternate DALL-E model to reduce API costs during rate limited restriction period#

set cfg(ask:rate:act) "dall-e-2"

-- absolute path to save DALL-E image PNG files to webserver#

set cfg(ask:path) "/home/armour/www/armour"

-- retain PNG and mp3 files for how many days? - [90]#

notes:#

- to keep forever, use empty value#

- those with votes or in quotes will remain preserved#

set cfg(ask:expire) "90"

-- URL root serving PNG files from above path#

set cfg(ask:site) "https://files.armour.bot"

------------------------------------------------------------------------------------------------#

AI SPEAK (text-to-speech)#

------------------------------------------------------------------------------------------------#

-- enable 'speak' functionality? (0|1) - [0]#

set cfg(speak:enable) 0

-- max number of lines to speak in responses#

set cfg(speak:lines) "4"

-- text-to-speech service (elevenlabs, openai) - [openai]#

-- openai: www.openai.com#

-- elevenlabs: www.elevenlabs.io#

set cfg(speak:service) "openai"

-- API query timeout (seconds)#

set cfg(speak:timeout) 30

------------------------------------------------------------------------------------------------#

OPENAI -- relevant settings only if cfg(speak:service) set to: openai#

------------------------------------------------------------------------------------------------#

-- OpenAI text-to-speech voice (alloy, echo, fable, onyx, nova, shimmer) - [onyx]#

set cfg(speak:openai:voice) "onyx"

-- OpenAI text-to-speech model - [tts-1]#

set cfg(speak:openai:model) "tts-1"

-- OpenAI text-to-speech file format mp3, opus, aac, flac#

set cfg(speak:openai:format) "mp3"

------------------------------------------------------------------------------------------------#

ELEVENLABS -- relevant settings only if cfg(speak:service) set to: elevenlabs#

www.elevenlabs.io#

------------------------------------------------------------------------------------------------#

-- ElevenLabs text-to-speech API key#

set cfg(speak:key) "API-KEY-HERE"

-- ElevenLabs text-to-speech voice model#

set cfg(speak:model) "eleven_turbo_v2_5"

-- ElevenLabs text-to-speech voice#

-- https://elevenlabs.io/app/voice-lab#

set cfg(speak:voice) "ZQe5CZNOzWyzPSCn5a3c"

------------------------------------------------------------------------------------------------#

COMMAND LEVELS#

------------------------------------------------------------------------------------------------#

-- level requirements for commands#

-- standard command structure#

- to disable a command, comment out line#

- to change the required level, update the level per command#

- to modify whether a command is accepted via public chan, privmsg, or dcc, update the binds#

#

- binds:#

pub: public channel command#

msg: privmsg command#

dcc: dcc command#

------------------------------------------------------------------------------------------------#

command plugin level req. binds#

------------------------------------------------------------------------------------------------#

set addcmd(register) { userdb 0 pub msg dcc }; # -- only if configured set addcmd(ask) { arm 1 pub msg dcc }; # -- requires openai plugin set addcmd(askmode) { arm 1 pub msg dcc }; # -- requires openai plugin set addcmd(and) { arm 1 pub msg dcc }; # -- requires openai plugin set addcmd(image) { arm 1 pub msg dcc }; # -- requires openai plugin set addcmd(speak) { arm 1 pub msg dcc }; # -- requires speak plugin set addcmd(joke) { arm 1 pub msg dcc }; # -- requires ninjas plugin set addcmd(dad) { arm 1 pub msg dcc }; # -- requires ninjas plugin set addcmd(history) { arm 1 pub msg dcc }; # -- requires ninjas plugin set addcmd(fact) { arm 1 pub msg dcc }; # -- requires ninjas plugin set addcmd(chuck) { arm 1 pub msg dcc }; # -- requires ninjas plugin set addcmd(cocktail) { arm 1 pub msg dcc }; # -- requires ninjas plugin set addcmd(weather) { arm 1 pub msg dcc }; # -- requires weather plugin

set addcmd(meme) { arm 1 pub msg dcc }; # -- requires humour plugin#

set addcmd(gif) { arm 1 pub msg dcc }; # -- requires humour plugin set addcmd(praise) { arm 1 pub msg dcc }; # -- requires humour plugin set addcmd(insult) { arm 1 pub msg dcc }; # -- requires humour plugin set addcmd(seen) { seen 1 pub msg dcc }; # -- requires seen plugin set addcmd(access) { userdb 1 pub msg dcc } set addcmd(cmds) { arm 1 pub msg dcc } set addcmd(help) { arm 1 pub msg dcc } set addcmd(info) { userdb 1 pub msg dcc } set addcmd(verify) { userdb 1 pub msg dcc } set addcmd(lang) { userdb 1 pub msg dcc } set addcmd(set) { userdb 1 pub msg dcc } set addcmd(time) { userdb 1 pub msg dcc } set addcmd(version) { arm 1 pub msg dcc } set addcmd(asn) { arm 1 pub msg dcc } set addcmd(country) { arm 1 pub msg dcc } set addcmd(note) { arm 1 pub msg dcc } set addcmd(exempt) { arm 100 pub msg dcc } set addcmd(queue) { arm 100 pub msg dcc } set addcmd(ack) { trakka 100 pub msg dcc }; # -- requires trakka plugin set addcmd(score) { trakka 100 pub msg dcc }; # -- requires trakka plugin set addcmd(nudge) { trakka 100 pub msg dcc }; # -- requires trakka plugin set addcmd(idle) { arm 100 pub msg dcc } set addcmd(ipqs) { ipqs 100 pub msg dcc }; # -- only if configured set addcmd(view) { arm 100 pub msg dcc } set addcmd(scan) { arm 100 pub msg dcc } set addcmd(search) { arm 100 pub msg dcc } set addcmd(scanrbl) { arm 100 pub msg dcc }; # -- only if configured set addcmd(scanport) { arm 100 pub msg dcc }; # -- only if configured set addcmd(mode) { arm 100 pub msg dcc } set addcmd(add) { arm 100 pub msg dcc } set addcmd(op) { arm 100 pub msg dcc } set addcmd(deop) { arm 100 pub msg dcc } set addcmd(voice) { arm 100 pub msg dcc } set addcmd(devoice) { arm 100 pub msg dcc } set addcmd(kick) { arm 100 pub msg dcc } set addcmd(ban) { arm 100 pub msg dcc } set addcmd(unban) { arm 100 pub msg dcc } set addcmd(topic) { arm 100 pub msg dcc } set addcmd(invite) { arm 100 pub msg dcc } set addcmd(stats) { arm 200 pub msg dcc } set addcmd(status) { arm 200 pub msg dcc } set addcmd(mod) { arm 200 pub msg dcc } set addcmd(black) { arm 200 pub msg dcc } set addcmd(captcha) { arm 300 pub msg dcc }; # -- only if configured set addcmd(chanscan) { arm 300 pub msg dcc } set addcmd(rem) { arm 300 pub msg dcc } set addcmd(newuser) { userdb 400 pub msg dcc } set addcmd(adduser) { userdb 400 pub msg dcc } set addcmd(remuser) { userdb 400 pub msg dcc } set addcmd(userlist) { userdb 400 pub msg dcc } set addcmd(usearch) { userdb 400 pub msg dcc } set addcmd(chanlist) { userdb 400 pub msg dcc } set addcmd(moduser) { userdb 400 pub msg dcc } set addcmd(ignore) { arm 450 pub msg dcc } set addcmd(jump) { arm 450 pub msg dcc } set addcmd(load) { arm 450 pub msg dcc } set addcmd(reload) { arm 450 pub msg dcc } set addcmd(restart) { arm 450 pub msg dcc } set addcmd(rehash) { arm 450 pub msg dcc } set addcmd(addchan) { userdb 450 pub msg dcc } set addcmd(remchan) { userdb 450 pub msg dcc } set addcmd(modchan) { userdb 450 pub msg dcc } set addcmd(deluser) { userdb 450 pub mdg dcc } set addcmd(say) { arm 500 pub msg dcc } set addcmd(deploy) { arm 500 pub msg dcc } set addcmd(do) { userdb 500 pub msg dcc } set addcmd(die) { arm 500 pub msg dcc } set addcmd(conf) { arm 500 pub msg dcc } set addcmd(showlog) { arm 500 pub msg dcc } set addcmd(update) { arm 500 pub msg dcc }

------------------------------------------------------------------------------------------------#

PLUGINS#

------------------------------------------------------------------------------------------------#

-- optional plugins to load#

-- to load plugins, uncomment their line after editing any config parameters in each file#

set addplugin(quote) "./armour/plugins/quote.tcl"; # -- quote management#

set addplugin(openai) "./armour/plugins/openai.tcl"; # -- openai (ChatGPT)#

set addplugin(speak) "./armour/plugins/speak.tcl"; # -- speak (text-to-speech)#

set addplugin(summarise) "./armour/plugins/summarise.tcl"; # -- sumarise chans/people (requires openai)#

set addplugin(ninjas) "./armour/plugins/ninjas.tcl"; # -- ninjas (jokes, facts, etc)#

set addplugin(humour) "./armour/plugins/humour.tcl"; # -- humour (gifs, memes, etc)#

set addplugin(weather) "./armour/plugins/weather.tcl"; # -- current weather by city#

set addplugin(seen) "./armour/plugins/seen.tcl"; # -- showing user lastseen#

set addplugin(tell) "./armour/plugins/tell.tcl"; # -- tell reminders#

set addplugin(trakka) "./armour/plugins/trakka/trakka.tcl"; # -- trakka scoring for private chans#

set addplugin(smsbot) "./armour/plugins/smsbot.tcl"; # -- SMS (via smsglobal.com)#

set addplugin(push) "./armour/plugins/push.tcl"; # -- push (via pushover.net)#

set addplugin(email) "./armour/plugins/email.tcl"; # -- send email notes#

------------------------------------------------------------------------------------------------#

END OF CONFIGURATION#

------------------------------------------------------------------------------------------------#

------------------------------------------------------------------------------------------------#

}; # -- end of namespace

------------------------------------------------------------------------------------------------#

source ./armour/armour.tcl; # -- do not edit

------------------------------------------------------------------------------------------------#

```ˇˇ