Add initial files from envbot v0.1-beta1

This commit is contained in:
Márcio Silva 2017-06-02 15:44:54 -03:00
commit b4830e97ae
81 changed files with 12852 additions and 0 deletions

17
AUTHORS Normal file
View File

@ -0,0 +1,17 @@
Main developer:
Arvid Norlander <anmaster [AT] sourceforge no spam DOT net>
Other developers:
EmErgE
Thanks to:
Remo Ford <remoford [AT] gmail no spam DOT com>
for helping with testing and many good ideas as well as some contributions.
likewhoa
for providing domain name.
Cyrus Lopez
for providing hosting.
Matt Richards (eggy)
for testing and finding bugs.
Vsevolod Kozlov
Thanks for contributions in core and modules.

119
ChangeLog Normal file
View File

@ -0,0 +1,119 @@
ChangeLog
=========
This is a overview of changes users may care about. Detailed changelog can be
done using bzr (does not work in tarballs, you need a development checkout for this):
bzr log -rtag:tag.. --short
Example (list changes compared to 0.1-beta1)
bzr log -rtag:0.1-beta1.. --short
0.1-beta1
---------
A lot is new in this release. Some of the highlights include:
* Help command.
* Coloured log output.
* Modules that is split over several files in a subdirectory.
See contrib/modules/m_perl for an example.
* Lots of speed improvements by avoiding subshells when possible and such.
* Checks in subshells that module loading will work before actually loading it.
This is to avoid crashes on syntax errors in modules and such.
* A new centralised bot command dispatching system that is easier to use
than the old on_PRIVMSG and having every module parse it by itself.
* API documentation is now auto generated from source code using bashdoc.
* Extended and reworked internal API.
* New more flexible module API.
* Many new modules.
* Core:
* commands - List commands from each module.
* ctcp - Responds to CTCPs send to the bot.
* dice - Roll a dice.
* help - Help command.
* karma - Karma module (the common ++/-- stuff)
* nicktracking - tracks nicks <-> host mapping for channels the bot is in.
* ping - Provide some latency info and such.
* uptime - Tell the uptime of the bot.
* Contrib (these either need extra deps or are not supported by the developers):
* convert - Convert between different units (needs GNU or BSD units installed).
* perl - Run perl code (not supported because it may not be safe).
* Lots of bugfixes.
The rest of this change log entry is mainly for developers wanting to port their
code from 0.0.1 to 0.1:
* 0.0.1 modules won't work without change, the both the module and core APIs
have changed a lot. For example on_PRIVMSG should not be used now most of the
time, instead see commands_register function.
See doc/module_api2.txt and the auto generated API docs for details.
* Quite a few Core API functions have changed parameter format:
A lot now take "out parameters" instead of using $() construct. This is
because subshells (caused by the $() construct) are slow.
* Sadly one feature from trunk were too unstable to make it into 0.1, it will
hopefully be in the next version. This feature was periodic events. This is
the reason for some odd code related to transports status in this version
($transport_status and transport_alive to be specific).
0.0.1
-----
475 Updated man page.
472,474 Fixed bash version check.
471 Fixed an incorrect regular expression in lib/main.sh
470 Made list_contains use grep -F instead.
469 Fixed typo in ebuild.
0.0.1-rc1
---------
465 Backported fix to remove eval from various places.
463-464 Fixed bug in modules/m_kick_ban.sh that made bot loose part of kick reason sometimes.
462 Fixed bug in modules/m_join.sh that caused it to never send a reason on part.
459-461 Added Gentoo ebuild for envbot.
457 Made it work correctly on FreeBSD
456 Fixed bug 29 (Channels not rejoined after ping timeout) and similar issue in modules/m_services.sh (ghost needed state not reset).
453 Fixed for one aspect of bug 11.
0.0.1-beta5
-----------
450 Merged r451 from trunk: Fix broken misc_clean_spaces (was broken after beta4).
443 Made parse_hostmask_*() faster and added some missing quotes.
441 Found that an INVITE hook was missing, added it.
430-435 Added more numerics.
429 Workaround for bug #21 added (Own nick desync during connect with ghost).
0.0.1-beta4
-----------
424 Config version update: Added $config_log_raw. Defines if we should log raw lines or not (affects both STDOUT and logfiles)
420,423 misc_clean_spaces was slow, changed to inline ways of stripping spaces.
416 Got rid of deprecated parse_get_colon_arg function.
415 Added some code to check for stuff that matches no hook, and found that there is no umode change hook because of this. Fixed.
411 Made assignment of factoids work if there is more than one separator in string, yes this may be slower but it works.
410 Fixed bug in factoids with forget command not reporting "I didn't have a factoid matching..." when none was found.
0.0.1-beta3
-----------
407 Make error messages in transports more verbose.
402-406 Make m_calc.sh check it's arguments on IRC better to make it secure.
401 Fix bug that made modules_load break if a dependency failed to load.
398 Made main envbot a wrapper script that sanitise the environment and then executes main bot.
395 Some more functions are now internal in lib/log.sh, modules should use the log level versions.
393 m_autojoin was slowing down rehash more than needed, fixed that.
392 Fixed: Could not unload rehash, module_rehash_UNLOAD returned 127!
391 Fixed bug that caused FINALISE hooks to never run
383 Add bash version check (it turned out bash-3.1 and older was not supported).
0.0.1-beta2
-----------
379 Actually more than one space between parameters on IRC is possible, yes that sucks, and no sane server would send it, but just to be on the safe side
377 Added this ChangeLog.
376 Fixed potential security problem in SQL cleaning code in module_sqlite3_clean_string (I can't find a way to abuse this so I don't think the risk is very high)
375 Added access_log_action()
373-375 Introduced more advanced logging API with several levels (info, warn, error and fatal)
371 Optimised log_write() in lib/log.sh (something like 20 times as fast now if my testing is correct)
0.0.1-beta1
-----------
First version, no ChangeLog as there is nothing to older to list changes against.

340
GPL2.txt Normal file
View File

@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

674
GPL3.txt Normal file
View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

16
INSTALL Normal file
View File

@ -0,0 +1,16 @@
Installing envbot
=================
Installation isn't needed, you can run it from source directory.
Installation is possible though, but not recommended.
All you have to do is to get the bot running is:
* Run:
make
* Copy bot_settings.sh.example to bot_settings.sh
* Edit bot_settings.sh to set correct nick, IRC server, service password, and so on.
* Run:
./envbot
To install use something like (change the paths to fit your system):
make install DESTDIR=/ PREFIX=/home/user/envbot

146
Makefile Normal file
View File

@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
# This file is used to generate some, uh, generated files.
# Also some other tasks
# Useful targets:
# all: Builds numerics file and generates example config
# man: Generates man page using help2man
# apidocs: Generates API docs from code for public functions
# apidocs-all: Generates API docs from code for public and internal functions.
# install: Installs to a DESTDIR (don't confuse with DISTDIR),
# See variables below.
# dist-dir: Generates a clean checkout of current version, ready to be
# tared up. Can only be done in a bzr branch/checkout
ENVBOT_VERSION = 0.1-beta1
# For make dest-dir, defaults
DISTDIR ?= dist
# For make install, defaults
DESTDIR ?= DEST
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
CONFDIR ?= $(PREFIX)/etc
LIBDIR ?= $(PREFIX)/lib
DATADIR ?= $(PREFIX)/share
MANDIR ?= $(DATADIR)/man
# And now for actual place of stuff
ENVBOT_LIBDIR ?= $(LIBDIR)/envbot
ENVBOT_TRANSPORTDIR ?= $(ENVBOT_LIBDIR)/transport
ENVBOT_LIBRARYDIR ?= $(ENVBOT_LIBDIR)/lib
ENVBOT_MODULESDIR ?= $(ENVBOT_LIBDIR)/modules
ENVBOT_DATADIR ?= $(DATADIR)/envbot/data
ENVBOT_LOGDIR ?= $(DATADIR)/envbot/logs
ENVBOT_CONFDIR ?= $(CONFDIR)/envbot
ENVBOT_DOCDIR ?= $(DATADIR)/doc/envbot-$(ENVBOT_VERSION)
# Now for some commands
INSTALL ?= install -p
SED ?= sed
RM ?= rm
all: numerics config
config:
$(SED) "s|@@moddir@@|modules|;s|@@transportdir@@|transport|;s|@@datadir@@|data|;s|@@logdir@@|logs|" doc/bot_settings.sh.example.in > bot_settings.sh.example
numerics:
tools/build_numerics.sh > lib/numerics.sh
# Used by developers to update man page.
man:
help2man -NS envbot -n 'An advanced modular IRC bot in bash' "/usr/bin/env bash envbot" > doc/envbot.1
clean:
$(RM) -vf *~ */*~ */*/*~ */*/*/*~ bot_settings.sh.example
cleandocs:
$(RM) -rf doc/api/private-core
$(RM) -rf doc/api/public-core
$(RM) -rf doc/api/private-modules
$(RM) -rf doc/api/public-modules
cleanlogs:
$(RM) -vrf logs/*
apidocs-private:
./tools/bashdoc/bashdoc.sh -p "envbot Core API (private functions) for "$(ENVBOT_VERSION) -o doc/api/private-core lib/*.sh
./tools/bashdoc/bashdoc.sh -p "envbot module-provided API (private functions) for "$(ENVBOT_VERSION) -o doc/api/private-modules modules/*.sh
apidocs-public:
./tools/bashdoc/bashdoc.sh -e "Type=API" -p "envbot Core API for "$(ENVBOT_VERSION) -o doc/api/public-core lib/*.sh
./tools/bashdoc/bashdoc.sh -e "Type=API" -p "envbot module-provided API for "$(ENVBOT_VERSION) -o doc/api/public-modules modules/*.sh
apidocs: apidocs-public
apidocs-all: apidocs-private apidocs-public
checkvars:
@if [ "$(ENV_USERNAME)" = "" ]; then \
echo "Please call this script with the ENV_USERNAME environment variable set"; \
exit 1; \
fi
@if [ "$(ENV_PATH)" = "" ]; then \
echo "Please call this script with the ENV_PATH environment variable set"; \
exit 1; \
fi
apidocs-upload: checkvars
rsync -hhzcrv --progress --delete --stats -e ssh doc/api/ $(ENV_USERNAME)@envbot.org:$(ENV_PATH)/
dist-dir:
$(RM) -rf $(DISTDIR)
bzr export $(DISTDIR)
install: cleandocs all apidocs-public
@echo "#########################################################################"
@echo "# #"
@echo "# Installing... Note that running from source directory is recommended! #"
@echo "# #"
@echo "#########################################################################"
$(INSTALL) -d $(DESTDIR)$(PREFIX) $(DESTDIR)$(BINDIR)
$(INSTALL) -d $(DESTDIR)$(ENVBOT_LIBDIR) $(DESTDIR)$(ENVBOT_CONFDIR)
$(INSTALL) -d $(DESTDIR)$(ENVBOT_DATADIR) $(DESTDIR)$(ENVBOT_TRANSPORTDIR)
$(INSTALL) -d $(DESTDIR)$(ENVBOT_LIBRARYDIR) $(DESTDIR)$(ENVBOT_MODULESDIR)
$(INSTALL) -d $(DESTDIR)$(ENVBOT_DOCDIR) $(DESTDIR)$(MANDIR)/man1
$(INSTALL) -d $(DESTDIR)$(ENVBOT_LOGDIR) $(DESTDIR)$(ENVBOT_DOCDIR)/api
$(INSTALL) -d $(DESTDIR)$(ENVBOT_DOCDIR)/api/core $(DESTDIR)$(ENVBOT_DOCDIR)/api/modules
$(INSTALL) -m 644 lib/*.sh $(DESTDIR)$(ENVBOT_LIBRARYDIR)
$(INSTALL) -m 644 modules/*.sh $(DESTDIR)$(ENVBOT_MODULESDIR)
$(INSTALL) -m 644 transport/*.sh $(DESTDIR)$(ENVBOT_TRANSPORTDIR)
$(INSTALL) -m 644 README AUTHORS GPL3.txt $(DESTDIR)$(ENVBOT_DOCDIR)
$(INSTALL) -m 644 doc/*.sql $(DESTDIR)$(ENVBOT_DOCDIR)
$(INSTALL) -m 644 doc/*.txt $(DESTDIR)$(ENVBOT_DOCDIR)
$(INSTALL) -m 644 doc/api/public-core/*.html $(DESTDIR)$(ENVBOT_DOCDIR)/api/core/
$(INSTALL) -m 644 doc/api/public-core/*.css $(DESTDIR)$(ENVBOT_DOCDIR)/api/core/
$(INSTALL) -m 644 doc/api/public-modules/*.html $(DESTDIR)$(ENVBOT_DOCDIR)/api/modules/
$(INSTALL) -m 644 doc/api/public-modules/*.css $(DESTDIR)$(ENVBOT_DOCDIR)/api/modules/
$(INSTALL) -m 644 doc/envbot.1 $(DESTDIR)$(MANDIR)/man1
$(INSTALL) -m 644 data/{faq.txt.example,quotes.txt.example.pqf} $(DESTDIR)$(ENVBOT_DATADIR)
$(SED) "s|^library_dir=.*|library_dir='$(ENVBOT_LIBRARYDIR)'|;s|^config_file=.*|config_file='$(ENVBOT_CONFDIR)/bot_settings.sh'|" envbot > envbot.tmp
$(INSTALL) envbot.tmp $(DESTDIR)$(BINDIR)/envbot
$(RM) envbot.tmp
$(SED) "s|@@moddir@@|$(ENVBOT_MODULESDIR)|;s|@@transportdir@@|$(ENVBOT_TRANSPORTDIR)|;s|@@datadir@@|$(ENVBOT_DATADIR)|;s|@@logdir@@|$(ENVBOT_LOGDIR)|" doc/bot_settings.sh.example.in > bot_settings.tmp
$(INSTALL) -m 644 bot_settings.tmp $(DESTDIR)$(ENVBOT_CONFDIR)/bot_settings.sh.example
$(RM) bot_settings.tmp
.PHONY: all apidocs apidocs-private apidocs-public checkvars apidocs-upload numerics clean cleanlogs cleandocs dist-dir

101
README Normal file
View File

@ -0,0 +1,101 @@
envbot - A modular IRC bot in bash
==================================
envbot is a modular IRC bot coded in bash.
http://envbot.org/trac
Features include:
* SSL
* IPv6
* Transport (SSL, whatever) to server are also modules
* Modularity
* Loading, unloading and reloading of modules at runtime
* Rehashing configuration at runtime
* Advanced access control
See http://envbot.org/trac/query?status=new&status=assigned&status=reopened&type=enchantment&order=priority for other planed features
Installing
----------
See the file INSTALL.
License
-------
envbot is licensed under GPL version 3, with the exceptions of:
data/quotes.txt.example.pqf:
The source (games-misc/fortune-mod-pqf/fortune-mod-pqf-6.0.ebuild from Gentoo portage)
says it is under GPL-2.
tools/bashdoc/bashdoc.sh
GPL-2. bashdoc is a heavily updated and modified version of bashdoc from
the sourcemage project.
Dependencies
------------
* bash - version 3.2.10 or later should work fine, but not tested on anything below 3.2.17
* Standard POSIX tools. Should be included on any recent and sane Linux
distro.
Some transports and module have extra dependencies. Note that when it says
"you need the program" just having the library won't work. You actually need
a program with this name.
Transports:
dev-tcp:
The bash you use must support the pseudo device /dev/tcp. Debian is known
to disable this. Most other distros are sane and have it on.
netcat:
You need the program netcat.
This is for Debian users and others with a broken distro. If your
distro supports it use dev-tcp transport instead.
I have only tested with GNU netcat. (http://netcat.sourceforge.net/)
Supports binding to a specific IP.
gnutls:
You need the program gnutls-cli. (http://www.gnutls.org/)
openssl:
You need the program openssl. (http://www.openssl.org/)
socat:
You need the program socat. (http://www.dest-unreach.org/socat/)
Note that while socat support IPv4, IPv6, SSL and non-SSL it doesn't
support both SSL and IPv6 at the same time if the version of socat
is lower than 1.5.
Supports binding to a specific IP.
Modules:
sqlite3
You need the program sqlite3. (http://www.sqlite.org/)
factoids
This depends on the sqlite3 module and therefore have the
same dependencies as it
seen
This depends on the sqlite3 module and therefore have the
same dependencies as it
Contributed modules
-------------------
These are extra modules in contrib/modules. They are not really
supported by the developers. Information about dependencies and
extra configuration options is in each contrib module.
If you want to use a contrib module the recommended way is:
cd modules
ln -s ../contrib/modules/m_modulename.sh
That way if the contrib module is updated you will get the new
version automatically.
Feedback
--------
We (the developers) would love to get feedback on what you like/dislike with
envbot, what features you want, and what you use it for.
Please also report any bugs you find at http://envbot.org/trac/simpleticket
(no login needed, but please enter your email so we can contact you if we
need more details about your problem).
Contacting developers
---------------------
You can reach us on IRC.
1) Server: irc.kuonet-ng.org
Channel: #envbot
2) Server: irc.securitychat.org
Channel: #envbot

3
TODO Normal file
View File

@ -0,0 +1,3 @@
TODO list
---------
TODO list has been moved to our issue tracker at http://envbot.org/trac/query

6
contrib/README Normal file
View File

@ -0,0 +1,6 @@
contrib modules and scripts
===========================
This directory contain stuff that the normal developers doesn't want to support
(even though they may have coded it)
See each file for description

View File

@ -0,0 +1,48 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007 Remo Ford #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
ENVBOT_DIR="/path/to/envbot"
ENVBOT_OPTIONS=""
ENVBOT_USER="envbot"
PID_FILE="/var/run/envbot.pid"
test -f /lib/lsb/init-functions || exit 1
. /lib/lsb/init-functions
case "$1" in
start)
log_begin_msg "Starting envbot..."
start-stop-daemon -b -d $ENVBOT_DIR --start -m -p $PID_FILE -c $ENVBOT_USER --exec $ENVBOT_DIR/envbot $ENVBOT_OPTIONS || log_end_msg 1
log_end_msg 0
;;
stop)
log_begin_msg "Stopping envbot..."
start-stop-daemon --stop --quiet -p $PID_FILE || log_end_msg 1
log_end_msg 0
;;
*)
log_success_msg "Usage: /etc/init.d/envbot {start|stop}"
exit 1
;;
esac
exit 0

View File

@ -0,0 +1,231 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Check bugs using the program bugz and return output from it.
## @pybugz bugz is a tool to search Gentoo bug reports (or other bugzillas)<br />
## @pybugz From eix pybugz:<br />
## @pybugz Description: Command line interface to (Gentoo) Bugzilla
## @Dependencies This module therefore depends on:<br />
## @Dependencies pybugz
## @Config_variables To set bugzilla to use something like this in config:<br />
## @Config_variables <pre>config_module_bugzilla_tracker_name[0]='gentoo'
## @Config_variables config_module_bugzilla_tracker_url[0]='https://bugs.gentoo.org/'</pre>
## @Config_variables Must end in trailing slash! Also the first entry will be the default.<br />
## @Config_variables You also need to specify flood limiting<br />
## @Config_variables (how often in seconds)<br />
## @Config_variables <tt>config_module_bugzilla_rate='10'</tt>
#---------------------------------------------------------------------
module_bugzilla_INIT() {
modinit_API='2'
modinit_HOOKS='after_load'
commands_register "$1" 'bugs_search' 'bugs search' || return 1
commands_register "$1" 'bug' || return 1
helpentry_module_bugzilla_description="Search in bugzilla bug trackers."
helpentry_bugzilla_bugs_search_syntax='[-t <tracker>] [-(all|closed)] <pattern>'
helpentry_bugzilla_bugs_search_description='Search for <pattern> in <tracker> (or the default tracker).'
helpentry_bugzilla_bug_syntax='[-t <tracker>] <id>'
helpentry_bugzilla_bug_description='Look up the bug with <id> in <tracker> (or the default tracker).'
}
module_bugzilla_UNLOAD() {
unset module_bugzilla_last_query module_bugzilla_default_bugtracker
unset module_bugzilla_parse_config module_bugzilla_find_tracker
hash_reset 'bugzilla_tracker'
}
module_bugzilla_REHASH() {
module_bugzilla_parse_config
}
#---------------------------------------------------------------------
## Initialize the hash of name -> url mapping
## @Type Private
#---------------------------------------------------------------------
module_bugzilla_parse_config() {
hash_reset 'bugzilla_tracker'
local index
for index in "${!config_module_bugzilla_tracker_name[@]}"; do
hash_set 'bugzilla_tracker' \
"${config_module_bugzilla_tracker_name[index]}" \
"${config_module_bugzilla_tracker_url[index]}"
done
module_bugzilla_default_bugtracker="${config_module_bugzilla_tracker_url[0]}"
}
#---------------------------------------------------------------------
## Find what tracker to use from the parameters.
## @param Name of parameter variable.
## @param Name of bugtracker variable.
## @Type Private
#---------------------------------------------------------------------
module_bugzilla_find_tracker() {
if [[ "${!1}" =~ ^(-(tracker|t)\ +([A-Za-z0-9]+)\ +)(.+) ]]; then
local tindex="${BASH_REMATCH[3]}"
# Store result back in variable.
printf -v "$1" '%s' "${BASH_REMATCH[4]}"
local turl
hash_get 'bugzilla_tracker' "$tindex" 'turl'
if [[ $turl ]]; then
printf -v "$2" '%s' "$turl"
else
feedback_generic_error "$sendernick" "bugs search" "No such bug tracker found."
fi
else
printf -v "$2" '%s' "${module_bugzilla_default_bugtracker}"
fi
}
# Called after module has loaded.
# Check for bugz
module_bugzilla_after_load() {
if ! hash bugz > /dev/null 2>&1; then
log_error "Couldn't find bugz command line tool. The bugzilla module depend on that tool (emerge pybugz to get it on Gentoo)."
return 1
fi
if [[ -z ${config_module_bugzilla_tracker_url[0]} ]]; then
log_error "Please set at least config_module_bugzilla_url[0] in config."
return 1
fi
if [[ -z ${config_module_bugzilla_tracker_name[0]} ]]; then
log_error "Please set at least config_module_bugzilla_name[0] in config."
return 1
fi
if [[ -z $config_module_bugzilla_rate ]]; then
log_error "Please set config_module_bugzilla_rate in config."
return 1
fi
module_bugzilla_parse_config
unset module_bugzilla_last_query
module_bugzilla_last_query='0'
}
module_bugzilla_handler_bugs_search() {
# Accept this anywhere, unless someone can give a good reason not to.
local sender="$1"
local channel="$2"
local sendernick=
parse_hostmask_nick "$sender" 'sendernick'
# If it isn't in a channel send message back to person who send it,
# otherwise send in channel
if ! [[ $2 =~ ^# ]]; then
channel="$sendernick"
fi
local parameters="$3"
local bugtracker
module_bugzilla_find_tracker 'parameters' 'bugtracker'
if [[ "$parameters" =~ ^(-(all|closed)\ +)?(.+) ]]; then
local mode="${BASH_REMATCH[2]}"
local pattern="${BASH_REMATCH[@]: -1}"
# Simple flood limiting
if time_check_interval "$module_bugzilla_last_query" "$config_module_bugzilla_rate"; then
time_get_current 'module_bugzilla_last_query'
local bugs_parameters=""
if [[ $mode = "all" ]]; then
bugs_parameters="-s all"
elif [[ $mode = "closed" ]]; then
bugs_parameters="-s CLOSED -s RESOLVED"
fi
log_info_file bugzilla.log "$sender made the bot run pybugz search on \"$pattern\""
# We unset TERM because otherwise bugz output some control codes
local result="$(unset TERM; ulimit -t 4; bugz -fqb "$bugtracker" search $bugs_parameters "$pattern")"
local lines="$(wc -l <<< "$result")"
local header footer
# Some odd formatting chars are always returned (in some versions of pybugz), so we can't check for empty string.
if [[ ${#result} -le 10 ]]; then
header="No bugs matching \"$pattern\" found"
elif [[ $lines -gt 1 ]]; then
header="First bug matching \"$pattern\": "
footer=" ($lines more bugs found)"
else
header="One bug matching \"$pattern\" found: "
fi
if [[ $(head -n 1 <<< "$result") =~ \ ([0-9]+)\ +([^ ]+)\ +(.*)$ ]]; then
local pretty_result="${format_bold}${bugtracker}${BASH_REMATCH[1]}${format_bold} ${format_bold}Description${format_bold}: ${BASH_REMATCH[3]} ${format_bold}Assigned To${format_bold}: ${BASH_REMATCH[2]}"
fi
send_msg "$channel" "${header}${pretty_result}${footer}"
else
log_error_file bugzilla.log "FLOOD DETECTED in bugzilla module"
fi
else
feedback_bad_syntax "$sendernick" "bugs search" "[-t tracker] [-(all|closed)] <pattern>"
fi
}
module_bugzilla_handler_bug() {
# Accept this anywhere, unless someone can give a good reason not to.
local sender="$1"
local channel="$2"
local sendernick=
parse_hostmask_nick "$sender" 'sendernick'
# If it isn't in a channel send message back to person who send it,
# otherwise send in channel
if ! [[ $2 =~ ^# ]]; then
channel="$sendernick"
fi
local parameters="$3"
local bugtracker
module_bugzilla_find_tracker 'parameters' 'bugtracker'
# Extract bug ID
if [[ "$parameters" =~ ^([0-9]+) ]]; then
local id="${BASH_REMATCH[1]}"
# Simple flood limiting
if time_check_interval "$module_bugzilla_last_query" "$config_module_bugzilla_rate"; then
time_get_current 'module_bugzilla_last_query'
log_info_file bugzilla.log "$sender made the bot check with pybugz for bug \"$id\""
# We unset TERM because otherwise bugz output some control codes
local result="$(unset TERM; ulimit -t 4; bugz -fqb "$bugtracker" get -n "$id" | grep -E 'Title|Status|Resolution')"
local resultread pretty_result
local title status resolution
# Read the data out of the multiline result.
while read -r resultread; do
if [[ $resultread =~ ^Title[\ :]+([^ ].*) ]]; then
title="${BASH_REMATCH[1]}"
elif [[ $resultread =~ ^Status[\ :]+([^ ].*) ]]; then
status="${BASH_REMATCH[1]}"
elif [[ $resultread =~ ^Resolution[\ :]+([^ ].*) ]]; then
resolution="${BASH_REMATCH[1]}"
fi
done <<< "$result"
# Yes this is a bit of a mess
if [[ "$title" ]]; then
# This info is always here
pretty_result="${format_bold}Bug $id${format_bold} (${format_bold}Status${format_bold} $status"
# The resolution may not exist, add it if it does.
if [[ $resolution ]]; then
pretty_result+=", ${format_bold}Resolution${format_bold} $resolution"
fi
# And add the title in. Does not depend on if resolution exist.
pretty_result+="): $title (${bugtracker}${id})"
else
pretty_result="Bug $id not found"
fi
send_msg "$channel" "${pretty_result}"
else
log_error_file bugzilla.log "FLOOD DETECTED in bugzilla module"
fi
else
feedback_bad_syntax "$sendernick" "bug" "[-t tracker] <id>"
fi
}

74
contrib/modules/m_calc.sh Normal file
View File

@ -0,0 +1,74 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 EmErgE <halt.system@gmail.com> #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Calculate with bc
## @Dependencies This module depends on bc
## @Dependencies (http://www.gnu.org/software/bc/bc.html)
#---------------------------------------------------------------------
module_calc_INIT() {
modinit_API='2'
modinit_HOOKS=''
if ! hash bc > /dev/null 2>&1; then
log_error "Couldn't find \"bc\" command line tool. The calc module depend on that tool."
return 1
fi
commands_register "$1" 'calc' || return 1
helpentry_module_calc_description="Simple calculator module."
helpentry_calc_calc_syntax='<expression>'
helpentry_calc_calc_description='Try to calculate <expression> using bc.'
}
module_calc_UNLOAD() {
return 0
}
module_calc_REHASH() {
return 0
}
module_calc_handler_calc() {
local sender="$1"
local channel="$2"
local sendernick=
parse_hostmask_nick "$sender" 'sendernick'
# If it isn't in a channel send message back to person who send it,
# otherwise send in channel
if ! [[ $2 =~ ^# ]]; then
channel="$sendernick"
fi
local parameters="$3"
# Sanity check on parameters
parameters="$(tr -d '\n\r\t' <<< "$parameters")"
if grep -Eq "scale=|read|while|if|for|break|continue|print|return|define|[e|j] *\(" <<< "$parameters"; then
send_msg "$channel" "${sendernick}: Can't calculate that, it contains a potential unsafe/very slow function."
elif [[ $parameters =~ \^[0-9]{4,} ]]; then
send_msg "$channel" "${sendernick}: Some too large numbers."
else
# Force some security guards
local myresult="$(ulimit -t 4; echo "$parameters" | bc -l 2>&1 | head -n 1)"
send_msg "$channel" "${sendernick}: $myresult"
fi
}

View File

@ -0,0 +1,117 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Convert values with units
## @Dependencies This module depends on units
## @Dependencies (http://www.gnu.org/software/units/units.html)
#---------------------------------------------------------------------
module_convert_INIT() {
modinit_API='2'
modinit_HOOKS=''
if ! hash units > /dev/null 2>&1; then
log_error "Couldn't find \"units\" command line tool. The convert module depend on that tool."
return 1
fi
# Is it GNU units?
if units --help > /dev/null 2>&1; then
module_convert_gnu=1
else
module_convert_gnu=0
fi
commands_register "$1" 'convert' || return 1
helpentry_module_convert_description="Convert between different units."
helpentry_convert_convert_syntax='<value> <unit> [to] <unit>'
helpentry_convert_convert_description='Convert the value from one unit to another.'
}
module_convert_UNLOAD() {
return 0
}
module_convert_REHASH() {
return 0
}
module_convert_handler_convert() {
local sender="$1"
local channel="$2"
local sendernick=
parse_hostmask_nick "$sender" 'sendernick'
# If it isn't in a channel send message back to person who send it,
# otherwise send in channel
if ! [[ $2 =~ ^# ]]; then
channel="$sendernick"
fi
local parameters="$3"
# Format: convert <value> <in unit> <out unit>
if [[ "$parameters" =~ ^([-0-9.]+)\ +([a-zA-Z0-9^/*]+)\ +(to\ +)?([a-zA-Z0-9^/*]+) ]]; then
local value="${BASH_REMATCH[1]}"
local inunit="${BASH_REMATCH[2]}"
local outunit="${BASH_REMATCH[@]: -1}"
# Construct expression of value and inunit,
# needed because of temperature
case $inunit in
C|F|K)
# This only work on GNU units.
if [[ $module_convert_gnu = 1 ]]; then
local inexpr="temp${inunit}($value)"
else
local inexpr="$value deg${inunit}"
fi
;;
*)
local inexpr="$value $inunit"
;;
esac
# Out: Temperature
case $outunit in
C|F|K)
# This only work on GNU units
if [[ $module_convert_gnu = 1 ]]; then
local outexpr="temp${outunit}"
else
local outexpr="deg${outunit}"
fi
local outunit="degrees $outunit"
;;
*)
local outexpr="$outunit"
;;
esac
# Need to do the local separately or return code will be messed up.
local myresult
# Force some security guards
# We can't use -t, that doesn't work on *BSD units...
# so we use awk to get interesting lines.
# Then check pipestatus to give nice return code
myresult="$(ulimit -t 4; units -q "$inexpr" "$outexpr" 2>&1 | awk '/^\t[0-9]+/ {print $1} /\*/ {print $2} /[Ee]rror|[Uu]nknown/'; [[ ${PIPESTATUS[0]} -eq 0 ]] || exit 1)"
if [[ $? -eq 0 ]]; then
send_msg "$channel" "${sendernick}: $myresult $outunit"
else
send_msg "$channel" "${sendernick}: Error: $myresult"
fi
else
feedback_bad_syntax "$sendernick" "convert" "<value> <in unit> [to] <out unit>"
fi
}

101
contrib/modules/m_eix.sh Normal file
View File

@ -0,0 +1,101 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Check eix and return output from it.
## @eix eix is a tool to search Gentoo packages<br />
## @eix From eix eix:<br />
## @eix <tt> Description: Small utility for searching ebuilds with indexing for fast results</tt>
## @Dependencies This module therefore depends on:<br />
## @Dependencies Gentoo<br />
## @Dependencies eix<br />
## @Config_variables You need to specify flood limiting in config.<br />
## @Config_variables (how often in seconds)<br />
## @Config_variables <tt>config_module_eix_rate='5'</tt><br />
#---------------------------------------------------------------------
module_eix_INIT() {
modinit_API='2'
modinit_HOOKS='after_load'
commands_register "$1" 'eix' || return 1
helpentry_module_eix_description="Search in Gentoo package database."
helpentry_eix_eix_syntax='<pattern>'
helpentry_eix_eix_description='Search for wildcard pattern in the local copy of the Gentoo package database.'
}
module_eix_UNLOAD() {
unset module_eix_format_string module_eix_last_query
}
module_eix_REHASH() {
return 0
}
# Called after module has loaded.
# Check for eix and config being sane.
module_eix_after_load() {
# Check (silently) for eix
if ! hash eix >/dev/null 2>&1; then
log_error "Couldn't find \"eix\" command line tool. The eix module depend on that tool."
return 1
fi
if [[ -z $config_module_eix_rate ]]; then
log_error_file eix.log "YOU MUST SET config_module_eix_rate IN YOUR CONFIG IN ORDER TO USE THE EIX MODULE"
return 1
fi
# Flood limiting.
unset module_eix_last_query
module_eix_last_query='0'
}
#---------------------------------------------------------------------
## eix format string.
## @Type Private
#---------------------------------------------------------------------
module_eix_format_string="<category>/${format_bold}<name>${format_bold} \(<availableversionsshort>\) \(${format_bold}<homepage>${format_bold}\): <description>"
module_eix_handler_eix() {
# Accept this anywhere, unless someone can give a good reason not to.
local sender="$1"
local channel="$2"
# If it isn't in a channel send message back to person who send it,
# otherwise send in channel
if ! [[ $2 =~ ^# ]]; then
parse_hostmask_nick "$sender" 'channel'
fi
local parameters="$3"
if [[ "$parameters" =~ ^(.+) ]]; then
local pattern="${BASH_REMATCH[1]}"
# Simple flood limiting
if time_check_interval "$module_eix_last_query" "$config_module_eix_rate"; then
time_get_current 'module_eix_last_query'
log_info_file eix.log "$sender made the bot run eix on \"$pattern\""
send_msg "$channel" "$(ulimit -t 4; EIX_PRINT_IUSE='false' eix -pSCxs --format "$module_eix_format_string" "$pattern" | head -n 1)"
else
log_error_file eix.log "FLOOD DETECTED in eix module"
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "eix" "<pattern>"
fi
}

55
contrib/modules/m_eval.sh Normal file
View File

@ -0,0 +1,55 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Allow owners to make the bot eval any code<br />
## THIS IS FOR DEBUGGING ONLY!!!! Don't use it in other cases
#---------------------------------------------------------------------
module_eval_INIT() {
modinit_API='2'
modinit_HOOKS=''
commands_register "$1" 'eval' || return 1
helpentry_module_eval_description="Eval command for developers debugging the bot. Don't use if you don't know what you are doing."
helpentry_eval_eval_syntax='<expression>'
helpentry_eval_eval_description='Evaluate <expression> in global scope.'
}
module_eval_UNLOAD() {
return 0
}
module_eval_REHASH() {
return 0
}
module_eval_handler_eval() {
# Accept anywhere
local sender="$1"
if access_check_owner "$sender"; then
local parameters="$3"
access_log_action "$sender" "did eval with: $parameters"
eval "$parameters"
else
access_fail "$sender" "eval a command" "owner"
fi
}

View File

@ -0,0 +1,193 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Example module meant to help people who want to make modules for envbot
#---------------------------------------------------------------------
#---------------------------------------------------------------------
## This is called to get a list of hooks that the module provides.
## Use the hook after_load to do other things
## @Type Module hook
## @Stdout A list of hooks.
#---------------------------------------------------------------------
module_helloworld_INIT() {
modinit_API='2'
# Set modinit_HOOKS to the hooks we have.
modinit_HOOKS='after_load'
# Register commands, each command handler will have a name like:
# module_modulename_handler_function
# Example: module_helloworld_handler_hi
# If command name and function name are the same you can skip
# command name.
commands_register "$1" 'hi' || return 1
# Here the function name and command name can't be the same,
# as the command name got space in it. Note that a command can
# be at most two words.
commands_register "$1" 'hello_world' 'hello world' || return 1
helpentry_module_helloworld_description="This is an example module."
helpentry_helloworld_hi_syntax='<target> <message>'
helpentry_helloworld_hi_description='Send a greeting to <target> (nick or channel) with the <message>.'
helpentry_helloworld_helloworld_syntax='<message>'
helpentry_helloworld_helloworld_description='Send a greeting to the current scope with the one word <message>.'
}
#---------------------------------------------------------------------
## Here we do anything needed to unload the module.
## @Type Module hook
## @return 0 Unloaded correctly
## @return 1 Failed to unload. On this the bot will quit.
## @Note This function is NOT called when the bot is exiting. To check for that
## @Note use the FINALISE hook!
#---------------------------------------------------------------------
module_helloworld_UNLOAD() {
# Here we unset any functions and variables that we have defined
# except the hook functions.
unset module_helloworld_variable module_helloworld_function
}
#---------------------------------------------------------------------
## Here do anything needed at rehash
## @Type Module hook
## @return 0 Rehashed correctly
## @return 1 Non fatal error for the bot itself. The bot will call UNLOAD on the module.
## @return 2 Fatal error of some kind. On this the bot will quit.
#---------------------------------------------------------------------
module_helloworld_REHASH() {
# We don't have anything to do here.
return 0
}
#---------------------------------------------------------------------
## Called after all the hooks are added for the module.
## @Type Module hook
## @return 0 Unloaded correctly
## @return 1 Failed. On this the bot will call unload on the module.
#---------------------------------------------------------------------
module_helloworld_after_load() {
# Set a global variable, this can't be done in INIT.
# Remember to unset all global variables on UNLOAD!
module_helloworld_variable="world!"
}
#---------------------------------------------------------------------
## This logs "hello world" as an informative level log item
## when called
## @Type Private
## @Note Note that this is a custom function used by
## @Note some other part of the script
#---------------------------------------------------------------------
module_helloworld_function() {
# Lets use the variable defined above!
log_info "Hello $module_helloworld_variable"
}
#---------------------------------------------------------------------
## Called on the command "hello world"
## @Type Function handler
## @param From who (n!u@h)
## @param To who (channel or botnick)
## @param The parameters to the command
#---------------------------------------------------------------------
module_helloworld_handler_hello_world() {
local sender="$1"
local target
# If it isn't in a channel send message back to person who send it,
# otherwise send in channel
if [[ $2 =~ ^# ]]; then
target="$2"
else
# parse_hostmask_nick gets the nick from a hostmask.
parse_hostmask_nick "$sender" 'target'
fi
local parameters="$3"
# Check if the syntax for the parameters is correct!
# Lets check for one parameter without spaces
if [[ "$parameters" =~ ^([^ ]+) ]]; then
# Store the bit in the first group of the regex into
# the variable message
local message="${BASH_REMATCH[1]}"
# Send a hello world message:
send_msg "$target" "Hello world! I had the parameter $message"
else
# So the regex for matching parameters didn't work, lets provide
# the user with some feedback!
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "hello world" "<message> # Where message is one word!"
fi
}
#---------------------------------------------------------------------
## Called on the command "hi"
## @Type Function handler
## @param From who (n!u@h)
## @param To who (channel or botnick)
## @param The parameters to the command
#---------------------------------------------------------------------
module_helloworld_handler_hi() {
local sender="$1"
local parameters="$3"
# Two parameters, one is single word, the other matches to
# end of line.
if [[ "$parameters" =~ ^([^ ]+)\ (.+) ]]; then
# Store the groups in some variables.
local target_channel="${BASH_REMATCH[1]}"
local message="${BASH_REMATCH[2]}"
# This is used for the access check below.
# Check if target is a channel or nick.
local scope
if [[ $target_channel =~ ^# ]]; then
scope="$target_channel"
else
scope="MSG"
fi
# Lets check for access.
# First variable is capability to check for
# Second variable is the hostmask of the sender of the message
# Third variable is the scope, that we set above.
if access_check_capab "hi" "$sender" "$scope"; then
# Such important events for security as a "hi" should
# really get logged even if it fails! ;)
access_log_action "$sender" "made the hi channel \"$message\" in/to \"$target_channel\""
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
send_msg "${target_channel}" "Hi $target_channel! $sendernick wants you to know ${message}"
# As an example also call our function.
module_helloworld_function
else
# Lets tell the sender they lack access!
# access_fail will send a PRIVMSG to the sender saying permission denied
# and also log the failed attempt.
access_fail "$sender" "make the bot hi" "hi"
fi
else
# As above, provide feedback about bad syntax.
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "hi" "<target> <message> # Where target is a nick or channel"
fi
}

View File

@ -0,0 +1,74 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 EmErgE <halt.system@gmail.com> #
# Copyright (C) 2007-2008 Vsevolod Kozlov #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Evaluate with perl
## @Dependencies This module depends on perl
## @Dependencies (http://www.perl.org/about.html)
## @Note This may not be safe!
#---------------------------------------------------------------------
module_perl_INIT() {
modinit_API='2'
modinit_HOOKS=''
if ! hash perl > /dev/null 2>&1; then
log_error "Couldn't find \"perl\" binary. The perl module depends on it."
return 1
fi
module_perl_working_dir="$MODULE_BASE_PATH"
commands_register "$1" 'perl' || return 1
helpentry_module_perl_description="Execute perl code."
helpentry_perl_perl_syntax='<code>'
helpentry_perl_perl_description='Execute perl code.'
}
module_perl_UNLOAD() {
unset module_perl_working_dir
unset module_perl_handler_perl
return 0
}
module_perl_REHASH() {
return 0
}
module_perl_handler_perl() {
local sender="$1"
local channel="$2"
local sendernick=
parse_hostmask_nick "$sender" 'sendernick'
if access_check_capab "perl_eval" "$sender" "$channel"; then
# If it isn't in a channel send message back to person who send it,
# otherwise send in channel
if ! [[ $2 =~ ^# ]]; then
channel="$sendernick"
fi
local parameters="$3"
# Extremely Safe Perl Evaluation
local myresult="$(perl "${module_perl_working_dir}/safe_eval.pl" "$parameters")"
send_msg "$channel" "${sendernick}: $myresult"
else
access_fail "$sender" "make the bot evalute perl expressions" "perl_eval"
fi
}

View File

@ -0,0 +1,25 @@
#!/usr/bin/perl
use strict;
use Safe;
my $expr = shift;
my $cpt = new Safe;
#Basic variable IO and traversal
$cpt->permit(':base_core');
$SIG{ALRM} = sub {
die "Alarm";
};
alarm(4);
my $ret = $cpt->reval($expr);
if ($@) {
print $@;
} else {
print $ret;
}

4
data/faq.txt.example Normal file
View File

@ -0,0 +1,4 @@
This is an example FAQ item
This is another FAQ item
Oh wow a third one!
Forth FAQ item

570
data/quotes.txt.example.pqf Normal file
View File

@ -0,0 +1,570 @@
"But you read a lot of books, I'm thinking. Hard to have faith, ain't it, when you've read too many books?" -- (Terry Pratchett, Carpe Jugulum)
"Nac mac Feegle wha hae!" -- (Terry Pratchett, Carpe Jugulum)
In Ghat they believe in vampire watermelons, although folklore is silent about *what* they believe about vampire watermelons. Possibly they suck back. -- (Terry Pratchett, Carpe Jugulum)
Perdita thought that not obeying rules was somehow *cool*. Agnes thought that rules like "Don't fall into this huge pit of spikes" were there for a purpose. -- (Terry Pratchett, Carpe Jugulum)
Lancre operated on the feudal system, which was to say, everyone feuded all the time and handed on the fight to their descendants. -- (Terry Pratchett, Carpe Jugulum)
"I name you ... Esmeralda Margaret Note Spelling of Lancre!" -- (Terry Pratchett, Carpe Jugulum)
There are many rhymes about magpies, but none of them is very reliable because they are not the ones the magpies know themselves. -- (Terry Pratchett, Carpe Jugulum)
One or two of the old barrows had been exposed over the years, their huge stones attracting their own folklore. If you left your unshod horse at one of them overnight and placed sixpence on the stone, in the morning the sixpence would be gone and you'd never see your horse again, either... -- (Terry Pratchett, Carpe Jugulum)
Fewer birds could sit more meekly than the Lancre wowhawk, or lappet-faced worrier, a carnivore permanently on the lookout for the vegetarian option. -- (Terry Pratchett, Carpe Jugulum)
- "Remember -- that which does not kill us can only make us stronger." - "And that which *does* kill us leaves us *dead*!" -- (Terry Pratchett, Carpe Jugulum)
Rincewind is one of those people who gets in the way of his own happiness. If it was raining kisses he'd be the only person with an umbrella. -- (Terry Pratchett, CIX Pratchett Conference)
He was always at a loss when people acted like this. When machines went funny you just oiled them or prodded them or, if nothing else worked, hit them with a hammer. Nomes didn't respond well to this treatment. -- (Terry Pratchett, Diggers)
"Nah," he said, eventually. "I've looked at the colours on flowers. They're definitely built-in." -- (Terry Pratchett, Diggers)
"There is nothing that can be in our way, for this is Jekub, that Laughs at Barriers, and says brrm-brrm." -- From the Book Of Nome, Jekub, Chap. 3, v. V (Terry Pratchett, Diggers)
The trouble with having an open mind, of course, is that people will insist on coming along and trying to put things in it. -- (Terry Pratchett, Diggers)
- "Do you know, humans think the world was made by a sort of big human?" - "Get away?" - "It took a week." - "I expect it had some help, then,' said Dorcas. -- The Nomes discuss religion (Terry Pratchett, Diggers)
If broomsticks were cars, this one would be a split-window Morris Minor. -- (Terry Pratchett, Equal Rites)
Still, it was a relief to get away from that macabre sight. Gander considered that gnolls didn't look any better inside than out. He hated their guts. -- (Terry Pratchett, Equal Rites)
"While I'm still confused and uncertain, it's on a much higher plane, d'you see, and at least I know I'm bewildered about the really fundamental and important facts of the universe." Treatle nodded. "I hadn't looked at it like that," he said, "But you're absolutely right. He's really pushed back the boundaries of ignorance." -- Discworld scientists at work (Terry Pratchett, Equal Rites)
They both savoured the strange warm glow of being much more ignorant than ordinary people, who were only ignorant of ordinary things. -- Discworld scientists at work (Terry Pratchett, Equal Rites)
They may have been ugly. they may have been evil. But when it came to poetry in motion, the Things had all the grace and coordination of a deck-chair. -- Meet the creatures from the Dungeon Dimensions (Terry Pratchett, Equal Rites)
"They say there's dwarf mines under the Ramtops," she said inconsequentially. "My, but them little buggers is in for a surprise." -- Granny reflects on Esk's methods of lighting a fire. (Terry Pratchett, Equal Rites)
For animals, the entire universe has been neatly divided into things to (a) mate with, (b) eat, (c) run away from, and (d) rocks. -- (Terry Pratchett, Equal Rites)
No enemies had ever taken Ankh-Morpock. Well *technically* they had, quite often; the city welcomed free-spending barbarian invaders, but somehow the puzzled raiders found, after a few days, that they didn't own their horses any more, and within a couple of months they were just another minority group with its own graffiti and food shops. -- (Terry Pratchett, Eric)
Old Tom was the single cracked bronze bell in the University bell tower. The clapper dropped out shortly after it was cast, but the bell still tolled out some tremendously sonorous silences every hour. -- (Terry Pratchett, Eric)
Rincewind had been told that death was just like going into another room. The difference is, when you shout, "Where's my clean socks?", no-one answers. -- (Terry Pratchett, Eric)
It was true about the time measurement as well. The Tezumen had realized long ago that everything was steadily getting worse and, having a terrible little-mindedness, had developed a complex system to keep track of how much worse each succeeding day was. -- (Terry Pratchett, Eric)
"These people were not only cheering, they were throwing flowers and hats. The hats were made of stone, but the thought was there." -- Life among the primitive Discworld tribes (Terry Pratchett, Eric)
While working his way along a wall he came to a huge door, which artistically portrayed a group of prisoners apparently being given a complete medical check-up [footnote: From a distance it did, anyway. Close to, no]. -- Rincewind visits the Tezumen tribe (Terry Pratchett, Eric)
- "There's a door" - "Where does it go?" - "It stays where it is, I think." -- (Terry Pratchett, Eric)
The trouble is that things *never* get better, they just stay the same, only more so. -- (Terry Pratchett, Eric)
- "So we're surrounded by absolutely nothing. There's a word for it. It's what you get when there's nothing left and everything's been used up." - "Yes. I think it's called the bill." -- (Terry Pratchett, Eric)
- "What're quantum mechanics?" - "I don't know. People who repair quantums, I suppose." -- (Terry Pratchett, Eric)
The librarian was, ex officio, a member of the college council. No-one had been able to find any rule about orang-utans being barred, although they had surreptiously looked very hard for one. -- Unseen University politics at work (Terry Pratchett, Eric)
I HOPE WE ARE NOT GOING TO HAVE ANY OF THIS 'FOUL FIEND' BUSINESS AGAIN. -- Death gets summoned by the college council (Terry Pratchett, Eric)
There had been some desultory talk about putting up a statue to Rincewind but, by the curious alchemy that tends to apply in these sensitive issues, this quickly became a plaque, then a note on the Roll of Honour, and finally a motion of censure for being improperly dressed. -- Unseen University politics at work (Terry Pratchett, Eric)
Any wizard bright enough to survive for five minutes was also bright enough to realise that if there was any power in demonology, then it lay with the demons. Using it for your own purposes would be like trying to beat mice to death with a rattlesnake. -- Why summoning demons is a Bad Idea (Terry Pratchett, Eric)
The gods of the Disc have never bothered much about judging the souls of the dead, and so people only go to hell if that's where they believe, in their deepest heart, that they deserve to go. Which they won't do if they don't know about it. This explains why it is so important to shoot missionaries on sight. -- (Terry Pratchett, Eric)
"You mean mysterious ancient races of Amazonian princesses who subject all male prisoners to strange and exhausting progenitative rites?" said Eric, his glasses beginning to fog. -- (Terry Pratchett, Eric)
The consensus seemed to be that if really large numbers of men were sent to storm the mountain, then enough might survive the rocks to take the citadel. This is essentially the basis of all military thinking. -- (Terry Pratchett, Eric)
The sergeant put on the poker face which has been handed down from NCO to NCO ever since one protoamphibian told another, lower ranking protoamphibian to muster a squad of newts and Take That Beach. -- (Terry Pratchett, Eric)
- "What shall I do?" - "Well, if you see anything crawl out of the sea and try to breathe, you could try telling it not to bother." -- Rincewind and Eric at the Beginning of Time (Terry Pratchett, Eric)
"Multiple exclamation marks," he went on, shaking his head, "are a sure sign of a diseased mind." -- Something that Terry feels strongly about, because a similar quote also appears in "Reaper Man" (Terry Pratchett, Eric)
The Supreme Life President of Hell wrote: "What business are we in???" He thought for a bit, and then carefully wrote, underneath: "We are in the damnation business!!!" -- (Terry Pratchett, Eric)
"Bingeley bingeley beep!" -- (Terry Pratchett, Feet of Clay)
...He hated the very idea of the world being divided into the shaved and the shavers. Or those who wore the shiny boots and those who cleaned the mud off them. Every time he saw Willikins the butler fold his, Vimes's, clothes, he suppressed a terrible urge to kick the butler's shiny backside as an affront to the dignity of man. -- (Terry Pratchett, Feet of Clay)
I AM DEATH, NOT TAXES. *I* TURN UP ONLY ONCE. -- (Terry Pratchett, Feet of Clay)
Slab: Jus' say "AarrghaarrghpleeassennononoUGH" -- Detritus' war on drugs (Terry Pratchett, Feet of Clay)
And, while it was regarded as pretty good evidence of criminality to be living in a slum, for some reason owning a whole street of them merely got you invited to the very best social occasions. -- (Terry Pratchett, Feet of Clay)
There were no public health laws in Ankh-Morpork. It would be like installing smoke detectors in Hell. -- (Terry Pratchett, Feet of Clay)
"Just because someone's a member of an ethnic minority doesn't mean they're not a nasty small-minded little jerk [...]" -- (Terry Pratchett, Feet of Clay)
You never ever volunteered. Not even if a sergant stood there and said, "We need someone to drink alcohol, bottles of, and make love, passionate, to women, for the use of." There was *always* a snag. If a choir of angels asked for volunteers for Paradise to step forward, Nobby knew enough to take one smart pace to the rear. -- (Terry Pratchett, Feet of Clay)
"Today Is A Good Day For Someone Else To Die!" -- (Terry Pratchett, Feet of Clay)
Rumour is information distilled so finely that it can filter through anything. It does not need doors and windows -- sometimes it does not need people. It can exist free and wild, running from ear to ear without ever touching lips. -- (Terry Pratchett, Feet of Clay)
In all, I've had seventeen demands for your badge. Some want parts of your body attached. Why did you have to upset everybody? -- Lord Vetinari reproves Vimes. (Terry Pratchett, Feet of Clay)
It was Carrot who'd suggested to the Patrician that hardened criminals should be given the chance to "serve the community" by redecorating the homes of the elderly, lending a new terror to old age and, given Ankh-Morpork's crime rate, leading to at least one old lady having her front room wallpapered so many times in six months that now she could only get in sideways. -- (Terry Pratchett, Feet of Clay)
It was hard enough to kill a vampire. You could stake them down and turn them into dust and ten years later someone drops a drop of blood in the wrong place and *guess who's back*? They returned more times than raw broccoli. -- (Terry Pratchett, Feet of Clay)
Many people, meeting Aziraphale for the first time, formed three impressions: that he was English, that he was intelligent, and that he was gayer than a tree full of monkeys on nitrous oxide. -- (Terry Pratchett & Neil Gaiman, Good Omens)
Kids! Bringing about Armageddon can be dangerous. Do not attempt it in your home. -- (Terry Pratchett & Neil Gaiman, Good Omens)
"You can't second-guess ineffability, I always say." -- (Terry Pratchett & Neil Gaiman, Good Omens)
He was currently wondering vaguely who Moey and Chandon were. -- Crowley listens to his favourite rock group (Terry Pratchett & Neil Gaiman, Good Omens)
He'd been particularly pleased with Manchester. -- Crowley contemplating his achievements (Terry Pratchett & Neil Gaiman, Good Omens)
The only time Crowley had bought petrol was once in 1967, to get the free James Bond bullet-hole-in-the-windscreen transfers, which he rather fancied at the time. -- (Terry Pratchett & Neil Gaiman, Good Omens)
He wondered reflectively what would happen if you asked a nun where the Gents was. Probably the Pope sent you a sharp note or something. -- (Terry Pratchett & Neil Gaiman, Good Omens)
Humans suffering from a conflict of signals aren't the best people to be holding guns, especially when they've just witnessed a natural childbirth, which definitely looked an un-American way of bringing new citizens into the world. -- (Terry Pratchett & Neil Gaiman, Good Omens)
Of course he was all in favour of Armageddon in *general* terms. -- (Terry Pratchett & Neil Gaiman, Good Omens)
"You see a wile, you thwart. Am I right?" -- Crowley the demon and Aziraphale the angel in conversation (Terry Pratchett & Neil Gaiman, Good Omens)
On those occasions when the angel managed to get his mind into the twentieth century, it always gravitated to 1950. -- (Terry Pratchett & Neil Gaiman, Good Omens)
They drove back through the dawn, while the cassette player played J. S. Bach's Mass in B Minor, vocals by F. Mercury. -- (Terry Pratchett & Neil Gaiman, Good Omens)
"Art thou a witch, *viva espana*?" -- The Spanish Inquisition (Tadfield version) in action (Terry Pratchett & Neil Gaiman, Good Omens)
Anathema didn't only believe in ley-lines, but in seals, whales, bicycles, rainforests, whole grain in loves, recycled paper, white South Africans out of South Africa, and Americans out of practically everywhere down to and including Long Island. -- (Terry Pratchett & Neil Gaiman, Good Omens)
Shadwell hated all southerners and, by inference, was standing at the North Pole. -- (Terry Pratchett & Neil Gaiman, Good Omens)
DON'T THINK OF IT AS DYING, said Death. JUST THINK OF IT AS LEAVING EARLY TO AVOID THE RUSH. -- (Terry Pratchett & Neil Gaiman, Good Omens)
The Kappamaki, a whaling research ship, was currently researching the question: How many whales can you catch in one week? -- (Terry Pratchett & Neil Gaiman, Good Omens)
The kraken stirs. And ten billion sushi dinners cry out for vengeance. -- (Terry Pratchett & Neil Gaiman, Good Omens)
"?" he said. -- (Terry Pratchett & Neil Gaiman, Good Omens)
Madame Tracy had even removed most of the Major Arcana from her Tarot card pack, because their appearance tended to upset people. -- (Terry Pratchett & Neil Gaiman, Good Omens)
I DON'T CARE WHAT IT SAYS, said the tall biker in the helmet, I NEVER LAID A FINGER ON HIM. -- (Terry Pratchett & Neil Gaiman, Good Omens)
Voodoo is a very interesting religion for the whole family, even those members of it who are dead. -- (Terry Pratchett & Neil Gaiman, Good Omens)
"Jesus won't cut you off before you're through With him you won't never get a crossed line, And when your bill comes it'll all be properly itemised He's the telephone repairman on the switchboard of my life. The phone line to the saviour's always free of interference He's in at any hour, day or night And when you call J-E-S-U-S you always call toll-free He's the telephone repairman on the switchboard of my life."
-- (Terry Pratchett & Neil Gaiman, Good Omens)
- "ALL YOU CAN HOPE FOR IS THE MERCY OF HELL." - "Yeah?" - "JUST OUR LITTLE JOKE." - "Ngk," said Crowley. -- Crowley in conversation with his superiors (Terry Pratchett & Neil Gaiman, Good Omens)
Death and Famine and War and Pollution continued biking towards Tadfield. And Grievous Bodily Harm, Cruelty To Animals, Things Not Working Properly Even After You've Given Them A Good Thumping but secretly No Alcohol Lager, and Really Cool People travelled with them. -- The eight Bikers of the Apocalypse (Terry Pratchett & Neil Gaiman, Good Omens)
Thud. Thud. Thud. Splat. -- (Terry Pratchett & Neil Gaiman, Good Omens)
"Did any of them kids have some space alien with a face like a friendly turd in a bike basket?" -- (Terry Pratchett & Neil Gaiman, Good Omens)
Crowley had been extremely impressed with the warranties offered by the computer industry, and had in fact sent a bundle Below to the department that drew up the Immortal Soul agreements, with a yellow memo form attached just saying: "Learn, guys." -- Crowley is a demon, in case you don't know (Terry Pratchett & Neil Gaiman, Good Omens)
R. P. Tyler was not, however, satisfied simply with being vouchsafed the difference between right and wrong. He felt it his bounden duty to tell the world. -- (Terry Pratchett & Neil Gaiman, Good Omens)
"This isn't how I imagined it, chaps," said War. "I haven't been waiting for thousands of years just to fiddle around with bits of wire. It's not what you'd call *dramatic*. Albrecht Duerer didn't waste his time doing woodcuts of the Four Button-Pressers of the Apocalypse, I do know that." -- Armageddon delayed by technical difficulties (Terry Pratchett & Neil Gaiman, Good Omens)
"I don't see why it matters what is written. Not when it's about people. It can always be crossed out." -- (Terry Pratchett & Neil Gaiman, Good Omens)
In the Beginning
It was a nice day. -- (Terry Pratchett & Neil Gaiman, Good Omens)
If you take the small view, the universe is just something small and round, like those water-filled balls which produce a miniature snowstorm when you shake them. Although, unless the ineffable plan is a lot more ineffable than it's given credit for, it does not have a large plastic snowman at the bottom. -- (Terry Pratchett & Neil Gaiman, Good Omens)
- "You're Hells Angels, then? What chapter are you from?" - REVELATIONS, CHAPTER SIX. -- Death in conversation with a biker (Terry Pratchett & Neil Gaiman, Good Omens)
The lorry blocked the road. And the corrugated iron blocked the road. And a thirty-foot-high pile of fish blocked the road. It was one of the most effectively blocked roads the sergeant had ever seen. -- (Terry Pratchett & Neil Gaiman, Good Omens)
Crowley was in Hell's bad books. Not that Hell has any other kind. -- (Terry Pratchett & Neil Gaiman, Good Omens)
God does not play dice with the universe: He plays an ineffable game of His own devising, which might be compared, from the perspective of any of the other players [i.e. everybody], to being involved in an obscure and complex variant of poker in a pitch-dark room, with blank cards, for infinite stakes, with a Dealer who won't tell you the rules, and who *smiles all the time*. -- (Terry Pratchett & Neil Gaiman, Good Omens)
It wasn't a dark and stormy night. It should have been, but there's the weather for you. For every mad scientist who's had a convenient thunderstorm just on the night his Great Work is complete and lying on the slab, there have been dozens who've sat around aimlessly under the peaceful stars while Igor clocks up the overtime. -- (Terry Pratchett & Neil Gaiman, Good Omens)
Many phenomena - wars, plagues, sudden audits - have been advanced as evidence for the hidden hand of Satan in the affairs of Man, but whenever students of demonology get together the M25 London orbital motorway is generally agreed to be among the top contenders for exhibit A. -- (Terry Pratchett & Neil Gaiman, Good Omens)
Sister Mary headed through the night-time hospital with the Adversary, Destroyer of Kings, Angel of the Bottomless Pit, Great Beast that is called Dragon, Prince of This World, Father of Lies, Spawn of Satan and Lord of Darkness safely in her arms. She found a bassinet and laid him down in it. He gurgled. She gave him a tickle. -- The antichrist is born (Terry Pratchett & Neil Gaiman, Good Omens)
Mr Young hadn't had to quiet a screaming baby for years. He'd never been much good at it to start with. He'd always respected Sir Winston Churchill, and patting small versions of him on the bottom had always seemed ungracious. -- (Terry Pratchett & Neil Gaiman, Good Omens)
The ducks in St James's Park are so used to being fed bread by secret agents meeting clandestinely that they have developed their own Pavlovian reaction. Put a St James's Park duck in a laboratory cage and show it a picture of two men -- one usually wearing a coat with a fur collar, the other something sombre with a scarf -- and it'll look up expectantly. -- (Terry Pratchett & Neil Gaiman, Good Omens)
A man threw himself through the window, a knife between his teeth, a Kalashnikov automatic rifle in one hand, a grenade in the other. "I glaim gis oteg in der gaing og der --" he paused. He tooke the knife out of his teeth and began again. -- (Terry Pratchett & Neil Gaiman, Good Omens)
Jaime had never realised that trees made a sound when they grew, and no-one else had realised it either, because the sound is made over hundreds of years in waves of twenty-four hours from peak to peak. Speed it up, and the sound a tree makes is *vrooom*. -- (Terry Pratchett & Neil Gaiman, Good Omens)
... walking like a man carrying a thermos flask of something that might cause, if he dropped it or even thought about dropping it, the sort of explosion that impels grey-beards to make statements like "And where this crater is now, once stood the city of Wah-Shing-Ton", in SF B-movies. -- Crowley gets out the Holy Water (Terry Pratchett & Neil Gaiman, Good Omens)
She'd stopped reading the kind of women's magazine that talked about romance and knitting and started reading the kind of women's magazine that talked about orgasms, but apart from making a mental note to have one if ever the occasion presented itself she dismissed them as only romance and knitting in a new form. -- (Terry Pratchett & Neil Gaiman, Good Omens)
"[...] a number of offences of murder by means of a blunt instrument, to whit, a dragon, and many further offences of generalized abetting [...]" -- (Terry Pratchett, Guards! Guards!)
"Have another drink, not-Corporal Nobby?" said Sergeant Colon unsteadily. "I do not mind if I do, not-Sgt Colon," said Nobby. -- The joys of working undercover (Terry Pratchett, Guards! Guards!)
"'E's fighting in there!" he stuttered, grabbing the captain's arm. "All by himself?" said the captain. "No, with everyone!" shouted Nobby, hopping from one foot to the other. -- Making Friends and Hitting People (Terry Pratchett, Guards! Guards!)
FABRICATI DIEM, PVNC. -- The motto of the Ankh-Morpork City Watch (Terry Pratchett, Guards! Guards!)
"Pour encourjay lays ortras." -- (Terry Pratchett, Guards! Guards!)
"This is Lord Mountjoy Quickfang Winterforth IV, the hottest dragon in the city. It could burn your head clean off." -- Captain Vimes addresses a band of rioters (Terry Pratchett, Guards! Guards!)
A good bookshop is just a genteel Black Hole that knows how to read. -- (Terry Pratchett, Guards! Guards!)
There was a thoughtful pause in the conversation as the assembled Brethren mentally divided the universe into the deserving and the undeserving, and put themselves on the appropriate side. -- The Elucidated Brethren see the light (Terry Pratchett, Guards! Guards!)
All dwarfs have beards and wear up to twelve layers of clothing. Gender is more or less optional. -- (Terry Pratchett, Guards! Guards!)
All dwarfs are by nature dutiful, serious, literate, obedient and thoughtful people whose only minor failing is a tendency, after one drink, to rush at enemies screaming "Arrrrrrgh!" and axing their legs off at the knee. -- (Terry Pratchett, Guards! Guards!)
People who are rather more than six feet tall and nearly as broad across the shoulders often have uneventful journeys. People jump out at them from behind rocks then say things like, "Oh. Sorry. I thought you were someone else." -- Carrot travels to Ankh-Morpork (Terry Pratchett, Guards! Guards!)
He nodded to the troll which was employed by the Drum as a splatter [footnote: Like a bouncer, but trolls use more force]. -- Nobby takes Carrot for a drink in The Mended Drum (Terry Pratchett, Guards! Guards!)
It was possibly the most circumspect advance in the history of military manoeuvres, right down at the bottom end of the scale that things like the Charge of the Light Brigade are at the top of. -- The City Watch takes action (Terry Pratchett, Guards! Guards!)
Lady Ramkin's bosom rose and fell like an empire. -- (Terry Pratchett, Guards! Guards!)
It's a metaphor of human bloody existence, a dragon. And if that wasn't bad enough, it's also a bloody great hot flying thing. -- Captain Vimes ponders his problems (Terry Pratchett, Guards! Guards!)
The three rules of the Librarians of Time and Space are: 1) Silence; 2) Books must be returned no later than the date last shown; and 3) Do not interfere with the nature of causality. -- (Terry Pratchett, Guards! Guards!)
A number of religions in Ankh-Morpork still practiced human sacrifice, except that they didn't really need to practice any more because they had got so good at it. -- (Terry Pratchett, Guards! Guards!)
Thunder rolled. ... It rolled a six. -- (Terry Pratchett, Guards! Guards!)
"Right, you bastards, you're... you're geography" -- (Terry Pratchett, Guards! Guards!)
"The significant owl hoots in the night." -- (Terry Pratchett, Guards! Guards!)
"Real children don't go hoppity-skip unless they are on drugs." -- Susan, the ultimate sensible governess (Terry Pratchett, Hogfather)
Getting an education was a bit like a communicable sexual disease. It made you unsuitable for a lot of jobs and then you had the urge to pass it on. -- (Terry Pratchett, Hogfather)
She'd become a governess. It was one of the few jobs a known lady could do. And she'd taken to it well. She'd sworn that if she did indeed ever find herself dancing on rooftops with chimney sweeps she'd beat herself to death with her own umbrella. -- (Terry Pratchett, Hogfather)
"ER...HO. HO. HO." -- Death makes a career move (Terry Pratchett, Hogfather)
Biers was where the undead drank. And when Igor the barman was asked for a Bloody Mary, he didn't mix a metaphor. -- (Terry Pratchett, Hogfather)
"Did you check the list?" YES. TWICE. ARE YOU SURE THAT'S ENOUGH? -- He's gonna find out... (Terry Pratchett, Hogfather)
"That statement is either so deep it would take a lifetime to fully comprehend every particle of its meaning, or it is a load of absolute tosh. Which is it, I wonder?" -- (Terry Pratchett, Hogfather)
"Real stupidity beats artificial intelligence every time." -- Bursar 1 - Hex 0 (Terry Pratchett, Hogfather)
*Glingleglingleglingle.* -- (Terry Pratchett, Hogfather)
Everything starts somewhere, though many physicists disagree. But people have always been dimly aware of the problem with the start of things. They wonder how the snowplough driver gets to work, or how the makers of dictionaries look up the spelling of words. -- (Terry Pratchett, Hogfather)
We took pity on him because he'd lost both parents at an early age. I think that, on reflection, we should have wondered a bit more about that. -- Lord Downey reflects on Mister Teatime (Terry Pratchett, Hogfather)
It's a sad and terrible thing that high-born folk really have thought that the servants would be totally fooled if spirits were put into decanters that were cunningly labelled *backwards*. And also throughout history the more politically conscious butler has taken it on trust, and with rather more justification, that his employers will not notice if the whisky is topped up with eniru. -- (Terry Pratchett, Hogfather)
The truth may be out there, but lies are inside your head. -- (Terry Pratchett, Hogfather)
+++ Divide By Cucumber Error. Please Reinstall Universe And Reboot +++ -- (Terry Pratchett, Hogfather)
"Millennium hand and shrimp." -- (Terry Pratchett, Hogfather)
Rincewind could scream for mercy in nineteen languages, and just scream in another forty-four. -- (Terry Pratchett, Interesting Times)
After the stampede the artist Three Solid Frogs got to his feet, retrieved his brush from his nostril, pulled his easel out of a tree, and tried to think placid thoughts. -- (Terry Pratchett, Interesting Times)
Just because it's not nice doesn't mean it's not miraculous. -- (Terry Pratchett, Interesting Times)
++?????++ Out of Cheese Error. Redo From Start. -- (Terry Pratchett, Interesting Times)
"Luck is my middle name," said Rincewind, indistinctly. "Mind you, my first name is Bad." -- (Terry Pratchett, Interesting Times)
Natural selection saw to it that professional heroes who at a crucial moment tended to ask themselves questions like "What is my purpose in life?" very quickly lacked both. -- (Terry Pratchett, Interesting Times)
"Stercus, stercus, stercus, moriturus sum." -- (Terry Pratchett, Interesting Times)
The Emperor had all the qualifications for a corpse except, as it were, the most vital one. -- (Terry Pratchett, Interesting Times)
"I know about people who talk about suffering for the common good. It's never bloody them! When you hear a man shouting "Forward, brave comrades!" you'll see he's the one behind the bloody big rock and the one wearing the only really arrow-proof helmet!" -- Rincewind gives a speech on politics. (Terry Pratchett, Interesting Times)
Many an ancient lord's last words had been, "You can't kill me because I've got magic aaargh." -- Magic armour is not all it's cracked up to be. (Terry Pratchett, Interesting Times)
[...] Vimes's grin was as funny as the one that moves very fast towards drowning men. And has a fin on top. -- (Terry Pratchett, Jingo)
"Taxation, gentlemen, is very much like dairy farming. The task is to extract the maximum amount of milk with the minimum of moo. And I am afraid to say that these days all I get is moo." -- (Terry Pratchett, Jingo)
She sighed again. She was familiar with the syndrome. They *said* they wanted a soulmate and helpmeet but sooner or later the list would include a skin like silk and a chest fit for a herd of cows. -- (Terry Pratchett, Jingo)
One of the universal rules of happiness is: always be wary of any helpful item that weighs less than its operating manual. -- (Terry Pratchett, Jingo)
"Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life." -- (Terry Pratchett, Jingo)
"*Veni, vici*...Vetinari." -- (Terry Pratchett, Jingo)
And there was nothing finer than a wizard dressed up formally, until someone could find a way of inflating a Bird of Paradise, possibly by using an elastic band and some kind of gas. -- (Terry Pratchett, Jingo)
"'Chapter Fifteen, Elementary Necromancy'", she read out loud. "'Lesson One: Correct Use of Shovel...'" -- (Terry Pratchett, Jingo)
"One o'clock pee em! Hello, Insert Name Here!" -- The Dis-organizer (Terry Pratchett, Jingo)
He had the look of a lawn mower just after the grass had organised a workers' collective. There was a definite suggestion that, deep inside, he knew this was not really happening. It could not be happening because this sort of thing did not happen. Any contradictory evidence could be safely ignored. -- (Terry Pratchett, Jingo)
It was so much easier to blame it on Them. It was bleakly depressing to think that They were Us. If it was Them, then nothing was anyone's fault. If it was us, what did that make Me? After all, I'm one of Us. I must be. I've certainly never thought of myself as one of Them. *No one* ever thinks of themselves as one of Them. We're always one of Us. It's Them that do the bad things. -- (Terry Pratchett, Jingo)
Bigmac wasn't an athlete. If there was an Olympic Sick Note event, he would've won the 100 metres I've Got Asthma, the half marathon Lurk in the Changing Rooms, and the freestyle Got to Go to the Doctor. -- (Terry Pratchett, Johnny and the Bomb)
"Is that *you*?" said a female voice. It wasn't exactly an unpleasant one, but it had a sharp penetrating quality. It seemed to be saying that if you *weren't* you, then it was *your* fault. Johnny recognized it instantly. It was the voice of someone who dialled the wrong numbers and then complained that the phone was answered by people she didn't want to speak to. -- (Terry Pratchett, Johnny and the Bomb)
When all else failed, she tried being reasonable. -- (Terry Pratchett, Johnny and the Bomb)
"You're not allowed to call them dinosaurs anymore." said Yo-less. "It's speciesist. You have to call them pre-petroleum persons." -- (Terry Pratchett, Johnny and the Bomb)
*No-one* liked the Joshua N'Clement block. There were two schools of thought about what should be done with it. The people who lived there thought everyone should be taken out en then the block should be blown up, and the people who lived *near* the block just wanted it blown up. -- (Terry Pratchett, Johnny and the Dead)
There was a new library in the Civic Centre. It was so new it didn't even have librarians. It had Assistant Information Officers. -- (Terry Pratchett, Johnny and the Dead)
Johnny had seen films of American shopping malls. They must have different sorts of people in America, he'd thought. They all looked cool, all the girls were beautiful, and the place wasn't crowded with little kamikaze grandmothers. -- (Terry Pratchett, Johnny and the Dead)
"You've got a lot of time for abstract thought when you've got your hand stuck up a dead badger." -- (Terry Pratchett, Johnny and the Dead)
The figures the telescope was producing were all that was left of an exploding star twenty million years ago. A billion small rubbery things on two planets who had been getting on with life in a quiet sort of way had been totally destroyed, but they were certainly helping Adrian get his Ph.D. and, who knows, they might have thought it all worthwhile if anyone had asked them. -- (Terry Pratchett, Johnny and the Dead)
"I believe it's very hard to have fun in Iceland without fish being involved in some way." -- Looking for a good place to party (Terry Pratchett, Johnny and the Dead)
- "America?" said Mrs Liberty. "Won't we get scalped?" - "Good grief, no!" said William Stickers, who was a bit more up to date about the world. - "*Probably* not," said Mr Fletcher, who had been watching the news lately and was even more up to date than William Stickers. -- Still looking for a good place to party (Terry Pratchett, Johnny and the Dead)
Sergeant Comely was working on the general assumption that where you got lots of people gathered together, something illegal was bound to happen sooner or later. -- (Terry Pratchett, Johnny and the Dead)
Mrs. Nugent was the Johnson's next door neighbour, and known to be unreasonable on subjects like Madonna played at full volume at 3 a.m. -- (Terry Pratchett, Johnny and the Dead)
"Why bother with such a big stone arch?" "It's just showing off. There's probably a sticker on the back saying 'My Other Grave Is A Porch'". -- (Terry Pratchett, Johnny and the Dead)
Granddad was superstitious about books. He thought that if you had enough of them around, education leaked out, like radioactivity. -- (Terry Pratchett, Johnny and the Dead)
"Are you a physicist?" "Me? I don't know anything about science!" "Marvellous! Ideal qualification!" -- (Terry Pratchett, Johnny and the Dead)
Suicide was against the law. Johnny had wondered why. It meant that if you missed, or the gas ran out, or the rope broke, you could get locked up in prison to show you that life was really very jolly and thoroughly worth living. -- (Terry Pratchett, Johnny and the Dead)
"Oook!"
"Oook?"
I MUST SAY THESE ARE VERY GOOD BISCUITS. HOW DO THEY GET THE BITS OF CHOCOLATE IN? -- Death has a snack (Terry Pratchett, Lords and Ladies)
Nanny Ogg never did any housework herself, but she was the cause of housework in other people. -- (Terry Pratchett, Lords and Ladies)
Verence would rather cut his own leg off than put a witch in prison, since it'd save trouble in the long run and probably be less painful. -- (Terry Pratchett, Lords and Ladies)
I LIKE TO THINK I AM A PICKER-UP OF UNCONSIDERED TRIFLES. Death grinned hopefully. -- (Terry Pratchett, Lords and Ladies)
Mustrum Ridcully did a lot for rare species. For one thing, he kept them rare. -- (Terry Pratchett, Lords and Ladies)
Using a metaphor in front of a man as unimaginative as Ridcully was like a red flag to a bu-- was like putting something very annoying in front of someone who was annoyed by it. -- (Terry Pratchett, Lords and Ladies)
The thing about iron is that you generally don't have to think fast in dealing with it. -- (Terry Pratchett, Lords and Ladies)
Nanny Ogg looked under her bed in case there was a man there. Well, you never knew your luck. -- (Terry Pratchett, Lords and Ladies)
The chieftain had been turned into a pumpkin although, in accordance with the rules of universal humour, he still had his hat on. -- (Terry Pratchett, Lords and Ladies)
For Magrat, stepping into a man's bedroom was like an explorer stepping on to that part of the map marked Here Be Dragons. -- (Terry Pratchett, Lords and Ladies)
"I wants your body, Mrs Ogg." -- Casanunda makes his move (Terry Pratchett, Lords and Ladies)
"I know she's in there," said Verence, holding his crown in his hands in the famous Ai-Senor-Mexican-Bandits-Have-Raided-Our-Village position. -- (Terry Pratchett, Lords and Ladies)
In fact, the mere act of opening the box will determine the state of the cat, although in this case there were three determinate states the cat could be in: these being Alive, Dead, and Bloody Furious. -- Schrodinger's Moggy explained (Terry Pratchett, Lords and Ladies)
The shortest unit of time in the multiverse is the New York Second, defined as the period of time between the traffic lights turning green and the cab behind you honking. -- (Terry Pratchett, Lords and Ladies)
"Serve 'em right for not inviting me to their weddings." -- Ridcully contemplates the Trousers of Time (Terry Pratchett, Lords and Ladies)
"Hah, I can just see a real playsmith putting *donkeys* in a play!" -- (Terry Pratchett, Lords and Ladies)
"Do not meddle in the affairs of wizards, especially simian ones. They are not all that subtle." -- (Terry Pratchett, Lords and Ladies)
"Go ahead, bake my quiche" -- Magrat instructs the castle cook (Terry Pratchett, Lords and Ladies)
In the Beginning there was nothing, which exploded. -- (Terry Pratchett, Lords and Ladies)
Remember, A Dragon is For Life, Not Just for Hogswatchnight -- Motto of The Sunshine Home for Sick Dragons in Morphic Street, Please Leave Donations of Coal by Side Door. (Terry Pratchett, Lords and Ladies)
The place looked as though it had been visited by Gengiz Cohen [footnote: hence the term "wholesale destruction"]. -- (Terry Pratchett, Lords and Ladies)
"This is a lovely party," said the Bursar to a chair, "I wish I was here." -- The Bursar is a man under a *lot* of stress (Terry Pratchett, Lords and Ladies)
No matter what she did with her hair it took about three minutes for it to tangle itself up again, like a garden hosepipe in a shed [Which, no matter how carefully coiled, will always uncoil overnight and tie the lawnmower to the bicycles]. -- (Terry Pratchett, Lords and Ladies)
He married that Palliard girl, remember? The one with the air-cooled teeth? -- (Terry Pratchett, Lords and Ladies)
And the child had a permanently runny nose and ought to be provided with a handkerchief or, failing that, a cork. -- (Terry Pratchett, Lords and Ladies)
It was here that the thaum, hitherto believed to be the smallest possible particle of magic, was succesfully demonstrated to be made up of /resons/ (Lit.: 'Thing-ies') or reality fragments. Currently research indicates that each reson is itself made up of a combination of at least five 'flavours', known as 'up', 'down', 'sideways', 'sex appeal' and 'peppermint'. -- (Terry Pratchett, Lords and Ladies)
A heap of discarded garments by the bed suggested that Verence had mastered the art of hanging up clothes as practised by half the population of the world, and that he had equally had difficulty with the complex topological manoeuvres necessary to turn the socks the right way out. -- (Terry Pratchett, Lords and Ladies)
Chain-mail isn't much defence against an arrow. It certainly isn't when the arrow is being aimed between your eyes. -- (Terry Pratchett, Lords and Ladies)
It's not enough to be able to pick up a sword. You have to know which end to poke into the enemy. -- (Terry Pratchett, Lords and Ladies)
The Monks of Cool, whose tiny and exclusive monastery is hidden in a really cool and laid-back valley in the lower Ramtops, have a passing-out test for a novice. He is taken into a room full of all types of clothing and asked: Yo, my son, which of these is the most stylish thing to wear? And the correct answer is: Hey, whatever I select. -- (Terry Pratchett, Lords and Ladies)
The person on the other side was a young woman. Very obviously a young woman. There was no possible way that she could have been mistaken for a young man in any language, especially Braille. -- The goddess with the nice earrings (Terry Pratchett, Maskerade)
Nanny Ogg found herself embarrassed to even think about this, and this was unusual because embarrassment normally came as naturally to Nanny as altruism comes to a cat. -- (Terry Pratchett, Maskerade)
People who didn't need people needed people around to know that they were the kind of people who didn't need people. -- (Terry Pratchett, Maskerade)
He had a unique stride: it looked as though his body was being dragged forward and his legs had to flail around underneath it, landing wherever they could find room. It wasn't so much a walk as a collapse, indefinitely postponed. -- (Terry Pratchett, Maskerade)
She'd even given herself a middle initial - X - which stood for "someone who has a cool and exciting middle name". -- (Terry Pratchett, Maskerade)
"What sort of person," said Salzella patiently, "sits down and *writes* a maniacal laugh? And all those exclamation marks, you notice? Five? A sure sign of someone who wears his underpants on his head. Opera can do that to a man." -- (Terry Pratchett, Maskerade)
"The singers all loathe the sight of one another, the chorus despises the singers, they both hate the orchestra, and everyone fears the conductor; the staff on one prompt side won't talk to the staff on the opposite prompt side, the dancers are all crazed from hunger in any case..." -- (Terry Pratchett, Maskerade)
Most people in Lancre, as the saying goes, went to bed with the chickens and got up with the cows. [footnote: Er. That is to say, they went to bed at the same time as the chickens went to bed, and got up at the same time as the cows got up. Loosely worded sayings can really cause misunderstandings.] -- (Terry Pratchett, Maskerade)
"...my father is the Emperor of Klatch and my mother is a small tray of raspberry puddings." -- Agnes tells Christine about herself (Terry Pratchett, Maskerade)
Instead, people would take pains to tell her that beauty was only skin-deep, as if a man ever fell for an attractive pair of kidneys. -- (Terry Pratchett, Maskerade)
A day ago the future had looked aching and desolate, and now it looked full of surprises and terror and bad things happening to people... If she had anything to do with it anyway. -- Granny Weatherwax commits optimism (Terry Pratchett, Maskerade)
- "There have been...accidents." - "What kind of accidents?" - "The kind of accidents you prefer to call...accidents." -- (Terry Pratchett, Maskerade)
- "But I don't *believe* in reincarnation!" he protested. - SQUEAK. - And this, Mr Pounder understood with absolute rodent clarity, meant: Reincarnation believes in *you*. -- (Terry Pratchett, Maskerade)
It was done far more often than the audiences ever realized -- when singers had a sore throat, or had completely dried, or had turned up so drunk they could barely stand, or, in one notorious instance many years previously, had died in the interval and subsequently sung their famous aria by means of a broom-handle stuck up their back and their jaw operated with a piece of string. -- (Terry Pratchett, Maskerade)
After you'd known Christine for any length of time, you found yourself fighting a desire to look into her ear to see if you could spot daylight coming the other way. -- (Terry Pratchett, Maskerade)
The pre-luncheon drinks were going quite well, Mr Bucket thought. Everyone was making polite conversation and absolutely no one had been killed up to the present moment. -- (Terry Pratchett, Maskerade)
Nanny could get a statue to cry on her shoulder and say what it really thought about pigeons. -- (Terry Pratchett, Maskerade)
Greebo could, in fact, commit sexual harrassment simply by sitting very quietly in the next room. -- (Terry Pratchett, Maskerade)
It is the fate of all banisters worth sliding down that there is something nasty waiting at the far end. -- (Terry Pratchett, Maskerade)
If the Creator had said, "Let there be light" in Ankh-Morpork, he'd have gotten no further because of all the people saying "What colour?" -- (Terry Pratchett, Men at Arms)
From the back, Vetinari looked like a carnivorous flamingo. -- (Terry Pratchett, Men at Arms)
Cuddy had only been a guard for a few days, but already he had absorbed one important and basic fact: it is almost impossible for anyone to be in a street without breaking the law. -- (Terry Pratchett, Men at Arms)
The Battle of Koom Valley is the only one known to history where both sides ambushed each other. -- (Terry Pratchett, Men at Arms)
Carrot was two metres tall but he'd been brought up as a dwarf, and then further up as a human. -- (Terry Pratchett, Men at Arms)
"Young Edward thinks that there is no lake of blood too big to wade through to put a rightful king on a throne, no deed too base in defence of a crown. A romantic, in fact." -- Lord Rust describing Edward d'Eath (Terry Pratchett, Men at Arms)
The Ramkins were more highly bred than a hilltop bakery, whereas Corporal Nobbs had been disqualified from the human race for shoving. -- (Terry Pratchett, Men at Arms)
He was said to have the body of a twenty-five year old, although no one knew where he kept it. -- The Life and Times of Corporal Nobbs (Terry Pratchett, Men at Arms)
"Pride is all very well, but a sausage is a sausage." -- Gaspode, of course (Terry Pratchett, Men at Arms)
The river Ankh is probably the only river in the universe on which the investigators can chalk the outline of the corpse. -- (Terry Pratchett, Men at Arms)
The Alchemist's Guild is opposite the Gambler's Guild. Usually. Sometimes it's above it, or below it, or falling in bits around it. -- (Terry Pratchett, Men at Arms)
Sham Harga had run a succesful eatery for many years by always smiling, never extending credit, and realizing that most of his customers wanted meals properly balanced between the four food groups: sugar, starch, grease and burnt crunchy bits. -- (Terry Pratchett, Men at Arms)
The Patrician relaxed, in a way which only then drew gentle attention to the foregoing moment of tension. -- (Terry Pratchett, Men at Arms)
Sometimes it's better to light a flamethrower than curse the darkness. -- (Terry Pratchett, Men at Arms)
Being a werewolf meant having the dexterity and jaw power to instantly rip out a man's jugular. It was a trick of her father's that had always annoyed her mother, especially when he did it just before meals. -- (Terry Pratchett, Men at Arms)
The door was still ajar, but there was a tentative tap on it which said, in a kind of metaphorical morse code, that the tapper could see very well that Carrot was in his room with a scantily clad woman and was trying to knock without actually being heard. -- (Terry Pratchett, Men at Arms)
"It's got three keyboards and a hundred extra knobs, including twelve with '?' on them." -- The Unseen University Organ, as designed by B. S. Johnson (Terry Pratchett, Men at Arms)
- BIG FIDO? - "Yes?" - HEEL. -- (Terry Pratchett, Men at Arms)
The Librarian of Unseen University had unilaterally decided to aid comprehension by producing an Orang-utan/Human Dictionary. He'd been working on it for three months. It wasn't easy. He'd got as far as "Oook". -- (Terry Pratchett, Men at Arms)
- "It could be a torture chamber or a dungeon or a hideous pit or anything!" - "It's just a student's bedroom, sergeant." - "You see?" -- (Terry Pratchett, Men at Arms)
The maze was so small that people got lost looking *for* it. -- Bloody Stupid Johnson at his finest (Terry Pratchett, Men at Arms)
"It would seem that you have no useful skill or talent whatsoever," he said. "Have you thought of going into teaching?" -- (Terry Pratchett, Mort)
Only one creature could have duplicated the expressions on their faces, and that would be a pigeon who has heard not only that Lord Nelson has got down off his column but has also been seen buying a 12-bore repeater and a box of cartridges. -- (Terry Pratchett, Mort)
- "My granny says that dying is like going to sleep," Mort added, a shade hopefully. - I WOULDN'T KNOW. I HAVE DONE NEITHER. -- (Terry Pratchett, Mort)
- "Pardon me for living, I'm sure." - NO-ONE GETS PARDONED FOR LIVING. -- (Terry Pratchett, Mort)
Although the scythe isn't pre-eminent among the weapons of war, anyone who has been on the wrong end of, say, a peasants' revolt will know that in skilled hands it is fearsome. -- (Terry Pratchett, Mort)
"Do not meddle in the affairs of wizards because a refusal often offends, I read somewhere." -- (Terry Pratchett, Mort)
"When a man is tired of Ankh-Morpork, he is tired of ankle-deep slurry." -- (Terry Pratchett, Mort)
I DON'T KNOW ABOUT YOU, he said, BUT I COULD MURDER A CURRY. -- Death addresses his new apprentice (Terry Pratchett, Mort)
Poets have tried to describe Ankh-Morpork. They have failed. Perhaps it's the sheer zestful vitality of the place, or maybe it's just that a city with a million inhabitants and no sewers is rather robust for poets, who prefer daffodils and no wonder. -- (Terry Pratchett, Mort)
It is a fact that although the Death of the Discworld is, in his own words, an ANTHROPOMORPHIC PERSONIFICATION, he long ago gave up using the traditional skeletal horses, because of the bother of having to stop all the time to wire bits back on. -- (Terry Pratchett, Mort)
"You're dead," he said. Keli waited. She couldn't think of any suitable reply. "I'm not" lacked a certain style, while "Is it serious?" seemed somehow too frivolous. -- Princess Keli in trouble (Terry Pratchett, Mort)
The thing between Death's triumphant digits was a fly from the dawn of time. It was the fly in the primordial soup. It had bred on mammoth turds. It wasn't a fly that bangs on window panes, it was a fly that drills through walls. -- Death goes fishing (Terry Pratchett, Mort)
Ankh-Morpork had dallied with many forms of government and had ended up with that form of democracy known as One Man, One Vote. The Patrician was the Man; he had the Vote. -- Discworld politics explained (Terry Pratchett, Mort)
- I USHERED SOULS INTO THE NEXT WORLD. I WAS THE GRAVE OF ALL HOPE. I WAS THE ULTIMATE REALITY. I WAS THE ASSASSIN AGAINST WHOM NO LOCK WOULD HOLD. - "Yes, point taken, but do you have any particular skills?" -- Death consults a job broker (Terry Pratchett, Mort)
- "Sodomy non sapiens," said Albert under his breath. - "What does that mean?" - "Means I'm buggered if I know." -- Mort and Albert are facing a problem (Terry Pratchett, Mort)
Women's clothes were not a subject that preoccupied Cutwell much -- in fact, usually when he thought about women his mental pictures seldom included any clothes at all -- but the vision in front of him really did take his breath away. -- Princess Keli prepares for her coronation (Terry Pratchett, Mort)
"You won't get away with this," said Cutwell. He thought for a bit and added, "Well, you will probably get away with it, but you'll feel bad about it on your deathbed and you'll wish -- " He stopped talking. -- Cutwell tries to reason with the Duke of Sto Helit (Terry Pratchett, Mort)
"What do people like to drink here, then?" The landlord looked sideways at his customers, a clever trick given that they were directly in front of him. -- Mort goes out for a drink (Terry Pratchett, Mort)
"You like it?" he said to Mort, in pretty much the same tone of voice people used when they said to St George, "You killed a *what*?" -- Mort tastes scrumble for the first time (Terry Pratchett, Mort)
The Librarian had seen many weird things in his time, but that had to be the 57th strangest. [footnote: he had a tidy mind] -- (Terry Pratchett, Moving Pictures)
"Woof bloody woof." -- Gaspode the Wonder Dog (Terry Pratchett, Moving Pictures)
No-one would have believed, in the final years of the Century of the Fruitbat, that Discworld affairs were being watched keenly and impatiently by intelligences greater than Man's, or at least much nastier; that their affairs were being scrutinised and studied as a man with a three-day appetite might study the All-You-Can-Gobble-For-A-Dollar menu outside Harga's House of Ribs... -- (Terry Pratchett, Moving Pictures)
It was the sort of thing you expected in the Street of Alchemists. The neighbours *preferred* explosions, which were at least identifiable and soon over. They were better than the smells, which crept up on you. -- (Terry Pratchett, Moving Pictures)
"Meat pies! Hot sausages! Inna bun! So fresh the pig h'an't noticed they're gone!" -- Genuine pig portion packages (Terry Pratchett, Moving Pictures)
The Archchancellor's most important job, as the Bursar saw it, was to sign things, preferably, from the Bursar's point of view, without reading them first. -- Middle management explained (Terry Pratchett, Moving Pictures)
By and large, the only skill the alchemists of Ankh-Morpork had discovered so far was the ability to turn gold into less gold. -- (Terry Pratchett, Moving Pictures)
Most alchemists were nervous, in any case; it came from not knowing what the crucible of bubbling stuff they were experimenting with was going to do next. -- (Terry Pratchett, Moving Pictures)
"If you put butter and salt on it, it tastes like salty butter." -- Popcorn comes to the Discworld (Terry Pratchett, Moving Pictures)
"Students?" barked the Archchancellor. "Yes, Master. You know? They're the thinner ones with the pale faces? Because we're a *university*? They come with the whole thing, like rats --" -- (Terry Pratchett, Moving Pictures)
Of course, it is very important to be sober when you take an exam. Many worthwhile careers in the street-cleansing, fruit-picking and subway-guitar-playing industries have been founded on a lack of understanding of this simple fact. -- (Terry Pratchett, Moving Pictures)
And then you bit onto them, and learned once again that Cut-me-own-Throat Dibbler could find a use for bits of an animal that the animal didn't know it had got. Dibbler had worked out that with enough fried onions and mustard people would eat *anything*. -- A fact McDonalds knows about as well (Terry Pratchett, Moving Pictures)
"The thing is that Mr. Dibbler can even sell sausages to people who have bought them off him *before*." -- Now *that's* marketing (Terry Pratchett, Moving Pictures)
- "You pay for it before you eat it? What happens if it's dreadful?" - "That's why." -- (Terry Pratchett, Moving Pictures)
"One minute I'm just another rabbit and happy about it, next minute *whazaam*, I'm thinking. That's a major drawback if you're looking for happiness as a rabbit, let me tell you. You want grass and sex, not thoughts like 'What's it all about, when you get right down to it?'" -- (Terry Pratchett, Moving Pictures)
"I'm a cat person, myself," she said, vaguely. A low-level voice said: "Yeah? Yeah? Wash in your own spit, do you?" -- It's a dog's life (Terry Pratchett, Moving Pictures)
"Why's it called Ming?" said the Archchancellor, on cue. The Bursar tapped the pot. It went *ming*. -- Discworld archeology revealed (Terry Pratchett, Moving Pictures)
- "I thought swords had to be straight." - "Perhaps they start out straight and go bendy with use. A lot of things do." -- Odour-eaters, right? Yeah, she means odour-eaters. (Terry Pratchett, Moving Pictures)
Azhural raised his staff. "It's fifteen hundred miles to Ankh-Morpork," he said. "We've got three hundred and sixty-three elephants, fifty carts of forage, the monsoon's about to break and we're wearing... we're wearing... sort of things, like glass, only dark... dark glass things on our eyes..." -- (Terry Pratchett, Moving Pictures)
People who used magic without knowing what they were doing usually came to a sticky end. All over the entire room, sometimes. -- (Terry Pratchett, Moving Pictures)
"He's in love," said Gaspode. "It's very tricky." "Yeah, I know how it is," said the cat sympathetically. "People throwing old boots and things at you." -- (Terry Pratchett, Moving Pictures)
"In a word -- im-possible!" "That's two words," said Dibbler. -- (Terry Pratchett, Moving Pictures)
"I'm vice-president of Throwing Out People Mr Dibbler Doesn't like the Face of." -- (Terry Pratchett, Moving Pictures)
- "It looks worse than you can imagine!" - "I can imagine some pretty bad things!" - "That's why I said *worse*!" -- (Terry Pratchett, Moving Pictures)
"Woof?" -- (Terry Pratchett, Moving Pictures)
"Could have bin worse, mister. I could have said 'miaow'." -- Our hero meets Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"Worlds only harmonica-playing dog. Tuppence." -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"Seems I can't get me 'ead down these days without rescuin' people or foilin' robbers or sunnink." -- It's a wonder dog's life (Terry Pratchett, Moving Pictures)
"How's he in the mysterious senses department?" -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"Walk a mile on these paws and call me a liar." -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"Woof. In tones of low menace." -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"There's nothin' wrong with bein' a son of a bitch." -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"I thought it was going to be bucket-of-water time myself." -- Gaspode's way of saying "I'm sorry, was I intruding?" (Terry Pratchett, Moving Pictures)
"I can explain it in Dog, but you only listen in Human." -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"I mean are we talking Thicko City here or what?" -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"I wouldn't give it to a dog, and I *am* one." -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"Does that look like ten per cent to you, Victor?" -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"... Percy the Pup here with a cold nose, bright eyes, glossy coat and the brains of a stunned herring." -- Gaspode the wonder dog on 'Laddie' (Terry Pratchett, Moving Pictures)
"Maybe you should loosen her clothing or something." -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"I could send you a bone with a file in it, only you'd eat it." -- (Terry Pratchett, Moving Pictures)
"Well, 'scuse me. I was jus' tryin' to save the world." -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"If gharstely creatures from before the Dawna Time starts wavin' at you from under your bed, jus' you don't come complainin' to me," -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"Idiot I may be, but tied up I ain't." -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"And Howondaland Smith, Balrog Hunter, practic'ly eats the dark for his tea." -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"Messin' around with girls in thrall to Creatures from the Void never works out, take my word for it." -- Gaspode gives Victor some practical advice (Terry Pratchett, Moving Pictures)
"Growl, growl." -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"I can bite your leg if you like." -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"I expect I've saved the day, right?" -- Gaspode the wonder dog (Terry Pratchett, Moving Pictures)
"Can't sing. Can't dance. Can handle a sword a little." -- Victor's resume (Terry Pratchett, Moving Pictures)
"Did I hear things, or can that little dog speak?" said Dibbler. "He says he can't," said Victor. Dibbler hesitated. "Well," he said, "I suppose he should know." -- Dibbler meets Gaspode the Wonder Dog (Terry Pratchett, Moving Pictures)
In retrospect, Victor was always a little unclear about those next few minutes. That's the way it goes. The moments that change your life are the ones that happen suddenly, like the one where you die. -- (Terry Pratchett, Moving Pictures)
"When Mister Safety Catch Is Not On, Mister Crossbow Is Not Your Friend." -- Detritus learns about weapons safety (Terry Pratchett, Night Watch)
He hated being thought of as one of those people that wore stupid ornamental armour. It was gilt by association. -- (Terry Pratchett, Night Watch)
"Don't put your trust in revolutions. They always come around again. That's why they're called revolutions. People die, and nothing changes." -- (Terry Pratchett, Night Watch)
The Assassin moved quietly from roof to roof until he was well away from the excitement around the Watch House.
His movements could be called cat-like, except that he did not stop to spray urine up against things. -- (Terry Pratchett, Night Watch)
No-one could sit in that chair. It was full of old T-shirts and books and supper plates and junk. There was a deep sock layer and possibly the Lost Strawberry Yoghurt. No-one could sit down there without special equipment. -- (Terry Pratchett, Only You Can Save Mankind)
One day, if he could master GCSE maths and reliably pick up a soldering iron by the end that wasn't hot, he was going to be a Big Man in computers. -- (Terry Pratchett, Only You Can Save Mankind)
"I saw a film where there was an alien crawling around inside a spaceship's air ducts and it could come out wherever it liked," said Johnny reproachfully. "Doubtless it had a map," said the Captain. -- (Terry Pratchett, Only You Can Save Mankind)
Basically, there were two sides to the world. There was the entire computer games software industry engaged in a tremendous effort to stamp out piracy, and there was Wobbler. Currently, Wobbler was in front. -- (Terry Pratchett, Only You Can Save Mankind)
Wobbler thought that California was where good people went when they died. -- (Terry Pratchett, Only You Can Save Mankind)
If Not You, Who Else? -- (Terry Pratchett, Only You Can Save Mankind)
"We got a talk about it at school. There's lots of stuff most girls can't do, but you've got to pretend they can, so that more of them will." -- Sexism explained (Terry Pratchett, Only You Can Save Mankind)
Bigmac's brother was reliably believed to be in the job of moving video recorders around in an informal way. -- (Terry Pratchett, Only You Can Save Mankind)
He microwaved himself something called a Pour-On Genuine Creole Lasagne, which said it served four portions. It did if you were dwarfs. -- (Terry Pratchett, Only You Can Save Mankind)
On Earth, No-one Can Hear You Say "Um". -- (Terry Pratchett, Only You Can Save Mankind)
"Stuck? You're an *alien*," said Johnny. "Aliens don't get stuck in air ducts. It's practically a well-known fact." -- (Terry Pratchett, Only You Can Save Mankind)
"If we find a cat I'm going to kick it!" -- (Terry Pratchett, Only You Can Save Mankind)
What our ancestors would really be thinking, if they were alive today, is: "Why is it so dark in here?" -- (Terry Pratchett, Pyramids)
All assassins had a full-length mirror in their rooms, because it would be a terrible insult to anyone to kill them when you were badly dressed. -- (Terry Pratchett, Pyramids)
"Ibid you already know." -- More Discworld philosophers (Terry Pratchett, Pyramids)
The Ephebians made wine out of anything they could put in a bucket, and ate anything that couldn't climb out of one. -- (Terry Pratchett, Pyramids)
Nature abhors dimensional abnormalities, and seals them neatly away so that they don't upset people. Nature, in fact, abhors a lot of things, including vacuums, ships called the "Marie Celeste", and the chuck keys for electric drills. -- (Terry Pratchett, Pyramids)
There was not a lot that could be done to make Morpork a worse place. A direct hit by a meteorite, for example, would count as gentrification. -- (Terry Pratchett, Pyramids)
- "Therefore I will have dinner sent in," said the priest. "It will be roast chicken." - "I hate chicken." Dios smiled. "No sire. On wednesdays the King always enjoys chicken, sire." -- (Terry Pratchett, Pyramids)
"What is this thing, anyway?" said the Dean, inspecting the implement in his hands. "It's called a shovel," said the Senior Wrangler. "I've seen the gardeners use them. You stick the sharp end in the ground. Then it gets a bit technical." -- (Terry Pratchett, Reaper Man)
No one was avoiding him, it was just that an apparent random Brownian motion was gently moving everyone away. -- (Terry Pratchett, Reaper Man)
"Dock-a-loodle-fod!" -- Dyslexic roosters are a sad sight (Terry Pratchett, Reaper Man)
People have believed for hundreds of years that newts in a well mean that the water's fresh and drinkable, and *in all that time* never asked themselves whether the newts got out to go to the lavatory. -- (Terry Pratchett, Reaper Man)
He'd never realized that, deep down inside, what he really wanted to do was make things go splat. -- (Terry Pratchett, Reaper Man)
DROP THE SCYTHE, AND TURN AROUND SLOWLY. -- Dirty Death (Terry Pratchett, Reaper Man)
Five exclamation marks, the sure sign of an insane mind. -- (Terry Pratchett, Reaper Man)
It is traditional, when loading wire trolleys, to put the most fragile items at the bottom. -- (Terry Pratchett, Reaper Man)
No matter how fast light travels it finds the darkness has always got there first, and is waiting for it. -- (Terry Pratchett, Reaper Man)
I EXPECT, he said, THAT YOU COULD MURDER A PIECE OF CHEESE? -- Death talks to the Death of Rats (Terry Pratchett, Reaper Man)
On the fabled hidden continent of Xxxx, somewhere near the rim, there is a lost colony of wizards who wear corks around their pointy hats and live on nothing but prawns. -- (Terry Pratchett, Reaper Man)
"Bonsai!" -- (Terry Pratchett, Reaper Man)
"Chap with a whip got as far as the big sharp spikes last week," said the low priest. -- Life in the Temple of Offler (Terry Pratchett, Reaper Man)
"You know," said Windle, "it's a wonderful afterlife." -- (Terry Pratchett, Reaper Man)
WHAT CAN THE HARVEST HOPE FOR, IF NOT FOR THE CARE OF THE REAPER MAN? -- Death appeals to Azrael (Terry Pratchett, Reaper Man)
- "Have you any last words?" - YES. I DON'T WANT TO GO. - "Well. Succinct, anyway." -- Death at the other end of the scythe, for once (Terry Pratchett, Reaper Man)
Mrs Evadne Cake was a medium, verging on small. -- (Terry Pratchett, Reaper Man)
"Chain letters," said the Tyrant. "The Chain Letter to the Ephebians. Forget Your Gods. Be Subjugated. Learn to Fear. Do not break the chain -- the last people who did woke up one morning to find fifty thousand armed men on their lawn." -- (Terry Pratchett, Small Gods)
"It's a god-eat-god world." -- (Terry Pratchett, Small Gods)
"You can't trample infidels when you're a tortoise. I mean, all you could do is give them a meaningful look." -- (Terry Pratchett, Small Gods)
"That's right," he said. "We're philosophers. We think, therefore we am." -- (Terry Pratchett, Small Gods)
His philosophy was a mixture of three famous schools -- the Cynics, the Stoics and the Epicureans -- and summed up all three of them in his famous phrase, "You can't trust any bugger further than you can throw him, and there's nothing you can do about it, so let's have a drink." -- We meet Dydactylos the philosopher (Terry Pratchett, Small Gods)
Dhblah sidled closer. This was not hard. Dhblah sidled everywhere. *Crabs* thought he walked sideways. -- (Terry Pratchett, Small Gods)
One day a tortoise will learn how to fly. -- (Terry Pratchett, Small Gods)
History, contrary to popular theories, *is* kings and dates and battles. -- (Terry Pratchett, Small Gods)
And it came to pass that in time the Great God Om spake unto Brutha, the Chosen One: "Psst!" -- (Terry Pratchett, Small Gods)
Brother Preptil, the master of the music, had described Brutha's voice as putting him in mind of a disappointed vulture arriving too late at the dead donkey. -- (Terry Pratchett, Small Gods)
"There's very good eating on one of these, you know." -- Eyeing the tortoise for tea (Terry Pratchett, Small Gods)
"Pets are always a great help in times of stress. And in times of starvation too, o'course." -- (Terry Pratchett, Small Gods)
Words are the litmus paper of the minds. If you find yourself in the power of someone who will use the word "commence" in cold blood, go somewhere else very quickly. But if they say "Enter", don't stop to pack. -- (Terry Pratchett, Small Gods)
The labyrinth of Ephebe is ancient and full of one hundred and one amazing things you can do with hidden springs, razor-sharp knives, and falling rocks. -- (Terry Pratchett, Small Gods)
"Ah. Philosophy," said Om. -- (Terry Pratchett, Small Gods)
"Not a man to mince words. People, yes. But not words." -- (Terry Pratchett, Small Gods)
SQUEAK. -- The Death of Rats (Terry Pratchett, Small Gods)
Bishops move diagonally. That's why they often turn up where the kings don't expect them to be. -- (Terry Pratchett, Small Gods)
"Eureka," he said. "Going to have a bath then?" -- Philosophy in action (Terry Pratchett, Small Gods)
Cuius testiculos habes, habeas cardia et cerebellum. -- (Terry Pratchett, Small Gods)
"Are you a philosopher? Where's your sponge?" -- (Terry Pratchett, Small Gods)
REMIND ME AGAIN, he said, HOW THE LITTLE HORSE-SHAPED ONES MOVE. -- Death on symbolic last games (Terry Pratchett, Small Gods)
"Go on, do Deformed Rabbit... it's my favourite." -- Shadow puppets are so cute (Terry Pratchett, Small Gods)
"Oh, a very useful philosophical animal, your average tortoise. Outrunning metaphorical arrows, beating hares in races... very handy." -- (Terry Pratchett, Small Gods)
Gravity is a habit that is hard to shake off. -- (Terry Pratchett, Small Gods)
The trouble with being a god is that you've got no one to pray to. -- (Terry Pratchett, Small Gods)
There are hardly any excesses of the most crazed psychopath that cannot easily be duplicated by a normal kindly family man who just comes in to work every day and has a job to do. -- (Terry Pratchett, Small Gods)
The people who really run organizations are usually found several levels down, where it is still possible to get things done. -- (Terry Pratchett, Small Gods)
Guilt was the grease in which the wheels of the authority turned. -- (Terry Pratchett, Small Gods)
Most gods find it hard to walk and think at the same time. -- (Terry Pratchett, Small Gods)
When the least they could do to you was everything, then the most they could do to you suddenly held no terror. -- (Terry Pratchett, Small Gods)
"What's a philosopher ?" said Brutha. "Someone who's bright enough to find a job with no heavy lifting," said a voice in his head. -- (Terry Pratchett, Small Gods)
"Slave is an Ephebian word. In Om we have no word for slave," said Vorbis. "So I understand," said the Tyrant. "I imagine that fish have no word for water." -- (Terry Pratchett, Small Gods)
"He says gods like to see an atheist around. Gives them something to aim at." -- (Terry Pratchett, Small Gods)
"You're not one of us." "I don't think I'm one of them, either," said Brutha. "I'm one of mine." -- (Terry Pratchett, Small Gods)
Simony's eyes gleamed with the gleam of a man who had seen the future and found it covered with armour plating. -- (Terry Pratchett, Small Gods)
"All holy piety in public, and all peeled grapes and self-indulgence in private." -- (Terry Pratchett, Small Gods)
When you can flatten entire cities at a whim, a tendency towards quiet reflection and seeing-things-from-the-other-fellow's-point- of-view is seldom necessary. -- (Terry Pratchett, Small Gods)
"Take it from me, whenever you see a bunch of buggers puttering around talking about truth and beauty and the best way of attacking Ethics, you can bet your sandals it's all because dozens of other poor buggers are doing all the real work around the place." -- (Terry Pratchett, Small Gods)
"Why do you bother with him? He's had thousands of people killed!" "Yes, but perhaps he thought that you wanted it." -- (Terry Pratchett, Small Gods)
The figures looked more or less human. And they were engaged in religion. You could tell by the knives (it's not murder if you do it for a god). -- (Terry Pratchett, Small Gods)
The trouble was that he was talking in philosophy, but they were listening in gibberish. -- (Terry Pratchett, Small Gods)
"He's muffed it," said Simony. "he could have done *anything* with them. And he just told them the facts. You can't inspire people with facts. They need a cause. They need a symbol." -- (Terry Pratchett, Small Gods)
"You can't find a hermit to teach you herming, because of course that rather spoils the whole thing." -- (Terry Pratchett, Small Gods)
Om began to feel the acute depression that steals over every realist in the presence of an optimist. -- (Terry Pratchett, Small Gods)
"All the other prophets came back with commandments!" "Where they get them?" "I ... suppose they made them up." "You get them from the same place." -- (Terry Pratchett, Small Gods)
Brutha tried to nod, and thought: I'm on everyone's side. It'd be nice if, just for once, someone was on mine. -- (Terry Pratchett, Small Gods)
Probably the last man who knew how it worked had been tortured to death years before. Or as soon as it was installed. Killing the creator was a traditional method of patent protection. -- (Terry Pratchett, Small Gods)
Give anyone a lever long enough and they can change the world. It's unreliable levers that are the problem. -- (Terry Pratchett, Small Gods)
"Now we've got a truth to die for!" "No. Men should die for lies. But the truth is too precious to die for." -- (Terry Pratchett, Small Gods)
YOU HAVE PERHAPS HEARD THE PHRASE THAT HELL IS OTHER PEOPLE? "Yes. Yes, of course." Death nodded. IN TIME, he said, YOU WILL LEARN THAT IT IS WRONG. -- (Terry Pratchett, Small Gods)
"I used to think that *I* was stupid, and then I met philosophers." -- (Terry Pratchett, Small Gods)
"I like the idea of democracy. You have to have someone everyone distrusts," said Brutha. "That way, everyone's happy." -- (Terry Pratchett, Small Gods)
Until an unfortunate axe incident, Gloria had been captain of the school basketball team. -- (Terry Pratchett, Soul Music)
The man gave a shrug which indicated that, although the world did indeed have many problems, this was one of them that was not his. -- (Terry Pratchett, Soul Music)
"Scum," said Crash, his voice low with resigned menace, "you've bought a leopard, haven't you?" -- (Terry Pratchett, Soul Music)
"Of course, just because we've heard a spine-chilling, blood-curdling scream of the sort to make your very marrow freeze in your bones doesn't automatically mean there's anything wrong." -- (Terry Pratchett, Soul Music)
It is said that whosoever the gods wish to destroy, they first make mad. In fact, whosoever the gods wish to destroy, they first hand the equivalent of a stick with a fizzing fuse and Acme Dynamite Company written on the side. It's more interesting, and doesn't take so long. -- (Terry Pratchett, Soul Music)
The question seldom addressed is *where* Medusa had snakes. Underarm hair is an even more embarassing problem when it keeps biting the top of the deodorant bottle. -- (Terry Pratchett, Soul Music)
People came to Ankh-Morpork to seek their fortune. Unfortunately, other people sought it too. -- (Terry Pratchett, Soul Music)
The class was learning about some revolt in which some peasants had wanted to stop being peasants and, since the nobles had won, had stopped being peasants *really quickly*. -- (Terry Pratchett, Soul Music)
The hippo of recollection stirred in the muddy waters of the mind. -- (Terry Pratchett, Soul Music)
SNH, SNH, SNH. -- (Terry Pratchett, Soul Music)
Smoke was coming out of the stricken piano. The Librarian's hands were walking through the keys like Casanunda in a nunnery. -- (Terry Pratchett, Soul Music)
They looked at one another in incomprehension, two minds driving opposite ways up a narrow street and waiting for the other man to reverse first. -- (Terry Pratchett, Soul Music)
The students were staring at her in the manner of those who have heard of the species 'female' but have never expected to get this close to one. -- (Terry Pratchett, Soul Music)
"I'm mean and turf and I'm mean and turf and I'm mean and turf and I'm mean and turf, And me an' my friends can walk towards you with our hats on backwards in a menacing way, Yo!" -- (Terry Pratchett, Soul Music)
The Patrician was a pragmatist. He never tried to fix things that worked. Things that didn't work, however, got broken. -- (Terry Pratchett, Soul Music)
"What duck?" -- (Terry Pratchett, Soul Music)
There was a roar like the scream of a camel who has just seen two bricks. -- (Terry Pratchett, Soul Music)
"Yes," said the skull. "Quit while you're a head, that's what I say." -- (Terry Pratchett, Soul Music)
He did of course sometimes have people horribly tortured to death, but this was considered to be perfectly acceptable behaviour for a civic ruler and generally approved of by the overhelming majority of citizens. [footnote: The overhelming majority of citizens being defined in this case as everyone not currently hanging upside down over a scorpion pit] -- (Terry Pratchett, Sourcery)
There were a few seconds of total silence as everyone waited to see what would happen next. And then Nijel uttered the battle cry that Rincewind would never quite forget to the end of his life. "Erm," he said, "excuse me..." -- (Terry Pratchett, Sourcery)
Of course, Ankh-Morpork's citizens had always claimed that the river water was incredibly pure. Any water that had passed through so many kidneys, they reasoned, had to be very pure indeed. -- (Terry Pratchett, Sourcery)
The subject of wizards and sex is a complicated one, but as has already been indicated it does, in essence, boil down to this: when it comes to wine, women and song, wizards are allowed to get drunk and croon as much as they like. -- (Terry Pratchett, Sourcery)
The vermine is a small black and white relative of the lemming, found in the cold Hublandish regions. Its skin is rare and highly valued, especially by the vermine itself; the selfish little bastard will do anything rather than let go of it. -- Discworld wildlife (Terry Pratchett, Sourcery)
"It's going to look pretty good, then, isn't it," said War testily, "the One Horseman and Three Pedestrians of the Apocralypse." -- The Four Horsemen of the Apocralypse encounter unexpected difficulties (Terry Pratchett, Sourcery)
It wasn't blood in general he couldn't stand the sight of, it was just his blood in particular that was so upsetting. -- (Terry Pratchett, Sourcery)
"I'm not going to ride on a magic carpet!" he hissed. "I'm afraid of grounds." "You mean heights," said Conina. "And stop being silly." "I know what I mean! It's the grounds that kill you!" -- (Terry Pratchett, Sourcery)
It became apparent that one reason why the Ice Giants were known as the Ice Giants was because they were, well, giants. The other was that they were made of ice. -- (Terry Pratchett, Sourcery)
"I meant," said Iplsore bitterly, "what is there in this world that makes living worthwhile?" Death thought about it. "CATS," he said eventually, "CATS ARE NICE." -- Death is obviously not a dog person (Terry Pratchett, Sourcery)
Death was Nature's way of telling you to slow down. -- (Terry Pratchett, Strata)
The sky spun again as Marco turned the ship so that 'down' was where long tradition had always put it, in the region of the feet. -- (Terry Pratchett, Strata)
He didn't look mad, but they never did. -- (Terry Pratchett, Strata)
"You can think and you can fight, but the world's always movin', and if you wanna stay ahead you gotta dance." -- (Terry Pratchett, The Amazing Maurice and his Educated Rodents)
The important thing about adventures, thought Mr Bunnsy, was that they shouldn't be so long as to make you miss mealtimes. -- (Terry Pratchett, The Amazing Maurice and his Educated Rodents)
Humans, eh? Think they're lords of creation. Not like us cats. We *know* we are. Ever see a cat feed a human? Case proven. -- (Terry Pratchett, The Amazing Maurice and his Educated Rodents)
A good plan isn't one where someone wins, it's where nobody thinks they've *lost*. -- (Terry Pratchett, The Amazing Maurice and his Educated Rodents)
"You can think and you can fight, but the world's always movin', and if you wanna stay ahead you gotta dance." -- (Terry Pratchett, The Amazing Maurice and his Educated Rodents)
On the fifth day the Governor of the town called all the tribal chieftains to an audience in the market square, to hear their grievances. He didn't always do anything about them, but at least they got *heard*, and he nodded a lot, and everyone felt better about it at least until they got home. This is politics. -- Carpet politics are very similar to Discworld politics (Terry Pratchett, The Carpet People)
"He's a man of few words, and he doesn't know what either of them mean," people said, but not when he was within hearing. -- (Terry Pratchett, The Carpet People)
Tourist, Rincewind decided, meant "idiot". -- (Terry Pratchett, The Colour of Magic)
"Reflected-sound-of-underground-spirits?" -- Economics explained (Terry Pratchett, The Colour of Magic)
"Let's just say that if complete and utter chaos was lightning, he'd be the sort to stand on a hilltop in a thunderstorm wearing wet copper armour and shouting 'All gods are bastards'." -- Rincewind discussing Twoflower (Terry Pratchett, The Colour of Magic)
"I perceive a possibility of an immediate chronological sequence of events which includes a violence," said Three. He stepped back, "I express preference for a chronological sequence of events which precludes a violence." -- (Terry Pratchett, The Dark Side of the Sun)
The one positive thing you could say about the bread products around him was that they were probably as edible now as they were on the day they were baked. *Forged* was a better term. Dwarf bread was made as a meal of last resort and also as a weapon and a currency. Dwarfs were not, as far as Vimes knew, religious in any way, but the way they thought about bread came close. -- (Terry Pratchett, The Fifth Elephant)
You did something because it had always been done, and the explanation was "but we've always done it this way." A million dead people can't have been wrong, can they? -- (Terry Pratchett, The Fifth Elephant)
There was no such thing as a dwarfish female pronoun or, once the children were on solids, any such thing as women's work. -- (Terry Pratchett, The Fifth Elephant)
He wasn't strictly aware of it, but he treated even geography as if he was investigating a crime (did you see who carved out the valley? Would you recognize that glacier if you saw it again?) -- (Terry Pratchett, The Fifth Elephant)
He was aware that a wise man should always respect the folkways of others, to use Carrot's happy phrase, but Vimes often had difficulty with this idea. For one thing, there were people in the world whose folkways consisted of gutting other people like clams and this was not a procedure that commanded, in Vimes, any kind of respect at all. -- (Terry Pratchett, The Fifth Elephant)
It was funny how people were people everywhere you went, even if the people concerned weren't the people the people who made up the phrase "people are people everywhere" had traditionally thought of as people. And even if you weren't virtuous, as you had been brought up to understand the term, you did like to see virtue in other people, provided it didn?t cost you anything. -- (Terry Pratchett, The Fifth Elephant)
A marriage is always made up of two people who are prepared to swear that only the other one snores. -- (Terry Pratchett, The Fifth Elephant)
As castles went, this one looked as though it could be taken by a small squad of not very efficient soldiers. For defence, putting a blanket over your head might be marginally safer. -- (Terry Pratchett, The Fifth Elephant)
She moved like someone who had grown used to her body and, in general, looked like what Vimes had heard described as "a woman of a certain age." He'd never been quite certain what age that was. -- (Terry Pratchett, The Fifth Elephant)
"When it's time to stop living, I will certainly make Death my number one choice!" -- (Terry Pratchett, The Last Continent)
In the fetid fleapit of Rincewind's brain the projectionist of memory put on reel two. Recollection began to flicker. -- (Terry Pratchett, The Last Continent)
Daggy stepped forward, but only comparatively; in fact, his mates had all, without discussion, taken one step backwards in the choreography of caution. -- (Terry Pratchett, The Last Continent)
PEOPLE'S WHOLE LIVES *DO* PASS IN FRONT OF THEIR EYES BEFORE THEY DIE. THE PROCESS IS CALLED 'LIVING'. -- (Terry Pratchett, The Last Continent)
"Nulli Sheilae sanguineae" -- (Terry Pratchett, The Last Continent)
"All bastards are bastards, but some bastards is *bastards*." -- (Terry Pratchett, The Last Continent)
They say the heat and the flies here can drive a man insane. But you don't have to believe that, and nor does that bright mauve elephant that just cycled past. -- (Terry Pratchett, The Last Continent)
Ridcully was to management what King Herod was to the Bethlehem Playgroup Association. His mental approach to it could be visualised as a sort of business flowchart with, at the top, a circle entitled "Me, who does the telling" and, connected below it by a line, a large circle entitled "Everyone else". -- (Terry Pratchett, The Last Continent)
"When You're Up to Your Ass in Alligators, Today Is the First Day of the Rest of Your Life." -- Management slogan, Ridcully-style (Terry Pratchett, The Last Continent)
Rincewind had always been happy to think of himself as a racist. The One Hundred Meters, the Mile, the Marathon -- he'd run them all. -- (Terry Pratchett, The Last Continent)
Too many people, when listing all the perils to be found in the search for lost treasure or ancient wisdom, had forgotten to put at the top of the list 'the man who arrived just before you'. -- (Terry Pratchett, The Last Hero)
- "DID YOU SAY HUMANS PLAY IT FOR FUN?" - "Some of them get to be very good at it, yes. I'm only an amateur, I'm afraid" - "BUT THEY ONLY LIVE EIGHTY OR NINETY YEARS!" -- The joys of bridge (Terry Pratchett, The Light Fantastic)
It looked like the sort of book described in library catalogues as "slightly foxed", although it would be more honest to admit that it looked as though it had beed badgered, wolved and possibly beared as well. -- Ah, but has it been hedgehogged? (Terry Pratchett, The Light Fantastic)
"The knuckles! The horrible knuckles!" -- (Terry Pratchett, The Light Fantastic)
I WAS AT A PARTY, he added, a shade reproachfully. -- Death is summoned by the Wizards (Terry Pratchett, The Light Fantastic)
A Thaum is the basic unit of magical strength. It has been universally established as the amount of magic needed to create one small white pigeon or three normal sized billiard balls. -- (Terry Pratchett, The Light Fantastic)
He moved in a way that suggested he was attempting the world speed record for the nonchalant walk. -- (Terry Pratchett, The Light Fantastic)
- "What is it that a man may call the greatest things in life?" - "Hot water, good dentishtry and shoft lavatory paper." -- Cohen the Barbarian in conversation with Discworld nomads (Terry Pratchett, The Light Fantastic)
The old shaman said carefully, "You didn't just see two men go through upside down on a broomstick, shouting and screaming at each other, did you?" The boy looked at him levelly. "Certainly not," he said. The old man heaved a sigh of relief. "Thank goodness for that," he said. "Neither did I." -- Rincewind and Twoflower take up broomstick flying (Terry Pratchett, The Light Fantastic)
Something small and distant broke through the cloud layer, trailing shreds of vapour. In the stratospheric calm the sounds of bickering came sharp and clear. "You said you could fly one of these things!" "No I didn't; I just said *you* couldn't!" -- Rincewind and Twoflower attempt broomstick flying (Terry Pratchett, The Light Fantastic)
"Early to rise, early to bed, makes a man healthy, wealthy and dead." -- Apparently Terry nicked this from James Thurber. Still a good quote, though. (Terry Pratchett, The Light Fantastic)
The druid stiffened. "*Nice?*" he said. "A triumph of the silicon chunk, a miracle of modern masonic technology -- *nice*?" "Oh, yes," said Twoflower, to whom sarcasm was merely a seven letter word beginning with S. -- (Terry Pratchett, The Light Fantastic)
"Shut up and tell me what that other idiot ish doing!" "No, but look, if I've got to shut up, how can I --" The knife at his throat became a hot streak of pain and Rincewind decided to give logic a miss. -- Cohen the Barbarian interrogates Rincewind (Terry Pratchett, The Light Fantastic)
"Students made it long ago," said Rincewind. "Handy way in and out after lights out." "Ah," said Twoflower, "I *understand*. Over the wall and out to brightly-lit tavernas to drink and sing and recite poetry, yes?" "Nearly right except for the singings and the poetry, yes," said Rincewind. -- (Terry Pratchett, The Light Fantastic)
- "Pull me up, then," he hinted. - "I think that might be sort of difficult," grunted Twoflower. "I don't actually think I can do it, in fact." - "What are you holding on to, then?" - "You." - "I mean besides me." - "What do you mean, besides you?" said Twoflower. -- (Terry Pratchett, The Light Fantastic)
There were no flies on C.M.O.T Dibbler. He would have charged them rent. -- (Terry Pratchett, The Truth)
The world is made up of four elements: Earth, Air, Fire and Water. This is a fact well known even to Corporal Nobbs. It's also wrong. There's a fifth element, and generally it's called Surprise. -- (Terry Pratchett, The Truth)
There are, it has been said, two types of people in the world. There are those who, when presented with a glass that is exactly half full, say: this glass is half full. And then there are those who say: this glass is half empty.
The world *belongs*, however, to those who can look at the glass and say: What's up with this glass? Excuse me? Excuse *me*? This is my glass? I don't *think* so. My glass was full! *And* it was a bigger glass! -- (Terry Pratchett, The Truth)
It was a puzzle why things were always dragged kicking and screaming. No one ever seemed to want to, for example, lead them gently by the hand. -- (Terry Pratchett, The Truth)
"The Truth Shall Make Ye Fret" -- (Terry Pratchett, The Truth)
A lie can run round the world before the truth has got its boots on. -- (Terry Pratchett, The Truth)
"He's going to go totally Librarian-poo." -- Gaspode about Vimes (Terry Pratchett, The Truth)
"But surely dogs can't talk--" Sacharissa began. "Oh dear oh dear oh dear," said Gaspode, "Did I *say* I was talking?" "Well, not in so many words--" -- Sacharissa meets Gaspode the Wonder Dog (Terry Pratchett, The Truth)
WHO KNOWS WHAT EVIL LURKS IN THE HEART OF MEN? The Death of Rats looked up from the feast of potato. SQUEAK, he said. Death waved a hand dismissively. WELL, YES, OBVIOUSLY *ME*, he said. I JUST WONDERED IF THERE WAS ANYONE ELSE. -- (Terry Pratchett, The Truth)
"Some people are heroes. And some people jot down notes." -- (Terry Pratchett, The Truth)
Our garden was debated territory between five local cats, and we'd heard that the best way to keep other cats out of the garden was to have one yourself. A moment's rational thought here will spot the slight flaw in this reasoning. -- (Terry Pratchett, The Unadulterated Cat)
Boot-faced cats aren't born but made, often because they've tried to outstare or occasionally rape a speeding car and have been repaired by a vet who just pulled all the bits together and stuck the stitches in where there was room. -- (Terry Pratchett, The Unadulterated Cat)
Cats don't hunt seals. They would if they knew what they were and where to find them. But they don't, so that's all right. -- (Terry Pratchett, The Unadulterated Cat)
It's an interesting fact that fewer than 17
of Real cats end their lives with the same name they started with. Much family effort goes into selecting one at the start ("She looks like a Winnifred to *me*"), and the as the years roll by it suddenly finds itself being called Meepo or Ratbag. -- (Terry Pratchett, The Unadulterated Cat)
Next comes the realist phase ("After all, from a purely geometrical point of view a cat is only a tube with a door at the top."). -- Getting Real cats to take medication can be a problem (Terry Pratchett, The Unadulterated Cat)
Everyone's heard of Erwin Schrodinger's famous thought experiment. You put a cat in a box with a bottle of poison, which many people would suggest is about as far as you need to go. -- (Terry Pratchett, The Unadulterated Cat)
Consider the situation. There you are, forehead like a set of balconies, worrying about the long-term effects of all this new 'fire' stuff on the environment, you're being chased and eaten by most of the planet's large animals, and suddenly tiny versions of one of the worst of them wanders into the cave and starts to purr. -- Why humans like cats (Terry Pratchett, The Unadulterated Cat)
"Zoology, eh? That's a big word, isn't it?" "No, actually it isn't," said Tiffany. "Patronizing is a big word. Zoology is really quite short." -- (Terry Pratchett, The Wee Free Men)
"Nac Mac Feegle! The Wee Free Men! Nae king! Nae quin! Nae laird! *We willna be fooled again!*" -- (Terry Pratchett, The Wee Free Men)
"Do not act incautiously when confronting little bald wrinkly smiling men!" -- Rule One (Terry Pratchett, Thief of Time)
"Sometimes I really think people ought to have to pass a *proper* exam before they're allowed to be parents. Not just the practical, I mean." -- (Terry Pratchett, Thief of Time)
"No running with scythes!" -- (Terry Pratchett, Thief of Time)
Look, that's why there's rules, understand? So that you *think* before you break 'em. -- (Terry Pratchett, Thief of Time)
"Bikkit!" -- (Terry Pratchett, Thief of Time)
Even with nougat you can have a perfect moment. -- (Terry Pratchett, Thief of Time)
- "Outside! What's it like?" - "Well -- It's sort of big" -- (Terry Pratchett, Truckers)
The Store was having its last sale. It was holding a Grand Final Clearance of specially selected sparks, and flames to suit every pocket. -- (Terry Pratchett, Truckers)
It's a small step for a man, but a giant leap for nomekind. -- (Terry Pratchett, Truckers)
Granny's remedies, made from simple, honest, and generally nearly poisonous herbs and roots, were amazing things. After one dose of stomache-ache jollop, you made sure you never complained of stomach ache ever again. In its way, it was a sort of cure. -- No, not that Granny. The other one. (Terry Pratchett, Truckers)
"No, no, no, what you do is, you get a gnu, then you point it at the driver and someone says, 'Look out, he's got a gnu!' and you say, 'Take us where we want to go or I'll fire this gnu at you!'" -- "Host Age at 10,000 Feet", Nome style (Terry Pratchett, Truckers)
There was a polite beeping from the Thing. "You may be interested to know," it said, "that we've broken the sound barrier." Masklin turned wearily to the others. "All right, own up. Who broke it?" -- (Terry Pratchett, Wings)
Beyond the top of the sky was the place the Thing had called the universe. It contained -- according to the Thing -- everything and nothing. And there was very little everything and more nothing than anyone could imagine. -- (Terry Pratchett, Wings)
- "The other humans around it are trying to explain to it what a planet is." - "Doesn't it know?" - "Many humans don't. Mistervicepresident is one of them." -- (Terry Pratchett, Wings)
AIRPORTS: A place where people hurry up and wait. -- From A Scientific Encyclopedia for the Enquiring Young Nome by Angalo de Haberdasheri (Terry Pratchett, Wings)
"I thought jet planes were just trucks with more wings and less wheels." -- (Terry Pratchett, Wings)
Nomes live ten times faster than humans. They're harder to see than a high- speed mouse. That's one reason why most humans hardly ever see them. The other is that humans are very good at not seeing things they know aren't there. And, since sensible humans know that there are no such things as people four inches high, a nome who doesn't want to be seen probably won't be seen. -- (Terry Pratchett, Wings)
And then there were the frogs. Very, very small frogs. They had such a tiny life cycle it still had trainer wheels on it. -- (Terry Pratchett, Wings)
And this had been the way things were for as far back as the frogs could remember [footnote: About three seconds. Frogs don't have good memories]. -- (Terry Pratchett, Wings)
"You get more air close to the ground," said Angalo. "I read that in a book. You get lots of air low down, and not much when you go up." "Why not?" said Gurder. "Dunno. It's frightened of heights, I guess." -- The nomes discuss science (Terry Pratchett, Wings)
CONCORDE: It goes twice as fast as a bullet and you get smoked salmon. -- From A Scientific Encyclopedia for the Enquiring Young Nome by Angalo de Haberdasheri (Terry Pratchett, Wings)
It had been in a pocket diary, and the names of the faraway places written on it were like magic -- Africa, Australia, China, Equator, Printed in Hong Kong, Iceland... -- (Terry Pratchett, Wings)
They stared at the branch. There wasn't just one flower out there, there were dozens, although the frogs weren't able to think like this because frogs can't count beyond one. They saw lots of ones. -- (Terry Pratchett, Wings)
They stared at them. Staring is one of the few things frogs are good at. Thinking isn't. -- (Terry Pratchett, Wings)
It would be nice to say that the tiny frogs thought long and hard about the new flower, about life in the old flower, about the need to explore, about the possibility that the world was bigger than a pool with petals around the edge. In fact, what they thought was: "._._.mipmip._._.mipmip._._.mipmip". -- (Terry Pratchett, Wings)
HOTELS: A place where TRAVELLING HUMANS are parked at night. Other humans bring them food, including the famous BACON, LETTUCE AND TOMATO SANDWICH. There are beds and towels and special things that rain on people to get them clean. -- From A Scientific Encyclopedia for the Enquiring Young Nome by Angalo de Haberdasheri (Terry Pratchett, Wings)
- "Very clever idea, though." - "What is?" - "Asking the questions when people arrive. If anyone was coming here to do some subversive overthrowing, everyone'd be down on him like a pound of bricks as soon as he answered 'Yes'." - "It's a sneaky trick, isn't it," said Angalo, in an admiring tone of voice. -- The nomes encounter American customs regulations (Terry Pratchett, Wings)
- "What's the human singing about, Thing?" said Masklin. - "It is a little difficult to follow. However, it appears that the singer wishes it to be known that he did something his way." - "Did what?" - "Insufficient data at this point. But whatever it was, he did it at a) each step along life's highway and b) not in a shy way..." -- (Terry Pratchett, Wings)
SATELLITES: They are in SPACE and stay there by going so fast that they are never in one place enough to fall down. TELEVISIONS are bounced off them. They are part of SCIENCE. -- From A Scientific Encyclopedia for the Enquiring Young Nome by Angalo de Haberdasheri (Terry Pratchett, Wings)
SCIENCE: A way of finding things out and then making them work. Science explains what is happening around us the whole time. So does RELIGION, but science is better because it comes up with more understandable excuses when it is wrong. There is a lot more Science than you think. -- From A Scientific Encyclopedia for the Enquiring Young Nome by Angalo de Haberdasheri (Terry Pratchett, Wings)
"All right," said Masklin. "But you're not to fly down low again and try to read the signposts. Every time you do that, humans rush into the streets and we get lots of shouting on the radio." "That's right." said the Thing. "People are bound to get excited when they see a ten-million-ton starship trying to fly down the street." -- (Terry Pratchett, Wings)
The Yen Buddhists are the richest religious sect in the universe. They hold that the accumulation of money is a great evil and a burden to the soul. They therefore, regardless of personal hazard, see it as their unpleasant duty to acquire as much as possible in order to reduce the risk to innocent people. -- (Terry Pratchett, Witches Abroad)
Asking someone to repeat a phrase you'd not only heard very clearly but were also exceedingly angry about was around Defcon II in the lexicon of squabble. -- (Terry Pratchett, Witches Abroad)
- "We've got a lot of experience of not having any experience" - "But the point is... the point is... the point is we've not been experienced for a lot longer than you." -- Stop being so negative (Terry Pratchett, Witches Abroad)
The only way housework could be done in this place was with a shovel or, for preference, a match. -- (Terry Pratchett, Witches Abroad)
People didn't hit you over the head with farmhouses back home. -- Nanny Ogg gets homesick (Terry Pratchett, Witches Abroad)
Racism was not a problem on the Discworld, because -- what with trolls and dwarfs and so on -- speciesism was more interesting. Black and white lived in perfect harmony and ganged up on green. -- (Terry Pratchett, Witches Abroad)
Nanny Ogg quite liked cooking, provided there were other people around to do things like chop up the vegetables and wash the dishes afterwards. -- Home Pragmatics (Terry Pratchett, Witches Abroad)
"Emberella," thought Magrat. "I'm fairy godmothering a girl who sounds like something you put up in the rain." -- (Terry Pratchett, Witches Abroad)
Magrat was annoyed. She was also frightened, which made her even more annoyed. It was hard for people when Magrat was annoyed. It was like being attacked by damp tissue. -- (Terry Pratchett, Witches Abroad)
Nanny Ogg looked him up and down or, at least, down and further down. "You're a dwarf," she said. -- Nanny Ogg meets Casanunda (Terry Pratchett, Witches Abroad)
- "'S called the Vieux River." - "Yes?" - "Know what that means?" - "No." - "The Old (Masculine) River," said Nanny. - "Yes?" - "Words have sex in foreign parts," said Nanny hopefully. -- (Terry Pratchett, Witches Abroad)
"You can't go around building a better world for people. Only people can build a better world for people. Otherwise it's just a cage." -- (Terry Pratchett, Witches Abroad)
Greebo's technique was unscientific and wouldn't have stood a chance against any decent swordmanship, but on his side was the fact that it is almost impossible to develop decent swordmanship when you seem to have run into a food mixer that is biting your ear off. -- (Terry Pratchett, Witches Abroad)
Genua had once controlled the river mouth and taxed its traffic in a way that couldn't be called piracy because it was done by the city government. -- Local-body politics explained (Terry Pratchett, Witches Abroad)
"Baths is unhygienic," Granny declared. "You know I've never agreed with baths. Sittin' around in your own dirt like that." -- Taking personal hygiene to new limits (Terry Pratchett, Witches Abroad)
The calender of the Theocracy of Muntab counts down, not up. No-one knows why, but it might not be a good idea to hang around and find out. -- (Terry Pratchett, Wyrd Sisters)
The duke had a mind that ticked like a clock and, like a clock, it regularly went cuckoo. -- (Terry Pratchett, Wyrd Sisters)
"There must be a hundred silver dollars in here," moaned Boggis, waving a purse. "I mean, that's not my league. That's not my class. I can't handle that sort of money. You've got to be in the Guild of Lawyers or something to steal that much." -- (Terry Pratchett, Wyrd Sisters)
"I'd like to know if I could compare you to a summer's day. Because -- well, June 12th was quite nice, and..." -- (Terry Pratchett, Wyrd Sisters)
"'Tis not right, a woman going into such places by herself." Granny nodded. She thoroughly approved of such sentiments so long as there was, of course, no suggestion that they applied to her. -- (Terry Pratchett, Wyrd Sisters)
Above the hearth was a huge pokerwork sign saying "Mother". No tyrant in the whole history of the world had ever achieved a domination so complete. -- (Terry Pratchett, Wyrd Sisters)
"A man could go far, knowing his rights like you do," said Granny. "But right now he should go home." -- (Terry Pratchett, Wyrd Sisters)
"I daresay," said Granny, pushing the Fool aside and stepping over a writhing taproot. "If anyone locked *me* in a dungeon, there'd be screams." -- (Terry Pratchett, Wyrd Sisters)
"He didn't take any notice!" whispered Tomjon. "A born critic," said the dwarf. -- Discworld stage actors in conversation (Terry Pratchett, Wyrd Sisters)
"Actors," said Granny, witheringly. "As if the world weren't full of enough history without inventing more." -- (Terry Pratchett, Wyrd Sisters)
In fact, no gods anywhere play chess. They prefer simple, vicious games, where you Do Not Achieve Transcendence but Go Straight to Oblivion; a key to the understanding of all religion is that a god's idea of amusement is Snakes and Ladders with greased rungs. -- (Terry Pratchett, Wyrd Sisters)
"Yes, bugger all that." said Nanny. "Let's curse somebody." -- Even Nanny Ogg gets upset occasionally (Terry Pratchett, Wyrd Sisters)
On nights such as these the gods, as has already been pointed out, play games other than chess with the fates of mortals and the thrones of kings. It is important to remember that they always cheat, right up to the end... -- (Terry Pratchett, Wyrd Sisters)

17
doc/ConfigChanges.txt Normal file
View File

@ -0,0 +1,17 @@
Any configuration file changes and how to update should be listed here.
Config version 17
-----------------
* Added config_log_colors
Config version 16
-----------------
* Added config_commands_private_always
* Renamed config_listenregex to config_commands_listenregex
* Renamed config_unknown_commands to config_feedback_unknown_commands.
Config version 15
-----------------
This file was added at this point. To convert from older versions to this
one, please read example config and add what is missing.

View File

@ -0,0 +1,296 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
# Variables marked with (*) will take effect at a rehash as well.
# What version this config is at. This is used to check
# if your config needs updating.
config_version=17
####################
# General settings #
####################
# Nick to use
config_firstnick="BOTNICK"
# Nick if first is in use
config_secondnick="BOTNICK_"
# Nick if second is in use
config_thirdnick="BOTNICK__"
config_ident='rfc3092'
config_gecos='ietf.org/rfc/rfc3092'
###################
# Server settings #
###################
# Server to use
config_server='irc.kuonet-ng.org'
# What port to use. Normally 6667 works for non SSL connections.
config_server_port='6667'
# If 1 use SSL, not all transport modules support this.
config_server_ssl=0
# Accept invalid server certificates?
# Note that some SSL modules (openssl for example) just
# print any errors and continues anyway
config_server_ssl_accept_invalid=1
# Be verbose when connecting?
# Not all SSL modules support it. The ones that doesn't
# support it will just ignore it.
# Be aware that this is mainly for debugging of SSL transport
# modules as it is possible the verbose output may confuse the bot!
config_server_ssl_verbose=0
# If not empty try to bind to this IP when connecting, useful
# to select vhost. Not all transport modules support this.
config_server_bind=""
# If this is empty don't use a server password.
config_server_passwd=""
##################
# Access control #
##################
# (*) Access regexes
# Without at least one set, the bot won't start
# "owner" is a special capab that grants all other access.
# The first access entry must be an owner.
#
# Scope is a regular expression of channels where access is effective.
# A /msg (like say to a non channel) will get the scope MSG
# Anything affecting global state will have the scope "GLOBAL"
#
# There can be several access masks matching same host (to allow
# for different scope/capab combinations).
#config_access_mask[1]='^The!owner@shellhost\.net$'
#config_access_capab[1]='owner'
#config_access_scope[1]='.*'
# Some more access examples:
#config_access_mask[2]='^Someone!whatever@customer-1234\.isp\.com$'
#config_access_capab[2]='say kick'
#config_access_scope[2]='#channel'
#config_access_mask[3]='^Someone!whatever@customer-1234\.isp\.com$'
#config_access_capab[3]='facoid_admin'
#config_access_scope[3]='GLOBAL'
############
# Commands #
############
# (*) A regular expression of prefixes we should listen to.
config_commands_listenregex="(;|${config_firstnick}[:,] )"
# (*) Should we treat any message in /msg as a command even
# if it doesn't have the the listen prefix?
config_commands_private_always=0
############
# Feedback #
############
# (*) How to treat unknown commands:
# Valid values:
# 0 = Ignore them (drop command, do nothing).
# 1 = Return error to sender.
# 2 = Pass them on to modules that may handle "generic" PRIVMSG.
# Note that non-commands always get passed on to "generic" PRIVMSG handling modules.
config_feedback_unknown_commands=1
###########
# Logging #
###########
# Directory for log files
config_log_dir="@@logdir@@"
# (*) Should we log raw data or not?
# Can be 1 (log) or 0 (don't log)
config_log_raw=0
# (*) Should we always log to STDOUT as well?
# Note that this doesn't mean it will not log at all if set to 0.
# It will still log errors and other important log messages to STDOUT.
# This is the same as --verbose.
# Can be 1 (log) or 0 (don't log)
config_log_stdout=0
# (*) When logging to STDOUT should we use colors?
config_log_colors=1
#############
# Transport #
#############
# Transport module. You should select exactly one.
# The recommended non-SSL module is dev-tcp.
# The recommended SSL module is gnutls.
config_transport='dev-tcp'
#config_transport='netcat' # Not well tested, for Debian users and other
# with broken distros.
#config_transport='gnutls'
#config_transport='socat' # Not well tested
#config_transport='openssl' # Not well tested
# netcat options
# MAKE SURE THEY ARE CORRECT if you use netcat.
#config_transport_netcat_path='/usr/bin/netcat'
# socat options
# MAKE SURE THEY ARE CORRECT if you use socat.
#
# This tells if to use IPv6 or IPv4 to connect.
# socat doesn't support automatic choice of this.
# Note that socat versions below 1.5 does not
# support using IPv6 and SSL at the same time.
# This can be either "ipv4" or "ipv6".
#config_transport_socat_protocol_family=ipv4
# Where are transports stored, this can be a relative path from the
# main script of the bot, or an absolute path.
config_transport_dir="@@transportdir@@"
###########
# Modules #
###########
# What modules to load on startup, space separated list
# For a list of modules see the modules dir.
# Note that the order of the modules may be important
#
# The list should normally start with "modules rehash services umodes autojoin"
# Some modules should be placed last. "factoids" and "karma are such modules.
config_modules="modules rehash services umodes autojoin ctcp"
# Where are modules stored, this can be a relative path from the
# main script of the bot, or an absolute path.
config_modules_dir="@@moddir@@"
############################
# Module specific settings #
############################
#####################################################################
# Services module
#
# (*) NickServ password
config_module_services_nickserv_passwd='nickserv password here'
# (*) Name of NickServ
# Normally this is correct.
config_module_services_nickserv_name='NickServ'
# (*) Service style. Supported are: generic, atheme
# For the default server (irc.kuonet-ng.org) use atheme
# Otherwise try generic, that will work with atheme too but
# some features will be disabled.
config_module_services_style='atheme'
# (*) Use server side aliases
# Try 1 first, if the bot fails to identify try 0
config_module_services_server_alias=1
#####################################################################
# FAQ module
#
# (*) Location of FAQ items.
#config_module_faq_file='@@datadir@@/faq.txt'
####################################################################
# Quote module
#
# (*) Location of quotes file
#config_module_quotes_file='@@datadir@@/quotes.txt'
#####################################################################
# AutoJoin module.
#
# Channels to autojoin on connect
config_module_autojoin_channels[1]='#channel'
# A channel can have a key as showed in the example below
config_module_autojoin_channels[2]='#otherchannel channelkey'
#####################################################################
# Umodes module.
#
# (*) Default umodes to set on connect. Also set these at a rehash.
config_module_umodes_default_umodes="+isB-w"
#####################################################################
# CTCP module
#
# (*) What to reply to VERSION requests.
config_module_ctcp_version_reply="envbot $envbot_version"
#####################################################################
# SQLite3 module
#
# (*) Location of SQLite3 database file
#config_module_sqlite3_database='@@datadir@@/envbot.db'
#####################################################################
# Factoids module
#
# (*) Table name for factoids in SQLite3 database
#config_module_factoids_table='factoids'
#####################################################################
# Karma module
#
# (*) Table name for karma data in SQLite3 database
#config_module_karma_table='karma'
#####################################################################
# Seen module
#
# (*) Table name for seen data in SQLite3 database
#config_module_seen_table='seen'
#####################################################################
# Vote module.
#
# (*) The channel where vote mode will work.
# Note that this is a regular expression that will be
# surronded by ^ and $ when matching.
#config_module_vote_channel='#mychannel'
# (*) How long a vote is open before it is closed (in seconds).
#config_module_vote_timeout='900'
#####################################################################
# For contrib modules please see the contrib module in question
# for information on what variables it uses.

100
doc/coding-standards.txt Normal file
View File

@ -0,0 +1,100 @@
Coding standards
================
This document describe the coding standards used in envbot.
Indention
---------
Indention is done with tabs, one tab char for each indention level.
Do adjust the code with spaces if you need to break a long line.
Example:
thisFunctionGotLotsOfParameters foo bar quux loooooooooooooooooooooooooooongParameter
some more parameters
That is, first indent with tabs to base indention level, then use spaces
to adjust.
Spaces
------
Trailing spaces
Forbidden.
Spaces around operators:
Depend on situation.
Examples:
myvar="this is a value"
if [[ "$myvar" = "something" ]]; then
As you can see it depends on where it is used.
Generally it should be used everywhere except assignment like
"=" and "=+".
Newlines
--------
There should not be newlines before a { or other block separator.
For example this is correct:
if [[ $a = $b ]]; then
runStuff
fi
But this is incorrect:
if [[ $a = $b ]]
then
runStuff
fi
Trailing newlines: There should be a single
ending newline at the end of the file, no
trailing newlines.
Functions
---------
Functions should be declared without the function keyword
and with the () following the name of the function, without
space between the function name and the (). After the () there
should be one space and then the opening bracket.
Example:
myNiceFunction() {
runStuff
}
Functions: Returning values
---------------------------
Return values using "printf -v" construct. Avoid subshells (for
performance reasons). One exception to this is the case of the
function only calling an external program, then just subshell
the function let the external programs output go to STDOUT of the
function.
Example of printf -v construct (first parameter for this function
is the name of the variable to return in):
myfunction() {
printf -v "$1" '%s' "something we computed"
}
Using return code should only be used when returned value is
an error code. Example:
otherfunction() {
if [[ "$1" = "foo" ]]; then
# Success!
return 0
else
# Fail
return 1
fi
}
Comments
--------
Comments are free form inside function, function comment should
be coded in bashdoc style.
(TODO: add description of that or link to that)
Comments should be indented to same level as surrounding code,
unless the comment is commented out code. Commented out code
should be avoided in committed code, but if it is needed, the
comment should be at the start of the line.
if
--
When testing in if use the [[ ]] construct. Do NOT under ANY
circumstances use the [ ] construct.

52
doc/envbot.1 Normal file
View File

@ -0,0 +1,52 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.36.
.TH ENVBOT "1" "July 2008" "envbot" "User Commands"
.SH NAME
envbot \- An advanced modular IRC bot in bash
.SH SYNOPSIS
.B envbot
[\fIOPTION\fR]...
.SH DESCRIPTION
envbot is an advanced modular IRC bot coded in bash.
.SH OPTIONS
.TP
\fB\-c\fR, \fB\-\-config\fR file
Use file instead of the default as config file.
.TP
\fB\-l\fR, \fB\-\-libdir\fR directory
Use directory instead of the default as library directory.
.TP
\fB\-v\fR, \fB\-\-verbose\fR
Force verbose output even if config_log_stdout is 0.
.TP
\fB\-d\fR, \fB\-\-debug\fR
Enable debugging code. Most likely pointless to anyone
except envbot developers or module developers.
.TP
\fB\-h\fR, \fB\-\-help\fR
Display this help and exit
.TP
\fB\-V\fR, \fB\-\-version\fR
Output version information and exit
.PP
Note that envbot can't handle short versions of options being written together like
\fB\-vv\fR currently.
.PP
Exit status is 0 if OK, 1 if minor problems, 2 if serious trouble.
.SH EXAMPLES
.TP
envbot
Runs envbot with default options.
.TP
envbot \fB\-c\fR bot.config
Runs envbot with the config bot.config.
.SH AUTHOR
Written by Arvid Norlander and EmErgE.
.SH "REPORTING BUGS"
Report bugs to http://envbot.org/trac/simpleticket
.SH COPYRIGHT
Copyright \(co 2007-2008 Arvid Norlander
.br
Copyright \(co 2007-2008 EmErgE
.br
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

15
doc/factoids.sql Normal file
View File

@ -0,0 +1,15 @@
/*
* This is how to create a factoid database
* Use this file like this:
* sqlite3 -batch data/envbot.db < doc/factoids.sql
*
* If you use another table name, change below in
* both places
*/
DROP TABLE IF EXISTS factoids;
CREATE TABLE factoids (
name TEXT UNIQUE NOT NULL PRIMARY KEY,
value TEXT NOT NULL,
who TEXT NOT NULL,
is_locked INTEGER NOT NULL DEFAULT 0
);

14
doc/karma.sql Normal file
View File

@ -0,0 +1,14 @@
/*
* This is how to create a factoid database
* Use this file like this:
* sqlite3 -batch data/envbot.db < doc/karma.sql
*
* If you use another table name, change below in
* both places
*/
DROP TABLE IF EXISTS karma;
CREATE TABLE karma (
target TEXT UNIQUE NOT NULL PRIMARY KEY,
rating INTEGER NOT NULL,
is_locked INTEGER NOT NULL DEFAULT 0
);

383
doc/module_api2.txt Normal file
View File

@ -0,0 +1,383 @@
Module API
==========
This file contains info needed to write a module.
File name
---------
There are two ways to store modules: single file and directory.
Single file:
The module is a single file.
The file name should follow this pattern:
m_modulename.sh
Example:
m_faq.sh
Directory:
The module is a directory that contains several files.
The directory name should follow this pattern:
m_modulename
Example:
m_faq
This directory should contain a file called __main__.sh
that the bot will load when loading the module. This file
may then load other files from the directory as needed.
Other than that there are no differences between the two ways to store
modules.
Variables
---------
All bash variables in the module that are global MUST follow the format
module_modulename_variablename to prevent overwriting other modules, or
global bot variables. FAQ module may use: module_faq_last_query for example
as a variable.
Variables that are local to the function (using the local directive) may have
other names. Don't forget variables created in for loops and such
(for foo in bar bash; do echo $foo; done the $foo variable is global here,
do local foo on the line before it)
Functions and hooks
-------------------
Hooks are functions with special names, as listed below in the section "The hooks".
Each function or hook should start with the module_ followed by the module name.
So if the module is faq.sh the modules should start with module_faq_ like module_faq_INIT.
All bash functions in the module MUST follow the format
module_modulename_functionname to prevent overwriting other modules, or
global bot functions.
If not mentioned otherwise the hook is optional.
Functions marked with TODO may change, get renamed or removed and may not work
even in current code.
The hooks
---------
module_modulename_INIT (REQUIRED)
Called directly after loading the module.
Parameters:
$1 = The module name.
None
Exported variables to this function:
MODULE_BASE_PATH
Useful for multifile modules, then it is the path to the base directory of
the module (without ending slash). If the module consists of a single file
it is the path to the module file.
Return status:
If return status isn't 0 the module is considered as failed to load.
Return on in variables (these will get unset after they have been processed):
modinit_HOOKS:
In this variable should be returned a space separated list of the optional functions
that the module implements. FAQ would return "before_connect on_PRIVMSG"
modinit_API:
Return 2 if you follow this module API.
Notes:
Check for dependencies (external commands and other modules) in this function,
as well as register any commands.
module_modulename_UNLOAD (REQUIRED)
Called to make the module unload itself.
In this function it should clean up after itself and unset all
it's functions and variables except the hook functions.
The unload function and other hook functions will be undefined
after unload finished.
The module may or may not get reloaded after this.
Parameters:
None
Return status:
0 = Unloaded correctly
1 = Failed to unload. On this the bot will quit.
Notes:
This function is NOT called when the bot is exiting. To check for that
use the FINALISE hook!
module_modulename_REHASH (REQUIRED)
Called to make the module reparse any config options.
Parameters:
None
Return status:
0 = Rehashed correctly
1 = Non fatal error for the bot itself. The bot will call UNLOAD on the module.
2 = Fatal error of some kind. On this the bot will quit.
module_modulename_FINALISE
Called directly before the bot quits. The bot has already closed
the connection to the IRC server at this point. This is for saving
stuff.
Parameters:
None
Return status:
Not checked.
module_modulename_after_load
Called after all the hooks are added for the module.
Parameters:
None
Return status:
0 = Unloaded correctly
1 = Failed. On this the bot will call unload on the module.
Notes:
This is useful for stuff that needs to echo log messages
as that can't be done in modulename_INIT.
module_modulename_before_connect
Called before the bot connected.
Parameters:
None
Return status:
Not checked.
module_modulename_on_connect
Called for each line that the bot
receives from the server during connecting.
Parameters:
$1 = Raw line.
Return status:
Not checked.
module_modulename_after_connect
Called after the bot connected.
Parameters:
None
Return status:
Not checked.
module_modulename_on_module_UNLOAD
Called when *ANOTHER* module is got unloaded.
This is meant for a provider module that must unregister some entry
at unload. For example m_help.sh would be such a module.
Parameters:
$1 = The module that got unloaded.
Return code:
Not checked.
module_modulename_on_PRIVMSG
Called when bot gets a PRIVMSG
Parameters:
$1 = From who (n!u@h)
$2 = To who (channel or botnick)
$3 = The message
Return code:
0 = pass it on to next module
1 = I have taken care of this, and don't
consult other modules about this.
module_modulename_on_NOTICE
Called when bot gets a NOTICE
Parameters:
$1 = From who (n!u@h)
$2 = To who (channel or botnick)
$3 = The message
Return code:
0 = pass it on to next module
1 = I have taken care of this, and don't
consult other modules about this.
module_modulename_on_INVITE (new in 0.0.1-beta5)
Called when bot gets an INVITE
Parameters:
$1 = From who (n!u@h)
$2 = Target nick (probably our own nick).
$3 = Target channel.
Return code:
Not checked.
module_modulename_on_NICK
Called when someone changes nick in a channel the bot is in
Parameters:
$1 = From who (n!u@h)
$2 = New nick
Return status:
Not checked.
Reason: A module with a list of users could get desynced if it didn't
get the nick change.
module_modulename_on_JOIN
Called when someone joins a channel the bot is in
Parameters:
$1 = Who did it (n!u@h)
$2 = What channel
Return status:
Not checked.
Reason: A module with a list of users could get desynced if it didn't
get the join.
module_modulename_on_PART
Called when someone parts a channel the bot is in
Parameters:
$1 = Who did it (n!u@h)
$2 = What channel
$3 = A reason if one exists.
Return status:
Not checked.
Reason: A module with a list of users could get desynced if it didn't
get the part.
module_modulename_on_KICK
Called when someone parts a channel the bot is in
Parameters:
$1 = Who did it (n!u@h).
$2 = What channel.
$3 = Who got kicked.
$4 = A reason if one exists.
Return status:
Not checked.
Reason: A module with a list of users could get desynced if it didn't
get the kick.
module_modulename_on_QUIT
Called when someone quits
Parameters:
$1 = Who did it (n!u@h).
$2 = A reason if one exists.
Return status:
Not checked.
Reason: A module with a list of users could get desynced if it didn't
get the quit.
module_modulename_on_TOPIC
Called when a topic is set or on channel join
Parameters:
$1 = Who did it (n!u@h).
$2 = What channel.
$3 = New topic.
Return status:
Not checked.
Reason: We don't want to get desynced modules.
module_modulename_on_channel_MODE
Called when someone changes a mode in the channel
Parameters:
$1 = Who did it (n!u@h).
$2 = On what channel.
$3 = The mode change with it's parameters.
Return status:
Not checked.
Reason: A module with a list of users could get desynced if it didn't
get the mode change.
module_modulename_on_user_MODE (new in 0.0.1-beta4)
Called when someone changes a user mode (most likely on ourself)
Parameters:
$1 = Who did it (n!u@h).
$2 = What target (nick).
$3 = The mode change with it's parameters.
Return status:
Not checked.
Reason: There is no point in checking this.
module_modulename_on_numeric
Called on any numeric after connect.
Parameters:
$1 = The numeric.
$2 = It's data.
Return status:
0 = pass it on to next module
1 = I have taken care of this, and don't
consult other modules about this.
Notes:
In most cases you DON'T want to return 1 for this. If you do be very very careful.
module_modulename_on_PONG
Called when bot receives a PONG from server
Parameters:
$1 = Sender
$2 = Second server.
$3 = The data.
Return status:
Not checked.
module_modulename_on_server_ERROR
# ERROR :Closing link (rfc3092@1.2.3.4) [Killed (AnMaster (testing a kill on inspircd))]
# ERROR :Closing Link: [1.2.3.4] (Throttled: Reconnecting too fast) -Email staff@example.com for more information.
# ERROR :Closing Link: envbot[host.name.se] Brain ([hurricane.KuoNET.org] Local kill by Brain (testing kill on unrealircd))
# ERROR :Closing Link: envbot[host.name.se] hurricane.KuoNET.org (Killed (Brain (testing kill on unrealircd, remote one)))
Called when the bot get an ERROR from the server.
Parameters:
$1 = The ERROR message.
Return status:
Not checked.
Reason: There isn't much point in swallowing this.
module_modulename_on_KILL
Called when the bot get a KILL message from the server.
Parameters:
$1 = The sender of the kill.
$2 = The target of the kill.
$3 = The KILL path.
$4 = The reason.
Return status:
Not checked.
Reason: Risk for desync of modules.
module_modulename_before_disconnect
Called when the bot is about to disconnect from server.
The bot may or may not reconnect after this depending on what caused this.
the disconnect.
Parameters:
None
Return status:
Not checked.
Notes:
This won't be called if the disconnect wasn't planned, like
a ping timeout, getting killed, and so on.
module_modulename_after_disconnect
Called when the bot gets disconnected from server.
The bot may or may not reconnect after this depending on what caused
the disconnect.
Parameters:
None
Return status:
Not checked.
Reason: A module may need clean up, don't let it desync.
module_modulename_on_RAW
Called when the bot gets *ANY* line from the server after the initial connect.
With this a module can hook onto something not supported by a more specific hook.
The downsides:
* Potentially speed as it has to be called for any message.
* You have to parse more on your own.
* You can desync other modules! Imagine a module that keep a list of
users in the channel. If you swallow a message about a join, part,
quit, nickchange, or other ones it may get desynced!
* Even worse, you can desync the bot itself if you swallow NICK, JOIN
PART, KICK or possibly others. Or make it ping out by swallowing PING.
Parameters:
$1 = The raw line
Return status:
0 = pass it on to next module
1 = I have taken care of this, and don't
consult other modules about this.
For raw this means no normal hooks
will be called on the line either.

15
doc/seen.sql Normal file
View File

@ -0,0 +1,15 @@
/*
* This is how to create a factoid database
* Use this file like this:
* sqlite3 -batch data/envbot.db < doc/seen.sql
*
* If you use another table name, change below in
* both places
*/
DROP TABLE IF EXISTS seen;
CREATE TABLE seen (
nick TEXT UNIQUE NOT NULL PRIMARY KEY,
channel TEXT NOT NULL,
timestamp TEXT NOT NULL,
message TEXT NOT NULL
);

101
doc/transports_api.txt Normal file
View File

@ -0,0 +1,101 @@
Transport modules
=================
Transport modules is a feature that allows different ways to connect to the server.
They may support different systems and/or different features.
File descriptors
================
If a module need to use a file descriptor it should use FD 3 and 4.
If more than two are needed, the module can try higher but that may be used
for other things in the future.
Configuration
=============
Additional configuration variables for a module should follow this naming scheme:
config_transport_transportname_variablename
Example:
config_transport_stunnel_path
Variables
=========
The module defines a variable called $transport_supports
It is a space separated list of supported features. Current ones:
* ipv4 - Supports IPv4
* ipv6 - Supports IPv6
* nossl - Supports making non-SSL connections
* ssl - Supports SSL
* bind - Supports using a specific IP when connecting
This variable only needs to be set after transport_check_support has run
Functions
=========
The function names below should not be unique for the module like with
normal modules. You can only use one transport module at a time.
transport_check_support()
Check if all the stuff needed to use this transport is available
on this system.
Return status:
0 = Yes
1 = No
transport_connect()
Try to connect
Return status:
0 = Connection successful
1 = Connection failed
Parameters:
$1 = Hostname/IP
$2 = Port
$3 = If 1 use SSL. If the module does not support it, just ignore it.
$3 = IP to bind to if any and if supported
If the module does not support it, just ignore it.
transport_disconnect()
Called to close connection
Parameters:
None
Return status:
Not checked.
Notes:
The module must handle this getting called even when not connected or when
partly connected (it should clean up any program the module is running in
the background on a ping time out for example).
transport_alive()
Called to check if connection is still alive.
Return status
0 If connection is still alive.
1 If it isn't alive.
Notes:
This function should be low overhead, it gets called quite often.
transport_read_line()
Return a line in the variable $line.
Return status:
0 = Success
1 = Connection has failed (timeout or whatever)
Notes:
The transport module should remove any trailing \r and/or \n (CR and/or LF)
from the string before it returns it.
transport_write_line()
Send a line
Parameters:
$* = send this
Return status:
Not checked.
Notes:
The transport module should add a \n (LF) to the end of the data it get.

42
envbot Executable file
View File

@ -0,0 +1,42 @@
#!/usr/bin/env bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## This is a wrapper that runs envbot in a sane setup.<br />
## Using a wrapper is really the simplest way to do it.
#---------------------------------------------------------------------
# Default config file
config_file="bot_settings.sh"
# Default place to load libraries from
# Also where to load main envbot from. We won't accept any other location
# for main.sh, but main.sh will for library scripts.
library_dir="lib"
# This work-around is needed on FreeBSD
bash="$(type -p bash)"
# Later arguments on the command line will override the defaults.
# env -i: Clean environment
# TERM="$TERM": But keep TERM
# bash --norc: Don't read profile/bashrc
exec env -i TERM="$TERM" REALHOME="$HOME" "$bash" --norc "${library_dir}/main.sh" --config "$config_file" --libdir "$library_dir" "$@"

99
lib/access.sh Normal file
View File

@ -0,0 +1,99 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Access control library.
#---------------------------------------------------------------------
#---------------------------------------------------------------------
## Check for owner access.
## @Type API
## @param n!u@h mask
## @return 0 If access was granted
## @return 1 If access was denied.
#---------------------------------------------------------------------
access_check_owner() {
debug_log_caller "$@"
security_assert_argc 1 1 "$@" || {
log_error "Aiie! Access denied because of incorrect function call!"
return 1
}
local index
for index in ${!config_access_mask[*]}; do
if [[ "$1" =~ ${config_access_mask[$index]} ]] && list_contains "config_access_capab[$index]" 'owner'; then
return 0
fi
done
return 1
}
#---------------------------------------------------------------------
## Check for access in scope.
## @Type API
## @param Capability to check for.
## @param n!u@h mask
## @param What scope
## @return 0 If access was granted
## @return 1 If access was denied.
#---------------------------------------------------------------------
access_check_capab() {
debug_log_caller "$@"
security_assert_argc 3 3 "$@" || {
log_error "Aiie! Access denied because of incorrect function call!"
return 1
}
local index
for index in ${!config_access_mask[*]}; do
if [[ "$2" =~ ${config_access_mask[$index]} ]] && \
[[ "$3" =~ ${config_access_scope[$index]} ]]; then
if list_contains "config_access_capab[$index]" "$1" || \
list_contains "config_access_capab[$index]" "owner"; then
return 0
fi
fi
done
return 1
}
#---------------------------------------------------------------------
## Used to log actions like "did a rehash" if access was granted.
## @Type API
## @param n!u@h mask
## @param What happened.
#---------------------------------------------------------------------
access_log_action() {
log_info_file owner.log "$1 performed the restricted action: $2"
}
#---------------------------------------------------------------------
## Return error message about failed access to someone, and log it
## @Type API
## @param n!u@h mask
## @param What they tried to do
## @param What capability they need
#---------------------------------------------------------------------
access_fail() {
log_error_file access.log "$1 tried to \"$2\" but lacks access."
local nick=
parse_hostmask_nick "$sender" 'nick'
send_notice "$nick" "Permission denied. You need the capability \"$3\" to do this action."
}

125
lib/channels.sh Normal file
View File

@ -0,0 +1,125 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Channel management.
#---------------------------------------------------------------------
#---------------------------------------------------------------------
## Space separated list of current channels
## @Type API
#---------------------------------------------------------------------
channels_current=""
#---------------------------------------------------------------------
## Join a channel
## @Type API
## @param The channel to join.
## @param Is a channel key, if any.
#---------------------------------------------------------------------
channels_join() {
local channel="$1"
local key=""
[[ -n "$2" ]] && key=" $2"
send_raw "JOIN ${channel}${key}"
}
#---------------------------------------------------------------------
## Part a channel
## @Type API
## @param The channel to part
## @param Is a reason.
#---------------------------------------------------------------------
channels_part() {
local channel="$1"
local reason=""
[[ -n "$2" ]] && reason=" :$2"
send_raw "PART ${channel}${reason}"
}
###########################################################################
# Internal functions to core or this file below this line! #
# Module authors: go away #
###########################################################################
#---------------------------------------------------------------------
## Internal function!
## Adds channels to the list
## @Type Private
## @param The channel to add
#---------------------------------------------------------------------
channels_add() {
channels_current+=" $1"
}
#---------------------------------------------------------------------
## Internal function!
## Removes channels to the list
## @Type Private
## @param The channel to remove
#---------------------------------------------------------------------
channels_remove() {
list_remove channels_current "$1" channels_current
}
#---------------------------------------------------------------------
## Check if we parted, called from main loop
## @Type Private
## @param n!u@h mask
## @param Channel parted.
## @param Reason (ignored).
#---------------------------------------------------------------------
channels_handle_part() {
local whoparted=
parse_hostmask_nick "$1" 'whoparted'
if [[ $whoparted == $server_nick_current ]]; then
channels_remove "$2"
fi
}
#---------------------------------------------------------------------
## Check if we got kicked, called from main loop
## @Type Private
## @param n!u@h mask of kicker
## @param Channel kicked from.
## @param Nick of kicked user
## @param Reason (ignored).
#---------------------------------------------------------------------
channels_handle_kick() {
local whogotkicked="$3"
if [[ $whogotkicked == $server_nick_current ]]; then
channels_remove "$2"
fi
}
#---------------------------------------------------------------------
## Check if we joined, called from main loop
## @Type Private
## @param n!u@h mask
## @param Channel joined.
#---------------------------------------------------------------------
channels_handle_join() {
local whojoined=
parse_hostmask_nick "$1" 'whojoined'
if [[ $whojoined == $server_nick_current ]]; then
channels_add "$2"
fi
}

265
lib/commands.sh Normal file
View File

@ -0,0 +1,265 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Handle registering of commands
#---------------------------------------------------------------------
#---------------------------------------------------------------------
## List of commands (maps to function for the command), a hash
## @Note Dummy variable to document the fact that it is a hash.
## @Type Private
#---------------------------------------------------------------------
commands_list=''
#---------------------------------------------------------------------
## List of functions (by module), a hash
## @Note Dummy variable to document the fact that it is a hash.
## @Type Private
#---------------------------------------------------------------------
commands_modules_functions=''
#---------------------------------------------------------------------
## List of commands (by function), a hash
## @Note Dummy variable to document the fact that it is a hash.
## @Type Private
#---------------------------------------------------------------------
commands_function_commands=''
#---------------------------------------------------------------------
## List of commands (by module)
## @Note Dummy variable to document the fact that it is a hash.
## @Type Private
#---------------------------------------------------------------------
commands_module_commands=''
#---------------------------------------------------------------------
## List of modules (by command)
## @Note Dummy variable to document the fact that it is a hash.
## @Type Private
#---------------------------------------------------------------------
commands_commands_module=''
#---------------------------------------------------------------------
## Comma separated list of all commands
## @Type Semi-private
#---------------------------------------------------------------------
commands_commands=''
# Just unset dummy variables.
unset commands_list commands_modules_functions commands_function_commands commands_module_commands commands_module_commands
#---------------------------------------------------------------------
## Register a command.
## @Type API
## @param Module name
## @param Function name (Part after module_modulename_handler_)
## @param Command name (on IRC, may contain spaces) (optional, defaults to same as function name, that is $2)
## @return 0 If successful
## @return 1 If failed for other reason
## @return 2 If invalid command name
## @return 3 If the command already exists (maybe from some other module)
## @return 4 If the function already exists for other command.
## @return 5 If the function in question is not declared.
#---------------------------------------------------------------------
commands_register() {
# Speed isn't that important here, it is only called at module load after all.
local module="$1"
local function_name="$2"
local command_name="$3"
# Command name is optional
if [[ -z $command_name ]]; then
command_name="$function_name"
fi
# Check for valid command name
if ! [[ $command_name =~ ^[a-zA-Z0-9] ]]; then
log_error "commands_register_command: Module \"$module\" gave invalid command name \"$command_name\". First char of command must be alphanumeric."
return 2
fi
if ! [[ $command_name =~ ^[a-zA-Z0-9][^\ ,]*( [^, ]+)?$ ]]; then
log_error "commands_register_command: Module \"$module\" gave invalid command name \"$command_name\". A command can be at most 2 words and should have no trailing white space and may not contain a \",\" (comma)."
return 2
fi
# Bail out if command is already registered.
if hash_exists 'commands_list' "$command_name"; then
log_error "commands_register_command: Failed to register command from \"$module\": a command with the name \"$command_name\" already exists."
return 3
fi
# Bail out if the function already is mapped to some other command
if hash_exists 'commands_function_commands' "$function_name"; then
log_error "commands_register_command: Failed to register command from \"$module\": the function is already registered under another command name."
return 4
fi
# Does the function itself exist?
local full_function_name="module_${module}_handler_${function_name}"
if ! declare -F | grep -qe "^declare -f ${full_function_name}$"; then
log_error "commands_register_command: Failed to register command from \"$module\": the function $full_function_name does not exist"
return 5
fi
# So it was valid. Lets add it then.
# Store in module -> function mapping.
hash_append 'commands_modules_functions' "$module" "$function_name" || {
log_error "commands_register_command: module -> commands mapping failed: mod=\"$module\" func=\"$function_name\"."
return 1
}
# Store in command -> function mapping
hash_set 'commands_list' "$command_name" "$full_function_name" || {
log_error "commands_register_command: command -> function mapping failed: cmd=\"$command_name\" full_func=\"$full_function_name\"."
return 1
}
# Store in function -> command mapping
hash_set 'commands_function_commands' "$function_name" "$command_name" || {
log_error "commands_register_command: function -> command mapping failed: func=\"$function_name\" cmd=\"$command_name\"."
return 1
}
# Store in command -> module mapping
hash_set 'commands_commands_module' "$command_name" "$module" || {
log_error "commands_register_command: command -> module mapping failed: cmd=\"$command_name\" mod=\"$module\"."
return 1
}
# Store in module -> commands mapping (ick!)
hash_append 'commands_module_commands' "$module" "$command_name" ',' || {
log_error "commands_register_command: module -> command mapping failed: mod=\"$module\" cmd=\"$command_name\"."
}
# Store in comma-separated command list
if [[ $commands_commands ]]; then
commands_commands+=",$command_name" || return 1
else
commands_commands="$command_name" || return 1
fi
}
#---------------------------------------------------------------------
## Get what module provides a command.
## @param Command to find.
## @param Variable to return in
## @Type Semi-private
#---------------------------------------------------------------------
commands_provides() {
hash_get "commands_commands_module" "$1" "$2"
}
#---------------------------------------------------------------------
## Get what commands exist in a module.
## @param Command to find.
## @param Variable to return comma separated list in
## @Type Semi-private
#---------------------------------------------------------------------
commands_in_module() {
hash_get "commands_module_commands" "$1" "$2"
}
#---------------------------------------------------------------------
## Will remove all commands from a module and unset the functions in question.
## @Type Private
## @param Module
## @return 0 If successful (or no commands exist for module)
## @return 1 If error
## @return 2 If fatal error
#---------------------------------------------------------------------
commands_unregister() {
local module="$1"
# Are there any commands for the module?
hash_exists 'commands_modules_functions' "$module" || {
return 0
}
local function_name full_function_name command_name functions
# Get list of functions
hash_get 'commands_modules_functions' "$module" 'functions' || return 2
# Iterate through the functions
for function_name in $functions; do
# Get command name
hash_get 'commands_function_commands' "$function_name" 'command_name' || return 2
# Unset from function -> command hash
hash_unset 'commands_function_commands' "$function_name" || return 2
# Unset from command -> function hash
hash_unset 'commands_list' "$command_name" || return 2
# Unset from command -> module mapping
hash_unset 'commands_commands_module' "$command_name" || return 2
# Remove from command list.
list_remove 'commands_commands' "$command_name" 'commands_commands' "," || return 1
# Unset help strings (if any):
unset helpentry_${module}_${function_name}_syntax
unset helpentry_${module}_${function_name}_description
# Unset function itself.
full_function_name="module_${module}_handler_${function_name}"
unset "$full_function_name" || return 2
done
# Unset the module -> commands mapping.
hash_unset 'commands_module_commands' "$module" || return 2
# Finally unset module -> functions mapping.
hash_unset 'commands_modules_functions' "$module" || return 2
}
#---------------------------------------------------------------------
## Process a line finding what command it would be
## @Type Private
## @param Sender
## @param Target
## @param Query
## @return 0 If not a command
## @return 1 If it indeed was a command that we therefore handled.
## @return 2 A command but that didn't exist.
#---------------------------------------------------------------------
commands_call_command() {
local regex="${config_commands_listenregex}"
# Not on a channel?
if [[ ! $2 =~ ^# ]]; then
# Should we treat it as a command anyway?
if [[ $config_commands_private_always == 1 ]]; then
local regex="(${config_commands_listenregex})?"
fi
fi
# Check if it is a command.
# (${config_commands_listenregex}, followed by an alphanumeric char.)
if [[ "$3" =~ ^${regex}([a-zA-Z0-9].*) ]]; then
local data="${BASH_REMATCH[@]: -1}"
# Right, get the parts of the command
if [[ $data =~ ^([a-zA-Z0-9][^ ]*)( [^, ]+)?( .*)? ]]; then
local firstword="${BASH_REMATCH[1]}"
local secondword="${BASH_REMATCH[2]}"
local parameters="${BASH_REMATCH[3]}"
local function=
# Check for two word commands first.
hash_get 'commands_list' "${firstword}${secondword}" 'function'
if [[ -z "$function" ]]; then
# Maybe one word then?
hash_get 'commands_list' "$firstword" 'function'
if [[ "$function" ]]; then
parameters="${secondword}${parameters}"
# No, not that either
else
return 2
fi
fi
# So we got a command, now lets run it
# (strip leading white spaces) from parameters.
"$function" "$1" "$2" "${parameters## }"
return 1
fi
return 2
fi
return 0
}

219
lib/config.sh Normal file
View File

@ -0,0 +1,219 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Configuration management
#---------------------------------------------------------------------
#---------------------------------------------------------------------
## Rehash config file.
## @Type API
## @return 0 Success.
## @return 2 Not same config version.
## @return 3 Failed to source. The bot should not be in an undefined state.
## @return 4 Config validation on faked source failed. The bot should not be in an undefined state.
## @return 5 Failed to source. The bot may be in an undefined state.
## @Note If config validation fails at REAL source, the bot may quit. However this should never happen.
#---------------------------------------------------------------------
config_rehash() {
local new_conf_ver="$(grep -E '^config_version=' "$config_file")"
if ! [[ $new_conf_ver =~ ^config_version=$config_current_version ]]; then
log_error "REHASH: Not same config version. Rehash aborted."
return 2
fi
# Try sourceing in a subshell first to catch errors
# without causing bot to break
( source "$config_file" )
if [[ $? -ne 0 ]]; then
log_error "REHASH: Failed faked source. Rehash aborted. (TIP: Check for syntax errors in config and any message above this message.)"
return 3
fi
# HACK: Subshell, then unset all but two config_ variables (one is readonly, the other is needed to validate)
# Then source config file and run validation on it.
( unset -v $(sed 's/ *config_current_version */ /g;s/ *config_file */ /g' <<<"${!config_*}")
source "$config_file"
config_validate && config_validate_transport )
if [[ $? -ne 0 ]]; then
log_error "REHASH: Failed config validation on new config. Rehash aborted."
return 4
fi
# Source for real if that worked
source "$config_file"
if [[ $? -ne 0 ]]; then
log_error "REHASH: Failed real source. BOT MAY BE IN UNDEFINED STATE."
return 5
fi
# Lets force command line -v, it may have been overwritten by config.
if [[ $force_verbose -eq 1 ]]; then
config_log_stdout='1'
fi
local status
modules_load_from_config
for module in $modules_loaded; do
module_${module}_REHASH
status=$?
if [[ $status -eq 1 ]]; then
log_error "Rehash of ${module} failed, trying to unload it."
modules_unload "${module}" || {
log_fatal "Unloading of ${module} after failed rehash failed."
bot_quit "Fatal error in unload of module that failed to rehash"
}
fi
if [[ $status -eq 2 ]]; then
log_fatal "Rehash of ${module} failed in a FATAL way. Quitting"
bot_quit "Fatal error in rehash of module"
fi
done
log_info_stdout "Rehash successful"
}
###########################################################################
# Internal functions to core or this file below this line! #
# Module authors: go away #
###########################################################################
#---------------------------------------------------------------------
## This will call logging if logging is setup,
## otherwise just print to STDOUT, with prefix
## @Type Private
#---------------------------------------------------------------------
config_dolog_fatal() {
if [[ $log_file ]]; then
log_fatal "$1"
else
echo "FATAL ERROR: $1"
fi
}
#---------------------------------------------------------------------
## Returns an error if the variable in question is empty/not set
## @Note Works only for non-array variables
## @Type Private
## @param Variable name
## @param Extra error line(s) to append (optional, one parameter for each extra line)
#---------------------------------------------------------------------
config_validate_check_exists() {
if [[ -z "${!1}" ]]; then
config_dolog_fatal "YOU MUST SET $1 IN THE CONFIG"
shift
# Do the rest of the messages
local line=
for line in "$@"; do
config_dolog_fatal "$line"
done
envbot_quit 2
fi
}
#---------------------------------------------------------------------
## Validate config file
## @Type Private
#---------------------------------------------------------------------
config_validate() {
# Note: normal logging is not initialized yet at this point,
# so we use config_dolog_fatal, that calls normal logging in case
# logging is loaded (like rehash).
# General settings
config_validate_check_exists config_firstnick
config_validate_check_exists config_ident
config_validate_check_exists config_gecos
# Server settings
config_validate_check_exists config_server
config_validate_check_exists config_server_port
config_validate_check_exists config_server_ssl
# Logging
config_validate_check_exists config_log_dir
config_validate_check_exists config_log_stdout
config_validate_check_exists config_log_raw
config_validate_check_exists config_log_colors
# Commands
config_validate_check_exists config_commands_listenregex
config_validate_check_exists config_commands_private_always
# Feedback
config_validate_check_exists config_feedback_unknown_commands
# Access
if [[ -z "${config_access_mask[1]}" ]]; then
config_dolog_fatal "YOU MUST SET AT LEAST ONE OWNER IN EXAMPLE CONFIG"
config_dolog_fatal "AND THAT OWNER MUST BE THE FIRST ONE (config_access_mask[1] that is)."
envbot_quit 1
fi
if ! list_contains "config_access_capab[1]" "owner"; then
config_dolog_fatal "YOU MUST SET AT LEAST ONE OWNER IN EXAMPLE CONFIG"
config_dolog_fatal "AND THAT OWNER MUST BE THE FIRST ONE (config_access_capab[1] that is)."
envbot_quit 1
fi
# Transports
config_validate_check_exists "config_transport_dir"
if [[ ! -d "${config_transport_dir}" ]]; then
config_dolog_fatal "The transport directory ${config_transport_dir} doesn't seem to exist"
envbot_quit 2
fi
config_validate_check_exists "config_transport"
if [[ ! -r "${config_transport_dir}/${config_transport}.sh" ]]; then
config_dolog_fatal "The transport ${config_transport} doesn't seem to exist"
envbot_quit 2
fi
# Modules
config_validate_check_exists config_modules_dir
if ! [[ -d "$config_modules_dir" ]]; then
if ! list_contains transport_supports "bind"; then
config_dolog_fatal "$config_modules_dir DOES NOT EXIST OR IS NOT A DIRECTORY."
envbot_quit 1
fi
fi
config_validate_check_exists config_modules
}
#---------------------------------------------------------------------
## Validate some settings from config file that can only be done after
## transport was loaded.
## @Type Private
#---------------------------------------------------------------------
config_validate_transport() {
# At this point logging is enabled, we can use it.
if [[ $config_server_ssl -ne 0 ]]; then
if ! list_contains transport_supports "ssl"; then
log_fatal "THIS TRANSPORT DOES NOT SUPORT SSL"
envbot_quit 1
fi
else
if ! list_contains transport_supports "nossl"; then
log_fatal "THIS TRANSPORT REQUIRES SSL"
envbot_quit 1
fi
fi
if [[ "$config_server_bind" ]]; then
if ! list_contains transport_supports "bind"; then
log_fatal "THIS TRANSPORT DOES NOT SUPORT BINDING AN IP"
envbot_quit 1
fi
fi
}

84
lib/debug.sh Normal file
View File

@ -0,0 +1,84 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Functions used during development for debugging.
#---------------------------------------------------------------------
#---------------------------------------------------------------------
## Debugging function to check that right number of parameters were
## provided.
## @param Lowest allowed count of parameters.
## @param Higest allowed count of parameters. (Optional, defaults to same as lower)
#---------------------------------------------------------------------
debug_assert_argc() {
[[ $envbot_debugging ]] || return 0
if [[ ${BASH_ARGC[1]} -lt $1 || ${BASH_ARGC[1]} -gt ${2:-$1} ]]; then
log_debug "${FUNCNAME[1]} should have had $1 parameters but had ${BASH_ARGC[1]} instead"
log_debug "${FUNCNAME[1]} was called from ${BASH_SOURCE[2]}:${BASH_LINENO[1]} ${FUNCNAME[2]}."
return 1
fi
}
#---------------------------------------------------------------------
## Reports who called function and with what arguments.
## @Type API
## @param Should be "$@" at first line of function.
#---------------------------------------------------------------------
debug_log_caller() {
[[ $envbot_debugging ]] || return 0
log_debug "${FUNCNAME[1]} called from ${BASH_SOURCE[2]}:${BASH_LINENO[1]} ${FUNCNAME[2]} with arguments: $*"
}
###########################################################################
# Internal functions to core or this file below this line! #
# Module authors: go away #
###########################################################################
#---------------------------------------------------------------------
## Enable debugging.
## @Type Private
#---------------------------------------------------------------------
debug_enable() {
envbot_debugging=1
shopt -s extdebug
log_debug "Debugging enabled"
}
#---------------------------------------------------------------------
## Disable debugging.
## @Type Private
#---------------------------------------------------------------------
debug_disable() {
envbot_debugging=''
shopt -u extdebug
log_debug "Debugging disabled"
}
#---------------------------------------------------------------------
## Enable or disable debugging at startup.
## @Type Private
#---------------------------------------------------------------------
debug_init() {
if [[ "$envbot_debugging" ]]; then
debug_enable
fi
}

59
lib/feedback.sh Normal file
View File

@ -0,0 +1,59 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## User feedback.
#---------------------------------------------------------------------
#---------------------------------------------------------------------
## Return a message that syntax was bad and what the correct syntax is.
## @Type API
## @param To who (nick or channel)
## @param From what command
## @param Syntax help
#---------------------------------------------------------------------
feedback_bad_syntax() {
send_notice "$1" "Syntax error. Correct syntax for $2 is $2 $3"
}
#---------------------------------------------------------------------
## Return a message that something else was wrong in the command.
## @Type API
## @param To who (nick or channel)
## @param From what function
## @param Error message.
#---------------------------------------------------------------------
feedback_generic_error() {
send_notice "$1" "$2: Error: $3"
}
#---------------------------------------------------------------------
## Return a message that a command was unknown.
## @Type Private
## @param Sender of message (n!u@h)
## @param To where (botnick or channel)
## @param Query
#---------------------------------------------------------------------
feedback_unknown_command() {
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
send_notice "$sendernick" "Error: Not able to parse this command: \"$3\". Are you sure you spelled it correctly?"
}

312
lib/hash.sh Normal file
View File

@ -0,0 +1,312 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Functions for working with associative arrays.
#---------------------------------------------------------------------
#---------------------------------------------------------------------
## Convert a string to hex
## @Type Private
## @param String to convert
## @param Name of variable to return result in.
#---------------------------------------------------------------------
hash_hexify() {
# Res will contain full output string, hex current char.
local hex i res=
for ((i=0;i<${#1};i++)); do
# The ' is not documented in bash but it works.
# See http://www.opengroup.org/onlinepubs/009695399/utilities/printf.html
# for documentation of the ' syntax for printf.
printf -v hex '%x' "'${1:i:1}"
# Add to string
res+=$hex
done
# Print to variable.
printf -v "$2" '%s' "$res"
}
#---------------------------------------------------------------------
## Convert a string from hex to normal
## @Type Private
## @param String to convert
## @param Name of variable to return result in.
#---------------------------------------------------------------------
hash_unhexify() {
# Res will contain full output string, unhex current char.
local unhex i=0 res=
for ((i=0;i<${#1};i+=2)); do
# Convert back from hex. 2 chars at a time
# FIXME: This will break if output would be multibyte chars.
printf -v unhex \\"x${1:i:2}"
res+=$unhex
done
printf -v "$2" '%s' "$res"
}
#---------------------------------------------------------------------
## Generate variable name for a item in the hash array.
## @Type Private
## @param Table name
## @param Index
## @param Name of variable to return result in.
#---------------------------------------------------------------------
hash_name_create() {
local hexindex
hash_hexify "$2" 'hexindex'
printf -v "$3" '%s' "hsh_${1}_${hexindex}"
}
#---------------------------------------------------------------------
## Translate a variable name to an entry index name.
## @param Variable name
## @param Return value for index
#---------------------------------------------------------------------
hash_name_getindex() {
local unhexindex tablename indexname
local IFS="_"
read -r tablename indexname <<< "${1/hsh_//}"
unset IFS
hash_unhexify "$indexname" "$2"
}
#---------------------------------------------------------------------
## Sets (overwrites any older) a value in a hash array
## @Type API
## @param Table name
## @param Index
## @param Value
#---------------------------------------------------------------------
hash_set() {
local varname
# Get variable name
hash_name_create "$1" "$2" 'varname'
# Set it using the printf to variable
printf -v "$varname" '%s' "$3"
}
#---------------------------------------------------------------------
## Append a value to the end of an entry in a hash array
## @Type API
## @param Table name
## @param Index
## @param Value to append
## @param Separator (optional, defaults to space)
#---------------------------------------------------------------------
hash_append() {
local varname
# Get variable name
hash_name_create "$1" "$2" 'varname'
# Append to end, or if empty just set.
if [[ "${!varname}" ]]; then
local sep=${4:-" "}
printf -v "$varname" '%s' "${!varname}${sep}${3}"
else
printf -v "$varname" '%s' "$3"
fi
}
#---------------------------------------------------------------------
## Opposite of <@function hash_append>, removes a value from a list
## in a hash entry
## @Type API
## @param Table name
## @param Index
## @param Value to remove
## @param Separator (optional, defaults to space)
#---------------------------------------------------------------------
hash_substract() {
local varname
# Get variable name
hash_name_create "$1" "$2" 'varname'
# If not empty try to remove value
if [[ "${!varname}" ]]; then
local sep=${4:-" "}
# FIXME: substrings of the entries in the list may match :/
local list="${!varname}"
list="${list//$3}"
# Remove any double $sep caused by this.
list="${list//$sep$sep/$sep}"
printf -v "$varname" '%s' "$list"
fi
}
#---------------------------------------------------------------------
## Replace a value in list style hash entry.
## @Type API
## @param Table name
## @param Index
## @param Value to replace
## @param Value to replace with
## @param Separator (optional, defaults to space)
#---------------------------------------------------------------------
hash_replace() {
local varname
# Get variable name
hash_name_create "$1" "$2" 'varname'
# Append to end, or if empty just set.
local sep=${5:-" "}
if [[ "${!varname}" =~ (^|$sep)${3}($sep|$) ]]; then
# FIXME: substrings of the entries in the list may match :/
local list="${!varname}"
list="${list//$3/$4}"
printf -v "$varname" '%s' "$list"
fi
}
#---------------------------------------------------------------------
## Removes an entry (if it exists) from a hash array
## @Note If the entry does not exist, nothing will happen
## @Type API
## @param Table name
## @param Index
#---------------------------------------------------------------------
hash_unset() {
local varname
# Get variable name
hash_name_create "$1" "$2" 'varname'
unset "${varname}"
}
#---------------------------------------------------------------------
## Gets a value (if it exists) from a hash array
## @Note If value does not exist, the variable will be empty.
## @Type API
## @param Table name
## @param Index
## @param Name of variable to return result in.
#---------------------------------------------------------------------
hash_get() {
local varname
# Get variable name
hash_name_create "$1" "$2" 'varname'
# Now print out to variable using indirect ref to get the value.
printf -v "$3" '%s' "${!varname}"
}
#---------------------------------------------------------------------
## Check if a list style hash entry contains a specific value.
## @Type API
## @param Table name
## @param Index
## @param Value to check for
## @param Separator (optional, defaults to space)
## @return 0 Found
## @return 1 Not found (or hash doesn't exist).
#---------------------------------------------------------------------
hash_contains() {
local varname
# Get variable name
hash_name_create "$1" "$2" 'varname'
local sep=${4:-" "}
if [[ "${sep}${!varname}${sep}" =~ ${sep}${3}${sep} ]]; then
return 0
else
return 1
fi
}
#---------------------------------------------------------------------
## Check if a any space separated entry in a hash array contains
## a specific value.
## @Type API
## @param Table name
## @param Value to check for
## @return 0 Found
## @return 1 Not found (or hash doesn't exist).
#---------------------------------------------------------------------
hash_search() {
# Get variable names
eval "local vars=\"\${!hsh_${1}_*}\""
# Append to end, or if empty just set.
if [[ $vars ]]; then
local var
# Extract index.
for var in $vars; do
[[ "${!varname}" =~ (^| )${2}( |$) ]] && return 0
done
fi
return 1
}
#---------------------------------------------------------------------
## Check if an entry exists in a hash array
## @Type API
## @param Table name
## @param Index
## @return 0 If the entry exists
## @return 1 If the entry doesn't exist
#---------------------------------------------------------------------
hash_exists() {
local varname
hash_name_create "$1" "$2" 'varname'
# This will return the return code we want.
[[ "${!varname}" ]]
}
#---------------------------------------------------------------------
## Removes an entire hash array
## @Type API
## @param Table name
## @return 0 Ok
## @return 1 Other error
## @return 2 Table not found
#---------------------------------------------------------------------
hash_reset() {
# Get all variables with a prefix
eval "local vars=\"\${!hsh_${1}_*}\""
# If any variable, unset them.
if [[ $vars ]]; then
unset ${vars} || return 1
else
return 2
fi
}
#---------------------------------------------------------------------
## Returns a space separated list of the indices of a hash array
## @Type API
## @param Table name
## @param Name of variable to return result in.
## @return 0 Ok
## @return 1 Other error
## @return 2 Table not found
#---------------------------------------------------------------------
hash_get_indices() {
# Get all variables with a prefix
eval "local vars=\"\${!hsh_${1}_*}\""
# If any variable loop through and get the "normal" index.
if [[ $vars ]]; then
local var unhexname returnlist
# Extract index.
for var in $vars; do
hash_name_getindex "$var" 'unhexname'
returnlist+=" $unhexname"
done
# Return them in variable.
printf -v "$2" '%s' "${returnlist}"
return 0
else
return 2
fi
}

285
lib/log.sh Normal file
View File

@ -0,0 +1,285 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Logging API
#---------------------------------------------------------------------
#---------------------------------------------------------------------
## Log a fatal error to the main log file as well as STDOUT.
## @Type API
## @param The log message to log
#---------------------------------------------------------------------
log_fatal() {
log "FATAL " "$log_color_fatal" "$1" 1
}
#---------------------------------------------------------------------
## Log a fatal error to a specific log file as well as
## the main log file and STDOUT.
## @Type API
## @param The extra log file (relative to the current log dir)
## @param The log message to log
#---------------------------------------------------------------------
log_fatal_file() {
log "FATAL " "$log_color_fatal" "$2" 1 "$1"
}
#---------------------------------------------------------------------
## Log an error to the main log file as well as STDOUT.
## @Type API
## @param The log message to log
#---------------------------------------------------------------------
log_error() {
log "ERROR " "$log_color_error" "$1" 1
}
#---------------------------------------------------------------------
## Log an error to a specific log file as well as
## the main log file and STDOUT.
## @Type API
## @param The extra log file (relative to the current log dir)
## @param The log message to log
#---------------------------------------------------------------------
log_error_file() {
log "ERROR " "$log_color_error" "$2" 1 "$1"
}
#---------------------------------------------------------------------
## Log a warning to the main log file as well as STDOUT.
## @Type API
## @param The log message to log
#---------------------------------------------------------------------
log_warning() {
log "WARNING " "$log_color_warning" "$1" 1
}
#---------------------------------------------------------------------
## Log a warning to a specific log file as well as
## the main log file and STDOUT.
## @Type API
## @param The extra log file (relative to the current log dir)
## @param The log message to log
#---------------------------------------------------------------------
log_warning_file() {
log "WARNING " "$log_color_warning" "$2" 1 "$1"
}
#---------------------------------------------------------------------
## Log an info message to the main log file.
## @Type API
## @param The log message to log
#---------------------------------------------------------------------
log_info() {
log "INFO " "$log_color_info" "$1" 0
}
#---------------------------------------------------------------------
## Log an info message to the main log file and STDOUT.
## Normally this shouldn't be used by modules.
## It is used for things like "Connecting"
## @Type API
## @param The log message to log
#---------------------------------------------------------------------
log_info_stdout() {
log "INFO " "$log_color_info" "$1" 1
}
#---------------------------------------------------------------------
## Log an info message to a specific log file as well as
## the main log file and STDOUT.
## Normally this shouldn't be used by modules.
## It is used for things like "Connecting"
## @Type API
## @param The extra log file (relative to the current log dir)
## @param The log message to log
#---------------------------------------------------------------------
log_info_stdout_file() {
log "INFO " "$log_color_info" "$2" 1 "$1"
}
#---------------------------------------------------------------------
## Log an info message to a specific log file as well as STDOUT.
## @Type API
## @param The extra log file (relative to the current log dir)
## @param The log message to log
#---------------------------------------------------------------------
log_info_file() {
log "INFO " "$log_color_info" "$2" 0 "$1"
}
#---------------------------------------------------------------------
## Log a debug message.
## @Type API
## @param The log message to log
#---------------------------------------------------------------------
log_debug() {
log "DEBUG " "" "$1" 0 debug.log
}
###########################################################################
# Internal functions to core or this file below this line! #
# Module authors: go away #
###########################################################################
#---------------------------------------------------------------------
## Logging prefix
## @Type Private
#---------------------------------------------------------------------
log_prefix="-"
#---------------------------------------------------------------------
## Get human readable date.
## @Type Private
## @Stdout Human readable date
#---------------------------------------------------------------------
log_get_date() {
date +'%Y-%m-%d %k:%M:%S'
}
#---------------------------------------------------------------------
## Get escape codes from tput
## @Type Private
## @param capname
## @param Return variable name
## @return 0 OK
## @return 1 Not supported or unknown cap
## @Note Return variable will be unset if the value is not supported
#---------------------------------------------------------------------
log_check_cap() {
tput $1 >/dev/null 2>&1
if [[ $? -eq 0 ]]; then
printf -v "$2" '%s' "$(tput $1)"
else
printf -v "$2" '%s' ''
fi
}
#---------------------------------------------------------------------
## Log, internal to this file.
## @Type Private
## @param Level to log at (ERROR or such, aligned to space)
## @param Color of level
## @param The log message to log
## @param Force log to stdout (0 or 1)
## @param Optional extra file to log to.
#---------------------------------------------------------------------
log() {
# Log file is set?
[[ $log_file ]] || return 0
# Log date.
local logdate="$(log_get_date)"
# ncm = No Color Message
local ncm="$log_prefix $logdate ${1}${3}"
echo "$ncm" >> "$log_file"
# Extra log file?
[[ $5 ]] && echo "$ncm" >> "$log_dir/$5"
# STDOUT?
if [[ $config_log_stdout -eq 1 || $4 -eq 1 ]]; then
# Colors and then get rid of bell chars.
echo "${log_color_std}${log_prefix}${log_color_none} $logdate ${2}${1}${log_color_none}${3//$'\007'}"
fi
}
#---------------------------------------------------------------------
## Used internally in core to log raw line
## @Type Private
## @param Line to log
#---------------------------------------------------------------------
log_raw_in() {
[[ $config_log_raw = 1 ]] && log_raw "<" "$log_color_in" "$1"
}
#---------------------------------------------------------------------
## Used internally in core to log raw line
## @Type Private
## @param Line to log
#---------------------------------------------------------------------
log_raw_out() {
[[ $config_log_raw = 1 ]] && log_raw ">" "$log_color_out" "$1"
}
#---------------------------------------------------------------------
## Internal function to this file.
## @Type Private
## @param Prefix to use
## @param Color of prefix
## @param Message to log
#---------------------------------------------------------------------
log_raw() {
# Log file is set?
[[ $log_file ]] || return 0
# No Color Message
# Log date.
local logdate="$(log_get_date)"
# No colors for file
echo "$1 $logdate $3" >> "$log_dir/raw.log"
# STDOUT?
if [[ $config_log_stdout -eq 1 ]]; then
# Get rid of bell chars.
echo "${2}${1}${log_color_none} $logdate RAW ${3//$'\007'}"
fi
}
#---------------------------------------------------------------------
## Create log file.
## @Type Private
#---------------------------------------------------------------------
log_init() {
local now
time_get_current 'now'
# This creates log dir for this run:
log_dir="${config_log_dir}/${now}"
# Security, the log may contain passwords.
mkdir -m 700 "$log_dir"
if [[ $? -ne 0 ]]; then
echo "Error: couldn't create log dir"
envbot_quit 1
fi
log_file="${log_dir}/main.log"
touch "$log_file"
if [[ $? -ne 0 ]]; then
echo "Error: couldn't create logfile"
envbot_quit 1
fi
# Should there be colors?
if [[ $config_log_colors -eq 1 ]]; then
local bold
# Generate colors
log_check_cap sgr0 log_color_none # No colour
log_check_cap bold bold # Bold local
log_check_cap 'setaf 1' log_color_error # Red
log_color_fatal="${log_color_error}${bold}" # Red bold
log_check_cap 'setaf 3' log_color_warning # Yellow
log_check_cap 'setaf 2' log_color_info # Green
log_check_cap 'setaf 4' log_color_std # Blue bold, for standard prefix
log_color_std+="${bold}"
log_check_cap 'setaf 5' log_color_in # Magenta, for prefix
log_check_cap 'setaf 6' log_color_out # Cyan, for prefix
fi
log_info_stdout "Log directory is $log_dir"
}

566
lib/main.sh Normal file
View File

@ -0,0 +1,566 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## This is the main file, it should be called with a wrapper (envbot)
#---------------------------------------------------------------------
###################
# #
# Sanity checks #
# #
###################
# Error to fail with for old bash.
fail_old_bash() {
echo "Sorry your bash version is too old!"
echo "You need at least version 3.2.10 of bash"
echo "Please install a newer version:"
echo " * Either use your distro's packages"
echo " * Or see http://www.gnu.org/software/bash/"
exit 2
}
# Check bash version. We need at least 3.2.10
# Lets not use anything like =~ here because
# that may not work on old bash versions.
if [[ "${BASH_VERSINFO[0]}${BASH_VERSINFO[1]}" -lt 32 ]]; then
fail_old_bash
elif [[ "${BASH_VERSINFO[0]}${BASH_VERSINFO[1]}" -eq 32 && "${BASH_VERSINFO[2]}" -lt 10 ]]; then
fail_old_bash
fi
# We should not run as root.
if [[ $EUID -eq 0 ]]; then
echo "ERROR: Don't run envbot as root. Please run it under a normal user. Really."
exit 1
fi
######################
# #
# Set up variables #
# #
######################
# Version and URL
#---------------------------------------------------------------------
## Version of envbot.
## @Type API
## @Read_only Yes
#---------------------------------------------------------------------
declare -r envbot_version='0.1-beta1'
#---------------------------------------------------------------------
## Homepage of envbot.
## @Type API
## @Read_only Yes
#---------------------------------------------------------------------
declare -r envbot_homepage='http://envbot.org'
##############
# #
# Sane env #
# #
##############
# Set some variables to make bot work sane
# For example tr + some LC_COLLATE = breaks in some cases.
unset LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY
unset LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS
unset LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION
export LC_ALL=C
export LANG=C
# Some of these may be overkill, but better be on
# safe side.
set +amu
set -f
shopt -u sourcepath hostcomplete progcomp xpg_echo dotglob
shopt -u nocasematch nocaseglob nullglob
shopt -s extquote promptvars extglob
# If you need some other PATH, override in top of config...
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# To make set -x more usable
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]} : '
# This is needed when we run the bot with env -i as recommended.
declare -r tmp_home="$(mktemp -dt envbot.home.XXXXXXXXXX)"
# I don't want to end up with rm -rf $HOME in case it is something
# else at that point, so lets use another variable.
# Temp trap on ctrl-c until the next "stage" of trap gets loaded (at connect)
trap 'rm -rvf "$tmp_home"; exit 1' TERM INT
#---------------------------------------------------------------------
## Now create a temp function to quit on problems in a way that cleans up
## temp stuff until we have loaded enough to use the normal function bot_quit.
## @param Return status of bot
#---------------------------------------------------------------------
envbot_quit() {
rm -rf "$tmp_home"
exit "$1"
}
# And finally lets export this as $HOME
export HOME="$tmp_home"
#---------------------------------------------------------------------
## Will be set to 1 if -v or --verbose is passed
## on command line.
## @Type Private
#---------------------------------------------------------------------
force_verbose=0
#---------------------------------------------------------------------
## Store command line for later use
## @Type Private
#---------------------------------------------------------------------
command_line=( "$@" )
# Some constants used in different places
#---------------------------------------------------------------------
## Current config version.
## @Type API
## @Read_only Yes
#---------------------------------------------------------------------
declare -r config_current_version=17
#---------------------------------------------------------------------
## In progress of quitting? This is used to
## work around the issue in bug 25.<br />
## -1 means not even in main loop yet.
## @Type Private
#---------------------------------------------------------------------
envbot_quitting=-1
#---------------------------------------------------------------------
## If empty debugging is turned off. If not empty it is on.
#---------------------------------------------------------------------
envbot_debugging=''
#---------------------------------------------------------------------
## Print help message
## @Type Private
#---------------------------------------------------------------------
print_cmd_help() {
echo 'envbot is an advanced modular IRC bot coded in bash.'
echo ''
echo 'Usage: envbot [OPTION]...'
echo ''
echo 'Options:'
echo ' -c, --config file Use file instead of the default as config file.'
echo ' -l, --libdir directory Use directory instead of the default as library directory.'
echo ' -v, --verbose Force verbose output even if config_log_stdout is 0.'
echo ' -d, --debug Enable debugging code. Most likely pointless to anyone'
echo ' except envbot developers or module developers.'
echo ' -h, --help Display this help and exit'
echo ' -V, --version Output version information and exit'
echo ''
echo "Note that envbot can't handle short versions of options being written together like"
echo "-vv currently."
echo ''
echo 'Exit status is 0 if OK, 1 if minor problems, 2 if serious trouble.'
echo ''
echo 'Examples:'
echo ' envbot Runs envbot with default options.'
echo ' envbot -c bot.config Runs envbot with the config bot.config.'
echo ''
echo "Report bugs to ${envbot_homepage}/trac/simpleticket"
envbot_quit 0
}
#---------------------------------------------------------------------
## Print version message
## @Type Private
#---------------------------------------------------------------------
print_version() {
echo "envbot $envbot_version - An advanced modular IRC bot in bash."
echo ''
echo 'Copyright (C) 2007-2008 Arvid Norlander'
echo 'Copyright (C) 2007-2008 EmErgE'
echo 'This is free software; see the source for copying conditions. There is NO'
echo 'warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.'
echo ''
echo 'Written by Arvid Norlander and EmErgE.'
envbot_quit 0
}
# Parse any command line arguments.
if [[ $# -gt 0 ]]; then
while [[ $# -gt 0 ]]; do
case "$1" in
'--help'|'-help'|'--usage'|'-usage'|'-h')
print_cmd_help
;;
'--config'|'-c')
config_file="$2"
shift 2
;;
'--debug'|'-d')
envbot_debugging=1
shift 1
;;
'--libdir'|'-l')
library_dir="$2"
shift 2
;;
'--verbose'|'-v')
force_verbose=1
shift 1
;;
'--version'|'-V')
print_version
;;
*)
print_cmd_help
;;
esac
done
fi
echo "Loading... Please wait"
if [[ -z "$config_file" ]]; then
echo "ERROR: No config file set, you probably didn't use the wrapper program to start envbot"
envbot_quit 1
fi
if [[ ! -r "$config_file" ]]; then
echo "ERROR: Can't read config file ${config_file}."
echo "Check that it is really there and correct permissions are set."
echo "If you used --config to specify name of config file, check that you spelled it correctly."
envbot_quit 1
fi
echo "Loading config"
source "$config_file"
if [[ $? -ne 0 ]]; then
echo "Error: couldn't load config from $config_file"
envbot_quit 1
fi
# This is hackish, it should be in config.sh (config_validate)
# The reason is that we need to check some things before we can load config.sh
if [[ -z "$config_version" ]]; then
echo "ERROR: YOU MUST SET THE CORRECT config_version IN THE CONFIG"
envbot_quit 2
fi
if [[ $config_version -ne $config_current_version ]]; then
echo "ERROR: YOUR config_version IS $config_version BUT THE BOT'S CONFIG VERSION IS $config_current_version."
echo "PLEASE UPDATE YOUR CONFIG. Check bot_settings.sh.example for current format."
envbot_quit 2
fi
# Force verbose output if -v or --verbose was on
# command line.
if [[ $force_verbose -eq 1 ]]; then
config_log_stdout='1'
fi
# Must be checked here and not in validate_config because of
# loading order.
if [[ -z "$library_dir" ]]; then
echo "ERROR: No library directory set, you probably didn't use the wrapper program to start envbot"
envbot_quit 1
fi
if [[ ! -d "$library_dir" ]]; then
echo "ERROR: library directory $library_dir does not exist, is not a directory or can't be read for some other reason."
echo "Check that it is really there and correct permissions are set."
echo "If you used --libdir to specify location of library directory, check that you spelled it correctly."
envbot_quit 2
fi
echo "Loading library functions"
# Load library functions.
libraries="hash time log send feedback numerics channels parse \
access misc config commands modules server debug"
for library in $libraries; do
source "${library_dir}/${library}.sh"
done
unset library
# Validate other config variables.
config_validate
time_init
log_init
debug_init
log_info_stdout "Loading transport"
source "${config_transport_dir}/${config_transport}.sh"
if [[ $? -ne 0 ]]; then
log_fatal "Couldn't load transport. Couldn't load the file..."
envbot_quit 2
fi
if ! transport_check_support; then
log_fatal "The transport reported it can't work on this system or with this configuration."
log_fatal "Please read any other errors displayed above and consult documentation for the transport module you are using."
envbot_quit 2
fi
# Now logging functions can be used.
# Load modules
log_info_stdout "Loading modules"
# Load modules
modules_load_from_config
#---------------------------------------------------------------------
## This can be used when the code does not need exact time.
## It will be updated each time the bot get a new line of
## data.
## @Type API
#---------------------------------------------------------------------
envbot_time=''
server_connected_before=0
while true; do
# In progress of quitting? This is used to
# work around the issue in bug 25.
envbot_quitting=0
for module in $modules_before_connect; do
module_${module}_before_connect
done
if [[ $server_connected_before -ne 0 ]]; then
# We got here by being connected before and
# loosing connection, keep retrying
while true; do
if server_connect; then
server_connected_before=1
break
else
log_error "Failed to reconnect, trying again in 20 seconds"
sleep 20
fi
done
else
# In this case abort on failure to connect, likely bad config.
# and most likely the user is present to fix it.
# If someone disagrees I may change it.
server_connect || {
log_error "Connection failed"
envbot_quit 1
}
server_connected_before=1
fi
trap 'bot_quit "Interrupted (Ctrl-C)"' INT
trap 'bot_quit "Terminated (SIGTERM)"' TERM
for module in $modules_after_connect; do
module_${module}_after_connect
done
while true; do
line=
transport_read_line
transport_status="$?"
# Still connected?
if ! transport_alive; then
break
fi
time_get_current 'envbot_time'
# Did we timeout waiting for data
# or did we get data?
if [[ $transport_status -ne 0 ]]; then
continue
fi
log_raw_in "$line"
for module in $modules_on_raw; do
module_${module}_on_raw "$line"
if [[ $? -ne 0 ]]; then
# TODO: Check that this does what it should.
continue 2
fi
done
if [[ $line =~ ^:${server_name}\ +([0-9]{3})\ +([^ ]+)\ +(.*) ]]; then
# this is a numeric
numeric="${BASH_REMATCH[1]}"
numericdata="${BASH_REMATCH[3]}"
server_handle_numerics "$numeric" "${BASH_REMATCH[2]}" "$numericdata"
for module in $modules_on_numeric; do
module_${module}_on_numeric "$numeric" "$numericdata"
if [[ $? -ne 0 ]]; then
break
fi
done
elif [[ "$line" =~ ^:([^ ]*)\ +PRIVMSG\ +([^:]+)\ +:(.*) ]]; then
sender="${BASH_REMATCH[1]}"
target="${BASH_REMATCH[2]}"
query="${BASH_REMATCH[3]}"
# Check if there is a command.
commands_call_command "$sender" "$target" "$query"
# Check return code
case $? in
1)
continue
;;
2)
if [[ $config_feedback_unknown_commands -eq 0 ]]; then
continue
elif [[ $config_feedback_unknown_commands -eq 1 ]]; then
feedback_unknown_command "$sender" "$target" "$query"
fi
;;
esac
for module in $modules_on_PRIVMSG; do
module_${module}_on_PRIVMSG "$sender" "$target" "$query"
if [[ $? -ne 0 ]]; then
break
fi
done
elif [[ "$line" =~ ^:([^ ]*)\ +NOTICE\ +([^:]+)\ +:(.*) ]]; then
sender="${BASH_REMATCH[1]}"
target="${BASH_REMATCH[2]}"
query="${BASH_REMATCH[3]}"
for module in $modules_on_NOTICE; do
module_${module}_on_PRIVMSG "$sender" "$target" "$query"
if [[ $? -ne 0 ]]; then
break
fi
done
elif [[ "$line" =~ ^:([^ ]*)\ +TOPIC\ +(#[^ ]+)(\ +:(.*))? ]]; then
sender="${BASH_REMATCH[1]}"
channel="${BASH_REMATCH[2]}"
topic="${BASH_REMATCH[4]}"
for module in $modules_on_TOPIC; do
module_${module}_on_TOPIC "$sender" "$channel" "$topic"
done
elif [[ "$line" =~ ^:([^ ]*)\ +MODE\ +(#[^ ]+)\ +(.+) ]]; then
sender="${BASH_REMATCH[1]}"
channel="${BASH_REMATCH[2]}"
modes="${BASH_REMATCH[3]}"
for module in $modules_on_channel_MODE ; do
module_${module}_on_channel_MODE "$sender" "$channel" "$modes"
done
elif [[ "$line" =~ ^:([^ ]*)\ +MODE\ +([^# ]+)\ +(.+) ]]; then
sender="${BASH_REMATCH[1]}"
target="${BASH_REMATCH[2]}"
modes="${BASH_REMATCH[3]}"
for module in $modules_on_user_MODE ; do
module_${module}_on_user_MODE "$sender" "$target" "$modes"
done
elif [[ "$line" =~ ^:([^ ]*)\ +INVITE\ +([^ ]+)\ +:?(.+) ]]; then
sender="${BASH_REMATCH[1]}"
target="${BASH_REMATCH[2]}"
channel="${BASH_REMATCH[3]}"
for module in $modules_on_INVITE; do
module_${module}_on_INVITE "$sender" "$target" "$channel"
done
elif [[ "$line" =~ ^:([^ ]*)\ +NICK\ +:?(.+) ]]; then
sender="${BASH_REMATCH[1]}"
newnick="${BASH_REMATCH[2]}"
# Check if it was our own nick
server_handle_nick "$sender" "$newnick"
for module in $modules_on_NICK; do
module_${module}_on_NICK "$sender" "$newnick"
done
elif [[ "$line" =~ ^:([^ ]*)\ +JOIN\ +:?(.*) ]]; then
sender="${BASH_REMATCH[1]}"
channel="${BASH_REMATCH[2]}"
# Check if it was our own nick that joined
channels_handle_join "$sender" "$channel"
for module in $modules_on_JOIN; do
module_${module}_on_JOIN "$sender" "$channel"
done
elif [[ "$line" =~ ^:([^ ]*)\ +PART\ +(#[^ ]+)(\ +:(.*))? ]]; then
sender="${BASH_REMATCH[1]}"
channel="${BASH_REMATCH[2]}"
reason="${BASH_REMATCH[4]}"
# Check if it was our own nick that parted
channels_handle_part "$sender" "$channel" "$reason"
for module in $modules_on_PART; do
module_${module}_on_PART "$sender" "$channel" "$reason"
done
elif [[ "$line" =~ ^:([^ ]*)\ +KICK\ +(#[^ ]+)\ +([^ ]+)(\ +:(.*))? ]]; then
sender="${BASH_REMATCH[1]}"
channel="${BASH_REMATCH[2]}"
kicked="${BASH_REMATCH[3]}"
reason="${BASH_REMATCH[5]}"
# Check if it was our own nick that got kicked
channels_handle_kick "$sender" "$channel" "$kicked" "$reason"
for module in $modules_on_KICK; do
module_${module}_on_KICK "$sender" "$channel" "$kicked" "$reason"
done
elif [[ "$line" =~ ^:([^ ]*)\ +QUIT(\ +:(.*))? ]]; then
sender="${BASH_REMATCH[1]}"
reason="${BASH_REMATCH[3]}"
for module in $modules_on_QUIT; do
module_${module}_on_QUIT "$sender" "$reason"
done
elif [[ "$line" =~ ^:([^ ]*)\ +KILL\ +([^ ]*)\ +:([^ ]*)\ +\((.*)\) ]]; then
sender="${BASH_REMATCH[1]}"
target="${BASH_REMATCH[2]}"
path="${BASH_REMATCH[3]}"
reason="${BASH_REMATCH[4]}"
# I don't think we need to check if we were the target or not,
# the bot doesn't need to care as far as I can see.
for module in $modules_on_KILL; do
module_${module}_on_KILL "$sender" "$target" "$path" "$reason"
done
elif [[ "$line" =~ ^:([^ ]*)\ +PONG\ +([^ ]*)\ +:?(.*)$ ]]; then
sender="${BASH_REMATCH[1]}"
server2="${BASH_REMATCH[2]}"
data="${BASH_REMATCH[3]}"
for module in $modules_on_PONG; do
module_${module}_on_PONG "$sender" "$server2" "$data"
done
elif [[ $line =~ ^[^:] ]] ;then
# ERROR?
if [[ "$line" =~ ^ERROR\ +:(.*) ]]; then
error="${BASH_REMATCH[1]}"
log_error "Got ERROR from server: $error"
for module in $modules_on_server_ERROR; do
module_${module}_on_server_ERROR "$error"
done
# If we get an ERROR we can assume we are disconnected.
break
# PING? If not report as unhandled
elif ! server_handle_ping "$line"; then
log_info_file unknown_data.log "A non-sender prefixed line that didn't match any hook: $line"
fi
else
log_info_file unknown_data.log "Something that didn't match any hook: $line"
fi
done
if [[ $envbot_quitting -ne 0 ]]; then
# Hm, a trap got aborted it seems.
# Trying to handle this.
log_info "Quit trap got aborted: envbot_quitting=${envbot_quitting}. Recovering"
bot_quit
break
fi
log_error 'DIED FOR SOME REASON'
transport_disconnect
server_connected=0
for module in $modules_after_disconnect; do
module_${module}_after_disconnect
done
# Don't reconnect right away. We might get throttled and other nasty stuff.
sleep 10
done
rm -rf "$tmp_home"

267
lib/misc.sh Normal file
View File

@ -0,0 +1,267 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Misc functions.
#---------------------------------------------------------------------
# Some codes for IRC formatting
#---------------------------------------------------------------------
## IRC formatting: Bold
## @Type API
#---------------------------------------------------------------------
format_bold=$'\002'
#---------------------------------------------------------------------
## IRC formatting: Underline
## @Type API
#---------------------------------------------------------------------
format_underline=$'\037'
#---------------------------------------------------------------------
## IRC formatting: Color
## @Type API
#---------------------------------------------------------------------
format_color=$'\003'
#---------------------------------------------------------------------
## IRC formatting: Inverse
## @Type API
#---------------------------------------------------------------------
format_inverse=$'\026'
#---------------------------------------------------------------------
## IRC formatting: Restore to normal
## @Type API
#---------------------------------------------------------------------
format_normal=$'\017'
#---------------------------------------------------------------------
## IRC formatting: ASCII bell
## Please. Don't. Abuse. This.
## @Type API
#---------------------------------------------------------------------
format_bell=$'\007'
# Color table:
# white 0
# black 1
# blue 2
# green 3
# red 4
# darkred 5
# purple 6
# darkyellow 7
# yellow 8
# brightgreen 9
# darkaqua 10
# aqua 11
# lightblue 12
# brightpurple 13
# darkgrey 14
# lightgrey 15
#---------------------------------------------------------------------
## This will add colors around this text.
## @Type API
## @param Foreground color
## @param Background color
## @param String to colorise
#---------------------------------------------------------------------
format_colorise() {
echo "${format_color}${1},${2}${3}${format_normal}"
}
#---------------------------------------------------------------------
## Quits the bot in a graceful way.
## @Type API
## @param Reason to quit (optional)
## @param Return status (optional, if not given, then exit 0).
#---------------------------------------------------------------------
bot_quit() {
# Yes this function is odd but there is a reason.
# If this is called from a trap like Ctrl-C we must be able to
# resume.
# Keep track of in what state we are
while true; do
case "$envbot_quitting" in
0)
for module in $modules_before_disconnect; do
module_${module}_before_disconnect
done
(( envbot_quitting++ ))
;;
1)
local reason="$1"
send_quit "$reason"
sleep 1
(( envbot_quitting++ ))
;;
2)
server_connected=0
for module in $modules_after_disconnect; do
module_${module}_after_disconnect
done
(( envbot_quitting++ ))
;;
3)
for module in $modules_FINALISE; do
module_${module}_FINALISE
done
(( envbot_quitting++ ))
;;
4)
log_info_stdout "Bot quit gracefully"
transport_disconnect
(( envbot_quitting++ ))
;;
# -1 is before main loop entered,
# may happen during module loading
5|-1)
rm -rvf "$tmp_home"
if [[ $2 ]]; then
exit $2
else
exit 0
fi
;;
*)
log_error "Um. bot_quit() and envbot_quitting is $envbot_quitting. This shouldn't happen."
log_error "Please report a bug including the last 40 lines or so of log and what you did to cause it."
# Quit and clean up temp files.
envbot_quit 2
;;
esac
done
}
#---------------------------------------------------------------------
## Restart the bot in a graceful way. I hope.
## @Type API
## @param Reason to restart (optional)
#---------------------------------------------------------------------
bot_restart() {
for module in $modules_before_disconnect; do
module_${module}_before_disconnect
done
local reason="$1"
send_quit "$reason"
sleep 1
server_connected=0
for module in $modules_after_disconnect; do
module_${module}_after_disconnect
done
for module in $modules_FINALISE; do
module_${module}_FINALISE
done
log_info_stdout "Bot quit gracefully"
transport_disconnect
rm -rvf "$tmp_home"
exec env -i TERM="$TERM" "$(type -p bash)" $0 "${command_line[@]}"
}
#---------------------------------------------------------------------
## Strip leading/trailing spaces.
## @Type API
## @Note Before this function was deprecated, but it has been recoded
## @Note in a much faster way. This version is not compatible with old
## @Note version.
## @param String to strip
## @param Variable to return in
#---------------------------------------------------------------------
misc_clean_spaces() {
# Fastest way that is still secure
local array
read -ra array <<< "$1"
printf -v "$2" '%s' "${array[*]}"
}
#---------------------------------------------------------------------
## Strip leading/trailing separator.
## @Type API
## @param String to strip
## @param Variable to return in
## @param Separator
#---------------------------------------------------------------------
misc_clean_delimiter() {
local sep="$3" array
local IFS="$sep"
# Fastest way that is still secure
read -ra array <<< "$1"
local tmp="${array[*]}"
printf -v "$2" '%s' "${tmp#${sep}}"
}
#---------------------------------------------------------------------
## Remove a value from a space (or other delimiter) separated list.
## @Type API
## @param List to remove from.
## @param Value to remove.
## @param Variable to return new list in.
## @param Separator (optional, defaults to space)
#---------------------------------------------------------------------
list_remove() {
local sep=${4:-" "}
local oldlist="${sep}${!1}${sep}"
local newlist="${oldlist//${sep}${2}${sep}/${sep}}"
misc_clean_delimiter "$newlist" "$3" "$sep" # Get rid of the unneeded spaces.
}
#---------------------------------------------------------------------
## Checks if a space separated list contains a value.
## @Type API
## @param List to check.
## @param Value to check for.
## @return 0 If found.
## @return 1 If not found.
#---------------------------------------------------------------------
list_contains() {
[[ " ${!1} " = *" $2 "* ]]
}
###########################################################################
# Internal functions to core or this file below this line! #
# Module authors: go away #
###########################################################################
#---------------------------------------------------------------------
## Like debug_assert_argc but works without debugging on.
## For use in sensitive functions in core.
## @Type Private
## @param Minimum count of parameters
## @param Maximum count of parameters
## @param All the rest of the parameters as "$@"
## @Example For example this could be called as:
## @Example <pre>
## @Example foo() {
## @Example security_assert_argc 2 2 "$@"
## @Example ... rest of function ...
## @Example }
## @Example </pre>
#---------------------------------------------------------------------
security_assert_argc() {
local min="$1" max="$2"
shift 2
if [[ $# -lt $min || $# -gt $max ]]; then
log_error "Security sensitive function ${FUNCNAME[1]} should have had between $min and $max parameters but had $# instead."
log_error "Security sensitive function ${FUNCNAME[1]} was called from ${BASH_SOURCE[2]}:${BASH_LINENO[1]} ${FUNCNAME[2]} with these parameters: $*"
log_error "This should be reported as a bug."
return 1
fi
return 0
}

447
lib/modules.sh Normal file
View File

@ -0,0 +1,447 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Modules management
#---------------------------------------------------------------------
#---------------------------------------------------------------------
## List of loaded modules. Don't change from other code.
## @Type Semi-private
#---------------------------------------------------------------------
modules_loaded=""
#---------------------------------------------------------------------
## Current module API version.
#---------------------------------------------------------------------
declare -r modules_current_API=2
#---------------------------------------------------------------------
## Call from after_load with a list of modules that you depend on
## @Type API
## @param What module you are calling from.
## @param Space separated list of modules you depend on
## @return 0 Success
## @return 1 Other error. You should return 1 from after_load.
## @return 2 One or several of the dependencies could found. You should return 1 from after_load.
## @return 3 Not all of the dependencies could be loaded (modules exist but did not load correctly). You should return 1 from after_load.
#---------------------------------------------------------------------
modules_depends_register() {
local callermodule="$1"
local dep
for dep in $2; do
if [[ $dep == $callermodule ]]; then
log_error_file modules.log "To the module author of $callermodule: You can't list yourself as a dependency of yourself!"
log_error_file modules.log "Aborting!"
return 1
fi
if ! list_contains "modules_loaded" "$dep"; then
log_info_file modules.log "Loading dependency of $callermodule: $dep"
modules_load "$dep"
local status="$?"
if [[ $status -eq 4 ]]; then
return 2
elif [[ $status -ne 0 ]]; then
return 3
fi
fi
if list_contains "modules_depends_${dep}" "$callermodule"; then
log_warning_file modules.log "Dependency ${callermodule} already listed as depending on ${dep}!?"
fi
# Use printf not eval here.
local listname="modules_depends_${dep}"
printf -v "modules_depends_${dep}" '%s' "${!listname} $callermodule"
done
}
#---------------------------------------------------------------------
## Call from after_load or INIT with a list of modules that you
## depend on optionally.
## @Type API
## @param What module you are calling from.
## @param The module you want to depend on optionally.
## @return 0 Success, module loaded
## @return 1 User didn't list it as loaded, don't use the features in question
## @return 2 Other error. You should return 1 from after_load.
## @return 3 One or several of the dependencies could found. You should return 1 from after_load.
## @return 4 Not all of the dependencies could be loaded (modules exist but did not load correctly). You should return 1 from after_load.
#---------------------------------------------------------------------
modules_depends_register_optional() {
local callermodule="$1"
local dep="$2"
if ! list_contains "modules_loaded" "$dep"; then
# So not loaded, now we need to find out if we should load it or not
# We use $config_modules for it
if ! list_contains 'config_modules' "$dep"; then
log_info_file modules.log "Optional dependency of $callermodule ($dep) not loaded."
return 1
fi
log_info_file modules.log "Loading optional dependency of $callermodule: ($dep)"
fi
# Ah we should load it then? Call modules_depends_register
modules_depends_register "$@"
}
#---------------------------------------------------------------------
## Semi internal!
## List modules that depend on another module.
## @Type Semi-private
## @param Module to check
## @Stdout List of modules that depend on this.
#---------------------------------------------------------------------
modules_depends_list_deps() {
# This is needed to be able to use indirect refs
local deplistname="modules_depends_${1}"
# Clean out spaces, fastest way
echo ${!deplistname}
}
###########################################################################
# Internal functions to core or this file below this line! #
# Module authors: go away #
# See doc/module_api.txt instead #
###########################################################################
#---------------------------------------------------------------------
## Used by unload to unregister from depends system
## (That is: remove from list of "depended on by" of other modules)
## @Type Private
## @param Module to unregister
#---------------------------------------------------------------------
modules_depends_unregister() {
local module newval
for module in $modules_loaded; do
if list_contains "modules_depends_${module}" "$1"; then
list_remove "modules_depends_${module}" "$1" "modules_depends_${module}"
fi
done
}
#---------------------------------------------------------------------
## Check if a module can be unloaded
## @Type Private
## @param Name of module to check
## @return Can be unloaded
## @return Is needed by some other module.
#---------------------------------------------------------------------
modules_depends_can_unload() {
# This is needed to be able to use indirect refs
local deplistname="modules_depends_${1}"
# Not empty/only whitespaces?
if ! [[ ${!deplistname} =~ ^\ *$ ]]; then
return 1
fi
return 0
}
#---------------------------------------------------------------------
## Add hooks for a module
## @Type Private
## @param Module name
## @param MODULE_BASE_PATH, exported to INIT as a part of the API
## @return 0 Success
## @return 1 module_modulename_INIT returned non-zero
## @return 2 Module wanted to register an unknown hook.
#---------------------------------------------------------------------
modules_add_hooks() {
local module="$1"
local modinit_HOOKS
local modinit_API
local MODULE_BASE_PATH="$2"
module_${module}_INIT "$module"
[[ $? -ne 0 ]] && { log_error_file modules.log "Failed to get initialize module \"$module\""; return 1; }
# Check if it didn't set any modinit_API, in that case it is a API 1 module.
if [[ -z $modinit_API ]]; then
log_error "Please upgrade \"$module\" to new module API $modules_current_API. This old API is obsolete and no longer supported."
return 1
elif [[ $modinit_API -ne $modules_current_API ]]; then
log_error "Current module API version is $modules_current_API, but the API version of \"$module\" is $module_API."
return 1
fi
local hook
for hook in $modinit_HOOKS; do
case $hook in
"FINALISE")
modules_FINALISE+=" $module"
;;
"after_load")
modules_after_load+=" $module"
;;
"before_connect")
modules_before_connect+=" $module"
;;
"on_connect")
modules_on_connect+=" $module"
;;
"after_connect")
modules_after_connect+=" $module"
;;
"before_disconnect")
modules_before_disconnect+=" $module"
;;
"after_disconnect")
modules_after_disconnect+=" $module"
;;
"on_module_UNLOAD")
modules_on_module_UNLOAD+=" $module"
;;
"on_server_ERROR")
modules_on_server_ERROR+=" $module"
;;
"on_NOTICE")
modules_on_NOTICE+=" $module"
;;
"on_PRIVMSG")
modules_on_PRIVMSG+=" $module"
;;
"on_TOPIC")
modules_on_TOPIC+=" $module"
;;
"on_channel_MODE")
modules_on_channel_MODE+=" $module"
;;
"on_user_MODE")
modules_on_user_MODE+=" $module"
;;
"on_INVITE")
modules_on_INVITE+=" $module"
;;
"on_JOIN")
modules_on_JOIN+=" $module"
;;
"on_PART")
modules_on_PART+=" $module"
;;
"on_KICK")
modules_on_KICK+=" $module"
;;
"on_QUIT")
modules_on_QUIT+=" $module"
;;
"on_KILL")
modules_on_KILL+=" $module"
;;
"on_NICK")
modules_on_NICK+=" $module"
;;
"on_numeric")
modules_on_numeric+=" $module"
;;
"on_PONG")
modules_on_PONG+=" $module"
;;
"on_raw")
modules_on_raw+=" $module"
;;
*)
log_error_file modules.log "Unknown hook $hook requested. Module may malfunction. Module will be unloaded"
return 2
;;
esac
done
}
#---------------------------------------------------------------------
## List of all the optional hooks.
## @Type Private
#---------------------------------------------------------------------
modules_hooks="FINALISE after_load before_connect on_connect after_connect before_disconnect after_disconnect on_module_UNLOAD on_server_ERROR on_NOTICE on_PRIVMSG on_TOPIC on_channel_MODE on_user_MODE on_INVITE on_JOIN on_PART on_KICK on_QUIT on_KILL on_NICK on_numeric on_PONG on_raw"
#---------------------------------------------------------------------
## Unload a module
## @Type Private
## @param Module name
## @return 0 Unloaded
## @return 2 Module not loaded
## @return 3 Can't unload, some other module depends on this.
## @Note If the unload fails for other reasons the bot will quit.
#---------------------------------------------------------------------
modules_unload() {
local module="$1"
local hook newval to_unset
if ! list_contains "modules_loaded" "$module"; then
log_warning_file modules.log "No such module as $1 is loaded."
return 2
fi
if ! modules_depends_can_unload "$module"; then
log_error_file modules.log "Can't unload $module because these module(s) depend(s) on it: $(modules_depends_list_deps "$module")"
return 3
fi
# Remove hooks from list first in case unloading fails so we can do quit hooks if something break.
for hook in $modules_hooks; do
# List so we can unset.
if list_contains "modules_${hook}" "$module"; then
to_unset+=" module_${module}_${hook}"
fi
list_remove "modules_${hook}" "$module" "modules_${hook}"
done
commands_unregister "$module" || {
log_fatal_file modules.log "Could not unregister commands for ${module}"
bot_quit "Fatal error in module unload, please see log"
}
module_${module}_UNLOAD || {
log_fatal_file modules.log "Could not unload ${module}, module_${module}_UNLOAD returned ${?}!"
bot_quit "Fatal error in module unload, please see log"
}
unset module_${module}_UNLOAD
unset module_${module}_INIT
unset module_${module}_REHASH
# Unset from list created above.
for hook in $to_unset; do
unset "$hook" || {
log_fatal_file modules.log "Could not unset the hook $hook of module $module!"
bot_quit "Fatal error in module unload, please see log"
}
done
modules_depends_unregister "$module"
list_remove "modules_loaded" "$module" "modules_loaded"
# Call any hooks for unloading modules.
local othermodule
for othermodule in $modules_on_module_UNLOAD; do
module_${othermodule}_on_module_UNLOAD "$module"
done
# Unset help string
unset helpentry_module_${module}_description
return 0
}
#---------------------------------------------------------------------
## Generate awk script to validate module functions.
## @param Module name
## @Type Private
## @return 0 If the file is OK
## @return 1 If the file lacks one of more of the functions.
#---------------------------------------------------------------------
modules_check_function() {
local module="$1"
# This is a one liner. Well mostly. ;)
# We check that the needed functions exist.
awk "function check_found() { if (init && unload && rehash) exit 0 }
/^declare -f module_${module}_INIT$/ { init=1; check_found() }
/^declare -f module_${module}_UNLOAD$/ { unload=1; check_found() }
/^declare -f module_${module}_REHASH$/ { rehash=1; check_found() }
END { if (! (init && unload && rehash)) exit 1 }"
}
#---------------------------------------------------------------------
## Load a module
## @Type Private
## @param Name of module to load
## @return 0 Loaded Ok
## @return 1 Other errors
## @return 2 Module already loaded
## @return 3 Failed to source it in safe subshell
## @return 4 Failed to source it
## @return 5 No such module
## @return 6 Getting hooks failed
## @return 7 after_load failed
## @Note If the load fails in a fatal way the bot will quit.
#---------------------------------------------------------------------
modules_load() {
local module="$1"
if list_contains "modules_loaded" "$module"; then
log_warning_file modules.log "Module ${module} is already loaded."
return 2
fi
# modulebase is exported as MODULE_BASE_PATH
# with ${config_modules_dir} prepended to the
# INIT function, useful for multi-file
# modules, but available for other modules too.
local modulefilename modulebase
if [[ -f "${config_modules_dir}/m_${module}.sh" ]]; then
modulefilename="m_${module}.sh"
modulebase="${modulefilename}"
elif [[ -d "${config_modules_dir}/m_${module}" && -f "${config_modules_dir}/m_${module}/__main__.sh" ]]; then
modulefilename="m_${module}/__main__.sh"
modulebase="m_${module}"
else
log_error_file modules.log "No such module as ${module} exists."
return 5
fi
( source "${config_modules_dir}/${modulefilename}" )
if [[ $? -ne 0 ]]; then
log_error_file modules.log "Could not load ${module}, failed to source it in safe subshell."
return 3
fi
( source "${config_modules_dir}/${modulefilename}" && declare -F ) | modules_check_function "$module"
if [[ $? -ne 0 ]]; then
log_error_file modules.log "Could not load ${module}, it lacks some important functions it should have."
return 3
fi
source "${config_modules_dir}/${modulefilename}"
if [[ $? -eq 0 ]]; then
modules_loaded+=" $module"
modules_add_hooks "$module" "${config_modules_dir}/${modulebase}" || \
{
log_error_file modules.log "Hooks failed for $module"
# Try to unload.
modules_unload "$module" || {
log_fatal_file modules.log "Failed Unloading of $module (that failed to load)."
bot_quit "Fatal error in module unload of failed module load, please see log"
}
return 6
}
if grep -qw "$module" <<< "$modules_after_load"; then
module_${module}_after_load
if [[ $? -ne 0 ]]; then
modules_unload ${module} || {
log_fatal_file modules.log "Unloading of $module that failed after_load failed."
bot_quit "Fatal error in module unload of failed module load (after_load), please see log"
}
return 7
fi
fi
else
log_error_file modules.log "Could not load ${module}, failed to source it."
return 4
fi
}
#---------------------------------------------------------------------
## Load modules from the config
## @Type Private
#---------------------------------------------------------------------
modules_load_from_config() {
local module
IFS=" "
for module in $modules_loaded; do
if ! list_contains config_modules "$module"; then
modules_unload "$module"
fi
done
unset IFS
for module in $config_modules; do
if [[ -f "${config_modules_dir}/m_${module}.sh" || -d "${config_modules_dir}/m_${module}" ]]; then
if ! list_contains modules_loaded "$module"; then
modules_load "$module"
fi
else
log_warning_file modules.log "$module doesn't exist! Removing it from list"
fi
done
}

348
lib/numerics.sh Normal file
View File

@ -0,0 +1,348 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
###########################################################################
# #
# WARNING THIS FILE IS AUTOGENERATED. ANY CHANGES WILL BE OVERWRITTEN! #
# See the source in tools/numerics.txt for comments about some numerics #
# This file was generated with tools/build_numerics.sh #
# #
###########################################################################
#---------------------------------------------------------------------
## Auto-generated list of numerics from tools/numerics.txt<br />
## This file contains a list of numerics that we currently use.
## It is therefore incomplete.<br />
## Because the list of variables in this file is so long, please see
## it's source for more details.
#---------------------------------------------------------------------
##########################
# Name -> number mapping #
##########################
numeric_RPL_WELCOME='001'
numeric_RPL_YOURHOST='002'
numeric_RPL_CREATED='003'
numeric_RPL_MYINFO='004'
numeric_RPL_ISUPPORT='005'
numeric_RPL_MAP='006'
numeric_RPL_MAPEND='007'
numeric_RPL_SNOMASK='008'
numeric_RPL_TRACEUSER='205'
numeric_RPL_STATSCLINE='213'
numeric_RPL_ENDOFSTATS='219'
numeric_RPL_UMODEIS='221'
numeric_RPL_STATSELINE='223'
numeric_RPL_RULES='232'
numeric_RPL_STATSUPTIME='242'
numeric_RPL_STATSCONN='250'
numeric_RPL_LUSERCLIENT='251'
numeric_RPL_LUSEROP='252'
numeric_RPL_LUSERUNKNOWN='253'
numeric_RPL_LUSERCHANNELS='254'
numeric_RPL_LUSERME='255'
numeric_RPL_ADMINME='256'
numeric_RPL_ADMINLOC1='257'
numeric_RPL_ADMINLOC2='258'
numeric_RPL_ADMINEMAIL='259'
numeric_RPL_TRYAGAIN='263'
numeric_RPL_LOCALUSERS='265'
numeric_RPL_GLOBALUSERS='266'
numeric_RPL_SILELIST='271'
numeric_RPL_ENDOFSILELIST='272'
numeric_RPL_AWAY='301'
numeric_RPL_USERHOST='302'
numeric_RPL_ISON='303'
numeric_RPL_TEXT='304'
numeric_RPL_UNAWAY='305'
numeric_RPL_UNAWAY='306'
numeric_RPL_WHOISREGNICK='307'
numeric_RPL_RULESSTART='308'
numeric_RPL_ENDOFRULES='309'
numeric_RPL_WHOISHELPOP='310'
numeric_RPL_WHOISUSER='311'
numeric_RPL_WHOISSERVER='312'
numeric_RPL_WHOISOPERATOR='313'
numeric_RPL_WHOWASUSER='314'
numeric_RPL_ENDOFWHO='315'
numeric_RPL_WHOISIDLE='317'
numeric_RPL_ENDOFWHOIS='318'
numeric_RPL_WHOISCHANNELS='319'
numeric_RPL_WHOISSPECIAL='320'
numeric_RPL_LISTSTART='321'
numeric_RPL_LIST='322'
numeric_RPL_LISTEND='323'
numeric_RPL_CHANNELMODEIS='324'
numeric_RPL_CREATIONTIME='329'
numeric_RPL_WHOISACCOUNT='330'
numeric_RPL_NOTOPIC='331'
numeric_RPL_TOPIC='332'
numeric_RPL_TOPICWHOTIME='333'
numeric_RPL_USERIP='340'
numeric_RPL_INVITING='341'
numeric_RPL_INVITELIST='346'
numeric_RPL_ENDOFINVITELIST='347'
numeric_RPL_EXCEPTLIST='348'
numeric_RPL_ENDOFEXCEPTLIST='349'
numeric_RPL_VERSION='351'
numeric_RPL_WHOREPLY='352'
numeric_RPL_NAMREPLY='353'
numeric_RPL_LINKS='364'
numeric_RPL_ENDOFLINKS='365'
numeric_RPL_ENDOFNAMES='366'
numeric_RPL_BANLIST='367'
numeric_RPL_ENDOFBANLIST='368'
numeric_RPL_ENDOFWHOWAS='369'
numeric_RPL_INFO='371'
numeric_RPL_MOTD='372'
numeric_RPL_ENDOFINFO='374'
numeric_RPL_MOTDSTART='375'
numeric_RPL_ENDOFMOTD='376'
numeric_RPL_WHOISHOST='378'
numeric_RPL_YOUREOPER='381'
numeric_RPL_REHASHING='382'
numeric_RPL_TIME='391'
numeric_RPL_HOSTHIDDEN='396'
numeric_ERR_NOSUCHNICK='401'
numeric_ERR_NOSUCHSERVER='402'
numeric_ERR_NOSUCHCHANNEL='403'
numeric_ERR_CANNOTSENDTOCHAN='404'
numeric_ERR_TOOMANYCHANNELS='405'
numeric_ERR_WASNOSUCHNICK='406'
numeric_ERR_TOOMANYTARGETS='407'
numeric_ERR_NOTEXTTOSEND='412'
numeric_ERR_TOOMANYMATCHES='416'
numeric_ERR_UNKNOWNCOMMAND='421'
numeric_ERR_NOMOTD='422'
numeric_ERR_ERRONEUSNICKNAME='432'
numeric_ERR_NICKNAMEINUSE='433'
numeric_ERR_NICKTOOFAST='438'
numeric_ERR_USERNOTINCHANNEL='441'
numeric_ERR_NOTONCHANNEL='442'
numeric_ERR_USERONCHANNEL='443'
numeric_ERR_SUMMONDISABLED='445'
numeric_ERR_USERSDISABLED='446'
numeric_ERR_NONICKCHANGE='447'
numeric_ERR_NOTFORHALFOPS='460'
numeric_ERR_NEEDMOREPARAMS='461'
numeric_ERR_ALREADYREGISTERED='462'
numeric_ERR_ONLYSERVERSCANCHANGE='468'
numeric_ERR_LINKCHANNEL='470'
numeric_ERR_CHANNELISFULL='471'
numeric_ERR_UNKNOWNMODE='472'
numeric_ERR_INVITEONLYCHAN='473'
numeric_ERR_BANNEDFROMCHAN='474'
numeric_ERR_BADCHANNELKEY='475'
numeric_ERR_NEEDREGGEDNICK='477'
numeric_ERR_BANLISTFULL='478'
numeric_ERR_CANNOTKNOCK='480'
numeric_ERR_NOPRIVILEGES='481'
numeric_ERR_CHANOPRIVSNEEDED='482'
numeric_ERR_ATTACKDENY='484'
numeric_ERR_SECUREONLYCHAN='489'
numeric_ERR_ALLMUSTUSESSL='490'
numeric_ERR_NOOPERHOST='491'
numeric_ERR_NOREJOINONKICK='495'
numeric_ERR_CHANOWNPRIVNEEDED='499'
numeric_ERR_UMODEUNKNOWNFLAG='501'
numeric_ERR_USERSDONTMATCH='502'
numeric_RPL_LOGON='600'
numeric_RPL_LOGOFF='601'
numeric_RPL_WATCHOFF='602'
numeric_RPL_NOWON='604'
numeric_RPL_NOWOFF='605'
numeric_RPL_WATCHLIST='606'
numeric_RPL_ENDOFWATCHLIST='607'
numeric_RPL_WHOISSECURE='671'
numeric_RPL_MODULES='900'
numeric_RPL_ENDOFMODULES='901'
numeric_RPL_COMMANDS='902'
numeric_RPL_ENDOFCOMMANDS='903'
numeric_ERR_CENSORED='936'
numeric_ERR_ALREDYCENSORED='937'
numeric_ERR_NOTCENSORED='938'
numeric_ERR_SPAMFILTERLISTFULL='939'
numeric_RPL_ENDOFSPAMFILTER='940'
numeric_RPL_SPAMFILTER='941'
numeric_ERR_INVALIDNICK='942'
numeric_RPL_SILENCEREMOVED='950'
numeric_RPL_SILENCEADDED='951'
numeric_ERR_ALREADYSILENCE='952'
numeric_ERR_CANNOTDOCOMMAND='972'
numeric_ERR_CANNOTCHANGECHANMODE='974'
##########################
# Number -> name mapping #
##########################
numerics[1]='RPL_WELCOME'
numerics[2]='RPL_YOURHOST'
numerics[3]='RPL_CREATED'
numerics[4]='RPL_MYINFO'
numerics[5]='RPL_ISUPPORT'
numerics[6]='RPL_MAP'
numerics[7]='RPL_MAPEND'
numerics[8]='RPL_SNOMASK'
numerics[205]='RPL_TRACEUSER'
numerics[213]='RPL_STATSCLINE'
numerics[219]='RPL_ENDOFSTATS'
numerics[221]='RPL_UMODEIS'
numerics[223]='RPL_STATSELINE'
numerics[232]='RPL_RULES'
numerics[242]='RPL_STATSUPTIME'
numerics[250]='RPL_STATSCONN'
numerics[251]='RPL_LUSERCLIENT'
numerics[252]='RPL_LUSEROP'
numerics[253]='RPL_LUSERUNKNOWN'
numerics[254]='RPL_LUSERCHANNELS'
numerics[255]='RPL_LUSERME'
numerics[256]='RPL_ADMINME'
numerics[257]='RPL_ADMINLOC1'
numerics[258]='RPL_ADMINLOC2'
numerics[259]='RPL_ADMINEMAIL'
numerics[263]='RPL_TRYAGAIN'
numerics[265]='RPL_LOCALUSERS'
numerics[266]='RPL_GLOBALUSERS'
numerics[271]='RPL_SILELIST'
numerics[272]='RPL_ENDOFSILELIST'
numerics[301]='RPL_AWAY'
numerics[302]='RPL_USERHOST'
numerics[303]='RPL_ISON'
numerics[304]='RPL_TEXT'
numerics[305]='RPL_UNAWAY'
numerics[306]='RPL_UNAWAY'
numerics[307]='RPL_WHOISREGNICK'
numerics[308]='RPL_RULESSTART'
numerics[309]='RPL_ENDOFRULES'
numerics[310]='RPL_WHOISHELPOP'
numerics[311]='RPL_WHOISUSER'
numerics[312]='RPL_WHOISSERVER'
numerics[313]='RPL_WHOISOPERATOR'
numerics[314]='RPL_WHOWASUSER'
numerics[315]='RPL_ENDOFWHO'
numerics[317]='RPL_WHOISIDLE'
numerics[318]='RPL_ENDOFWHOIS'
numerics[319]='RPL_WHOISCHANNELS'
numerics[320]='RPL_WHOISSPECIAL'
numerics[321]='RPL_LISTSTART'
numerics[322]='RPL_LIST'
numerics[323]='RPL_LISTEND'
numerics[324]='RPL_CHANNELMODEIS'
numerics[329]='RPL_CREATIONTIME'
numerics[330]='RPL_WHOISACCOUNT'
numerics[331]='RPL_NOTOPIC'
numerics[332]='RPL_TOPIC'
numerics[333]='RPL_TOPICWHOTIME'
numerics[340]='RPL_USERIP'
numerics[341]='RPL_INVITING'
numerics[346]='RPL_INVITELIST'
numerics[347]='RPL_ENDOFINVITELIST'
numerics[348]='RPL_EXCEPTLIST'
numerics[349]='RPL_ENDOFEXCEPTLIST'
numerics[351]='RPL_VERSION'
numerics[352]='RPL_WHOREPLY'
numerics[353]='RPL_NAMREPLY'
numerics[364]='RPL_LINKS'
numerics[365]='RPL_ENDOFLINKS'
numerics[366]='RPL_ENDOFNAMES'
numerics[367]='RPL_BANLIST'
numerics[368]='RPL_ENDOFBANLIST'
numerics[369]='RPL_ENDOFWHOWAS'
numerics[371]='RPL_INFO'
numerics[372]='RPL_MOTD'
numerics[374]='RPL_ENDOFINFO'
numerics[375]='RPL_MOTDSTART'
numerics[376]='RPL_ENDOFMOTD'
numerics[378]='RPL_WHOISHOST'
numerics[381]='RPL_YOUREOPER'
numerics[382]='RPL_REHASHING'
numerics[391]='RPL_TIME'
numerics[396]='RPL_HOSTHIDDEN'
numerics[401]='ERR_NOSUCHNICK'
numerics[402]='ERR_NOSUCHSERVER'
numerics[403]='ERR_NOSUCHCHANNEL'
numerics[404]='ERR_CANNOTSENDTOCHAN'
numerics[405]='ERR_TOOMANYCHANNELS'
numerics[406]='ERR_WASNOSUCHNICK'
numerics[407]='ERR_TOOMANYTARGETS'
numerics[412]='ERR_NOTEXTTOSEND'
numerics[416]='ERR_TOOMANYMATCHES'
numerics[421]='ERR_UNKNOWNCOMMAND'
numerics[422]='ERR_NOMOTD'
numerics[432]='ERR_ERRONEUSNICKNAME'
numerics[433]='ERR_NICKNAMEINUSE'
numerics[438]='ERR_NICKTOOFAST'
numerics[441]='ERR_USERNOTINCHANNEL'
numerics[442]='ERR_NOTONCHANNEL'
numerics[443]='ERR_USERONCHANNEL'
numerics[445]='ERR_SUMMONDISABLED'
numerics[446]='ERR_USERSDISABLED'
numerics[447]='ERR_NONICKCHANGE'
numerics[460]='ERR_NOTFORHALFOPS'
numerics[461]='ERR_NEEDMOREPARAMS'
numerics[462]='ERR_ALREADYREGISTERED'
numerics[468]='ERR_ONLYSERVERSCANCHANGE'
numerics[470]='ERR_LINKCHANNEL'
numerics[471]='ERR_CHANNELISFULL'
numerics[472]='ERR_UNKNOWNMODE'
numerics[473]='ERR_INVITEONLYCHAN'
numerics[474]='ERR_BANNEDFROMCHAN'
numerics[475]='ERR_BADCHANNELKEY'
numerics[477]='ERR_NEEDREGGEDNICK'
numerics[478]='ERR_BANLISTFULL'
numerics[480]='ERR_CANNOTKNOCK'
numerics[481]='ERR_NOPRIVILEGES'
numerics[482]='ERR_CHANOPRIVSNEEDED'
numerics[484]='ERR_ATTACKDENY'
numerics[489]='ERR_SECUREONLYCHAN'
numerics[490]='ERR_ALLMUSTUSESSL'
numerics[491]='ERR_NOOPERHOST'
numerics[495]='ERR_NOREJOINONKICK'
numerics[499]='ERR_CHANOWNPRIVNEEDED'
numerics[501]='ERR_UMODEUNKNOWNFLAG'
numerics[502]='ERR_USERSDONTMATCH'
numerics[600]='RPL_LOGON'
numerics[601]='RPL_LOGOFF'
numerics[602]='RPL_WATCHOFF'
numerics[604]='RPL_NOWON'
numerics[605]='RPL_NOWOFF'
numerics[606]='RPL_WATCHLIST'
numerics[607]='RPL_ENDOFWATCHLIST'
numerics[671]='RPL_WHOISSECURE'
numerics[900]='RPL_MODULES'
numerics[901]='RPL_ENDOFMODULES'
numerics[902]='RPL_COMMANDS'
numerics[903]='RPL_ENDOFCOMMANDS'
numerics[936]='ERR_CENSORED'
numerics[937]='ERR_ALREDYCENSORED'
numerics[938]='ERR_NOTCENSORED'
numerics[939]='ERR_SPAMFILTERLISTFULL'
numerics[940]='RPL_ENDOFSPAMFILTER'
numerics[941]='RPL_SPAMFILTER'
numerics[942]='ERR_INVALIDNICK'
numerics[950]='RPL_SILENCEREMOVED'
numerics[951]='RPL_SILENCEADDED'
numerics[952]='ERR_ALREADYSILENCE'
numerics[972]='ERR_CANNOTDOCOMMAND'
numerics[974]='ERR_CANNOTCHANGECHANMODE'
# End of generated file.

100
lib/parse.sh Normal file
View File

@ -0,0 +1,100 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Data parsing
#---------------------------------------------------------------------
#---------------------------------------------------------------------
## Get parts of hostmask.
## @Note In most cases you should use one of
## @Note <@function parse_hostmask_nick>, <@function parse_hostmask_ident>
## @Note or <@function parse_hostmask_host>. Only use this function
## @Note if you want all several parts.
## @Type API
## @param n!u@h mask
## @param Variable to return nick in
## @param Variable to return ident in
## @param Variable to return host in
#---------------------------------------------------------------------
parse_hostmask() {
if [[ $1 =~ ^([^ !]+)!([^ @]+)@([^ ]+) ]]; then
printf -v "$2" '%s' "${BASH_REMATCH[1]}"
printf -v "$3" '%s' "${BASH_REMATCH[2]}"
printf -v "$4" '%s' "${BASH_REMATCH[3]}"
fi
}
#---------------------------------------------------------------------
## Get nick from hostmask
## @Type API
## @param n!u@h mask
## @param Variable to return result in
#---------------------------------------------------------------------
parse_hostmask_nick() {
if [[ $1 =~ ^([^ !]+)! ]]; then
printf -v "$2" '%s' "${BASH_REMATCH[1]}"
fi
}
#---------------------------------------------------------------------
## Get ident from hostmask
## @Type API
## @param n!u@h mask
## @param Variable to return result in
#---------------------------------------------------------------------
parse_hostmask_ident() {
if [[ $1 =~ ^[^\ !]+!([^ @]+)@ ]]; then
printf -v "$2" '%s' "${BASH_REMATCH[1]}"
fi
}
#---------------------------------------------------------------------
## Get host from hostmask
## @Type API
## @param n!u@h mask
## @param Variable to return result in
#---------------------------------------------------------------------
parse_hostmask_host() {
if [[ $1 =~ ^[^\ !]+![^\ @]+@([^ ]+) ]]; then
printf -v "$2" '%s' "${BASH_REMATCH[1]}"
fi
}
#---------------------------------------------------------------------
## This is used to get data out of 005.
## @Type API
## @param Name of data to get
## @param Variable to return result (if any result) in
## @return 0 If found otherwise 1
## @Note That if the variable doesn't have any data,
## @Note but still exist it will return nothing on STDOUT
## @Note but 0 as error code
#---------------------------------------------------------------------
parse_005() {
if [[ $server_005 =~ ${1}(=([^ ]+))? ]]; then
if [[ ${BASH_REMATCH[2]} ]]; then
printf -v "$2" '%s' "${BASH_REMATCH[2]}"
fi
return 0
fi
return 1
}

178
lib/send.sh Normal file
View File

@ -0,0 +1,178 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Functions for sending data to server
#---------------------------------------------------------------------
#---------------------------------------------------------------------
## Simple flood limiting.
## Note that this doesn't handle this very well:<br />
## seconds:milliseconds message<br />
## 01:999 message<br />
## 02:001 other message<br />
## Then they get too close.<br />
## I think this won't flood us off though.<br />
## @Type Private
#---------------------------------------------------------------------
send_last=0
#---------------------------------------------------------------------
## Send a "raw" line to the server.
## @Type API
## @param Line to send
#---------------------------------------------------------------------
send_raw() {
# Do the flood limiting
local now=
time_get_current 'now'
if [[ "$send_last" == "$now" ]]; then
sleep 1
fi
time_get_current 'send_last'
send_raw_flood "$*"
}
#---------------------------------------------------------------------
## Send a PRIVMSG
## @Type API
## @param Who (channel or nick)
## @param Message
#---------------------------------------------------------------------
send_msg() {
# Don't do anything if no message
[[ -z $2 ]] && return 0
send_raw "PRIVMSG ${1} :${2}"
}
#---------------------------------------------------------------------
## Send a NOTICE
## @Type API
## @param Who (channel or nick)
## @param Message
#---------------------------------------------------------------------
send_notice() {
# Don't do anything if no message
[[ -z $2 ]] && return 0
send_raw "NOTICE ${1} :${2}"
}
#---------------------------------------------------------------------
## Send a CTCP
## @Type API
## @param Who (channel or nick)
## @param Message
#---------------------------------------------------------------------
send_ctcp() {
# Don't do anything if no message
[[ -z $2 ]] && return 0
send_msg "$1" $'\1'"${2}"$'\1'
}
#---------------------------------------------------------------------
## Send a NCTCP (ctcp reply)
## @Type API
## @param Who (channel or nick)
## @param Message
#---------------------------------------------------------------------
send_nctcp() {
# Don't do anything if no message
[[ -z $2 ]] && return 0
send_notice "$1" $'\1'"${2}"$'\1'
}
#---------------------------------------------------------------------
## Send a NICK to change nick
## @Type API
## @param New nick
#---------------------------------------------------------------------
send_nick() {
send_raw "NICK ${1}"
}
#---------------------------------------------------------------------
## Send a MODE to change umodes.
## @Type API
## @param Modes to send
#---------------------------------------------------------------------
send_umodes() {
send_raw "MODE $server_nick_current $1"
}
#---------------------------------------------------------------------
## Send a MODE to change channel modes.
## @Type API
## @param Target channel
## @param Modes to set
#---------------------------------------------------------------------
send_modes() {
send_raw "MODE $1 $2"
}
#---------------------------------------------------------------------
## Send a TOPIC to change channel topic.
## @Type API
## @param Channel to change topic of
## @param New topic.
#---------------------------------------------------------------------
send_topic() {
send_raw "TOPIC $1 :$2"
}
#---------------------------------------------------------------------
## This is semi-internal only
## This may flood ourself off. Use send_raw instead in most cases.
## Also this doesn't log the actual line, so used for passwords.
## @Type API
## @param What to log instead (example could be: "NickServ IDENTIFY (password)")
## @param The line to send
#---------------------------------------------------------------------
send_raw_flood_nolog() {
log_raw_out "<hidden line from logs>: $1"
transport_write_line "$2"$'\r'
}
#---------------------------------------------------------------------
## This is semi-internal only
## This may flood ourself off. Use send_raw instead in most cases.
## Same syntax as send_raw
## @Type Semi-private
#---------------------------------------------------------------------
send_raw_flood() {
log_raw_out "$*"
transport_write_line "$*"$'\r'
}
###########################################################################
# Internal functions to core or this file below this line! #
# Module authors: go away #
###########################################################################
#---------------------------------------------------------------------
## Module authors: use the wrapper: bot_quit in misc.sh instead!
## @Type Private
## @param If set, a quit reason
#---------------------------------------------------------------------
send_quit() {
local reason=""
[[ -n "$1" ]] && reason=" :$1"
send_raw_flood "QUIT${reason}"
}

337
lib/server.sh Normal file
View File

@ -0,0 +1,337 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Server connection.
#---------------------------------------------------------------------
# Server info variables
#---------------------------------------------------------------------
## Name of server (example: server1.example.net)
## @Type API
#---------------------------------------------------------------------
server_name=""
#---------------------------------------------------------------------
## The 004 received from the server.
## @Type API
#---------------------------------------------------------------------
server_004=""
#---------------------------------------------------------------------
## The 005 received from the server. Use parse_005 to get data out of this.
## @Type API
## @Note See http://www.irc.org/tech_docs/005.html for an incomplete list of 005 values.
#---------------------------------------------------------------------
server_005=""
# NAMES output with UHNAMES and NAMESX
# :photon.kuonet-ng.org 353 envbot = #bots :@%+AnMaster!AnMaster@staff.kuonet-ng.org @ChanServ!ChanServ@services.kuonet-ng.org bashbot!rfc3092@1F1794B2:769091B3
# NAMES output with NAMESX only:
# :hurricane.KuoNET.org 353 envbot = #test :bashbot ~@Brain ~@EmErgE &@AnMaster/kng
#---------------------------------------------------------------------
## 1 if UHNAMES enabled, otherwise 0
## @Type API
#---------------------------------------------------------------------
server_UHNAMES=0
#---------------------------------------------------------------------
## 1 if NAMESX enabled, otherwise 0
## @Type API
#---------------------------------------------------------------------
server_NAMESX=0
# These are passed in a slightly odd way in 005 so we do them here.
#---------------------------------------------------------------------
## The mode char (if any) for ban excepts (normally +e)
## @Type API
#---------------------------------------------------------------------
server_EXCEPTS=""
#---------------------------------------------------------------------
## The mode char (if any) for invite excepts (normally +I)
## @Type API
#---------------------------------------------------------------------
server_INVEX=""
# In case we don't get a 005, make some sane defaults.
#---------------------------------------------------------------------
## List channel modes supported by server.
## @Type API
#---------------------------------------------------------------------
server_CHMODES_LISTMODES="b"
#---------------------------------------------------------------------
## "Always parameters" channel modes supported by server.
## @Type API
#---------------------------------------------------------------------
server_CHMODES_ALWAYSPARAM="k"
#---------------------------------------------------------------------
## "Parameter on set" channel modes supported by server.
## @Type API
#---------------------------------------------------------------------
server_CHMODES_PARAMONSET="l"
#---------------------------------------------------------------------
## Simple channel modes supported by server.
## @Type API
#---------------------------------------------------------------------
server_CHMODES_SIMPLE="imnpst"
#---------------------------------------------------------------------
## Prefix channel modes supported by server.
## @Type API
#---------------------------------------------------------------------
server_PREFIX_modes="ov"
#---------------------------------------------------------------------
## Channel prefixes supported by server.
## @Type API
#---------------------------------------------------------------------
server_PREFIX_prefixes="@+"
#---------------------------------------------------------------------
## What is our current nick?
## @Type API
#---------------------------------------------------------------------
server_nick_current=""
#---------------------------------------------------------------------
## 1 if we are connected, otherwise 0
## @Type API
#---------------------------------------------------------------------
server_connected=0
###########################################################################
# Internal functions to core or this file below this line! #
# Module authors: go away #
###########################################################################
#---------------------------------------------------------------------
## Get some common data out of 005, the whole will also be saved to
## $server_005 for any module to use via parse_005().
## This function is for cases that needs special action, like NAMESX
## and UHNAMES.
## This should be called directly after receiving a part of the 005!
## @Type Private
## @param The last part of the 005.
#---------------------------------------------------------------------
server_handle_005() {
# Example from freenode:
# :heinlein.freenode.net 005 envbot IRCD=dancer CAPAB CHANTYPES=# EXCEPTS INVEX CHANMODES=bdeIq,k,lfJD,cgijLmnPQrRstz CHANLIMIT=#:20 PREFIX=(ov)@+ MAXLIST=bdeI:50 MODES=4 STATUSMSG=@ KNOCK NICKLEN=16 :are supported by this server
# :heinlein.freenode.net 005 envbot SAFELIST CASEMAPPING=ascii CHANNELLEN=30 TOPICLEN=450 KICKLEN=450 KEYLEN=23 USERLEN=10 HOSTLEN=63 SILENCE=50 :are supported by this server
local line="$1"
if [[ $line =~ EXCEPTS(=([^ ]+))? ]]; then
# Some, but not all also send what char the modes for EXCEPTS is.
# If it isn't sent, lets guess it is +e
if [[ ${BASH_REMATCH[2]} ]]; then
server_EXCEPTS="${BASH_REMATCH[2]}"
else
server_EXCEPTS="e"
fi
fi
if [[ $line =~ INVEX(=([^ ]+))? ]]; then
# Some, but not all also send what char the modes for INVEX is.
# If it isn't sent, lets guess it is +I
if [[ ${BASH_REMATCH[2]} ]]; then
server_INVEX="${BASH_REMATCH[2]}"
else
server_INVEX="I"
fi
fi
if [[ $line =~ PREFIX=(\(([^ \)]+)\)([^ ]+)) ]]; then
server_PREFIX="${BASH_REMATCH[1]}"
server_PREFIX_modes="${BASH_REMATCH[2]}"
server_PREFIX_prefixes="${BASH_REMATCH[3]}"
fi
if [[ $line =~ CHANMODES=([^ ,]+),([^ ,]+),([^ ,]+),([^ ,]+) ]]; then
server_CHMODES_LISTMODES="${BASH_REMATCH[1]}"
server_CHMODES_ALWAYSPARAM="${BASH_REMATCH[2]}"
server_CHMODES_PARAMONSET="${BASH_REMATCH[3]}"
server_CHMODES_SIMPLE="${BASH_REMATCH[4]}"
fi
# Enable NAMESX is supported.
if [[ $line =~ NAMESX ]]; then
log_info "Enabled NAMESX support"
send_raw_flood "PROTOCTL NAMESX"
server_NAMESX=1
fi
# Enable UHNAMES if it is there.
if [[ $line =~ UHNAMES ]]; then
log_info "Enabled UHNAMES support"
send_raw_flood "PROTOCTL UHNAMES"
server_UHNAMES=1
fi
}
#---------------------------------------------------------------------
## Respond to PING from server.
## @Type Private
## @param Raw line
## @return 0 If it was a PING
## @return 1 If it was not a PING
#---------------------------------------------------------------------
server_handle_ping() {
if [[ "$1" =~ ^PING\ *:(.*) ]] ;then
send_raw "PONG :${BASH_REMATCH[1]}"
return 0
fi
return 1
}
#---------------------------------------------------------------------
## Handle numerics from server.
## @Type Private
## @param Numeric
## @param Target (self)
## @param Data
#---------------------------------------------------------------------
server_handle_numerics() {
# Slight sanity check
if [[ "$2" != "$server_nick_current" ]]; then
log_warning 'Own nick desynced!'
log_warning "It should be $server_nick_current but server says it is $2"
log_warning "Correcting own nick and lets hope that doesn't break anything"
server_nick_current="$2"
fi
}
#---------------------------------------------------------------------
## Handle NICK messages from server
## @Type Private
## @param Sender
## @param New nick
#---------------------------------------------------------------------
server_handle_nick() {
local oldnick=
parse_hostmask_nick "$1" 'oldnick'
if [[ $oldnick == $server_nick_current ]]; then
server_nick_current="$2"
fi
}
#---------------------------------------------------------------------
## Handle nick in use.
## @Type Private
#---------------------------------------------------------------------
server_handle_nick_in_use() {
if [[ $on_nick -eq 3 ]]; then
log_error "Third nick is ALSO in use. I give up"
bot_quit 2
elif [[ $on_nick -eq 2 ]]; then
log_warning "Second nick is ALSO in use, trying third"
send_nick "$config_thirdnick"
server_nick_current="$config_thirdnick"
on_nick=3
else
log_info_stdout "First nick is in use, trying second"
send_nick "$config_secondnick"
on_nick=2
# FIXME: THIS IS HACKISH AND MAY BREAK
server_nick_current="$config_secondnick"
fi
sleep 1
}
#---------------------------------------------------------------------
## Connect to IRC server.
## @Type Private
#---------------------------------------------------------------------
server_connect() {
server_connected=0
on_nick=1
# Clear current channels:
channels_current=""
# HACK: Clean up if we are aborted, replaced after connect with one that sends QUIT
trap 'transport_disconnect; rm -rvf "$tmp_home"; exit 1' TERM INT
log_info_stdout "Connecting to \"${config_server}:${config_server_port}\"..."
transport_connect "$config_server" "$config_server_port" "$config_server_ssl" "$config_server_bind" || return 1
[[ $config_server_passwd ]] && send_raw_flood_nolog "PASS $config_server_passwd"
log_info_stdout "logging in as $config_firstnick..."
send_nick "$config_firstnick"
# FIXME: THIS IS HACKISH AND MAY BREAK
server_nick_current="$config_firstnick"
# If a server password is set, send it.
send_raw_flood "USER $config_ident 0 * :${config_gecos}"
while true; do
line=
transport_read_line
local transport_status="$?"
# Still connected?
if ! transport_alive; then
return 1
fi
# Did we timeout waiting for data
# or did we get data?
if [[ $transport_status -ne 0 ]]; then
continue
fi
# Check with modules first, needed so we don't skip them.
for module in $modules_on_connect; do
module_${module}_on_connect "$line"
done
if [[ "$line" =~ ^:[^\ ]+\ +${numeric_RPL_MOTD} ]]; then
continue
fi
log_raw_in "$line"
if [[ "$line" =~ ^:[^\ ]+\ +([0-9]{3})\ +([^ ]+)\ +(.*) ]]; then
local numeric="${BASH_REMATCH[1]}"
# We use this to check below for our own nick.
local numericnick="${BASH_REMATCH[2]}"
local data="${BASH_REMATCH[3]}"
case "$numeric" in
"$numeric_RPL_MOTDSTART")
log_info "Motd is not displayed in log";
;;
"$numeric_RPL_YOURHOST")
if [[ $line =~ ^:([^ ]+) ]]; then # just to get the server name, this should always be true
server_name="${BASH_REMATCH[1]}"
fi
;;
"$numeric_RPL_WELCOME")
# This should work
server_nick_current="$numericnick"
;;
# We don't care about these and don't want to show it as unhandled.
"$numeric_RPL_CREATED"|"$numeric_RPL_LUSERCLIENT"|"$numeric_RPL_LUSEROP"|"$numeric_RPL_LUSERUNKNOWN"|"$numeric_RPL_LUSERCHANNELS"|"$numeric_RPL_LUSERME"|"$numeric_RPL_LOCALUSERS"|"$numeric_RPL_GLOBALUSERS"|"$numeric_RPL_STATSCONN")
continue
;;
"$numeric_RPL_MYINFO")
server_004="$data"
server_004=$(tr -d $'\r\n' <<< "$server_004") # Get rid of ending newline
;;
"$numeric_RPL_ISUPPORT")
server_005+=" $data"
server_005=$(tr -d $'\r\n' <<< "$server_005") # Get rid of newlines
server_005="${server_005/ :are supported by this server/}" # Get rid of :are supported by this server
server_handle_005 "$line"
;;
"$numeric_ERR_NICKNAMEINUSE"|"$numeric_ERR_ERRONEUSNICKNAME")
server_handle_nick_in_use
;;
"$numeric_RPL_ENDOFMOTD"|"$numeric_ERR_NOMOTD")
sleep 1
log_info_stdout 'Connected'
server_connected=1
break
;;
*)
if [[ -z "${numerics[10#${numeric}]}" ]]; then
log_info_file unknown_data.log "Unknown numeric during connect: $numeric Data: $data"
else
log_info_file unknown_data.log "Known but not handled numeric during connect: $numeric Data: $data"
fi
;;
esac
fi
server_handle_ping "$line"
done
}

110
lib/time.sh Normal file
View File

@ -0,0 +1,110 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Functions for working with time.
#---------------------------------------------------------------------
#---------------------------------------------------------------------
## Check if a set time has passed
## @Type API
## @param Unix timestamp to check against
## @param Number of seconds
## @return 0 If at least the given number of seconds has passed
## @return 1 If it hasn't
#---------------------------------------------------------------------
time_check_interval() {
local newtime=
time_get_current 'newtime'
(( ( newtime - $1 ) > $2 ))
}
#---------------------------------------------------------------------
## Get current time (seconds since 1970-01-01 00:00:00 UTC)
## @Type API
## @param Variable to return current timestamp in
#---------------------------------------------------------------------
time_get_current() {
printf -v "$1" '%s' "$(( time_initial + SECONDS ))"
}
#---------------------------------------------------------------------
## Returns how long a time interval is in a human readable format.
## @Type API
## @param Time interval
## @param Variable to return result in.
## @Note Modified version of function posted by goedel at
## @Note http://forum.bash-hackers.org/index.php?topic=59.0
#---------------------------------------------------------------------
time_format_difference() {
local tdiv=$1
local tmod i
local output=""
for ((i=0; i < ${#time_format_units[@]}; ++i)); do
# n means no limit.
if [[ ${time_format_unitspan[i]} == n ]]; then
tmod=$tdiv
else
(( tmod = tdiv % time_format_unitspan[i] ))
(( tdiv = tdiv / time_format_unitspan[i] ))
fi
output="$tmod${time_format_units[i]} $output"
[[ $tdiv = 0 ]] && break
done
printf -v "$2" '%s' "${output% }"
}
###########################################################################
# Internal functions to core or this file below this line! #
# Module authors: go away #
###########################################################################
#---------------------------------------------------------------------
## Array used for time_format_difference
## @Type Private
#---------------------------------------------------------------------
declare -r time_format_units=( s min h d mon )
#---------------------------------------------------------------------
## Array used for time_format_difference
## @Type Private
## @Note n means no limit.
#---------------------------------------------------------------------
declare -r time_format_unitspan=( 60 60 24 30 n )
#---------------------------------------------------------------------
## Initial timestamp that we use to get current time later on.
## @Type Private
#---------------------------------------------------------------------
time_initial=''
#---------------------------------------------------------------------
## Set up time variables
## @Type Private
#---------------------------------------------------------------------
time_init() {
# Set up initial env
time_initial="$(date -u +%s)"
SECONDS=0
}

228
modules/m_assign_mode.sh Normal file
View File

@ -0,0 +1,228 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# Copyright (C) 2007-2008 EmErgE <halt.system@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Channel modes
#---------------------------------------------------------------------
module_assign_mode_INIT() {
modinit_API='2'
modinit_HOOKS=''
commands_register "$1" 'op' || return 1
commands_register "$1" 'deop' || return 1
commands_register "$1" 'halfop' || return 1
commands_register "$1" 'dehalfop' || return 1
commands_register "$1" 'voice' || return 1
commands_register "$1" 'devoice' || return 1
commands_register "$1" 'protect' || return 1
commands_register "$1" 'deprotect' || return 1
commands_register "$1" 'topic' || return 1
helpentry_module_assign_mode_description="Provides op, deop and related commands."
local help_cmd
for help_cmd in op deop halfop halfdeop voice devoice protect deprotect; do
printf -v "helpentry_assign_mode_${help_cmd}_syntax" '<#channel> <nick>'
printf -v "helpentry_assign_mode_${help_cmd}_description" "$(tr 'a-z' 'A-Z' <<< "${help_cmd:0:1}")${help_cmd:1} <nick> in <#channel>."
done
helpentry_assign_mode_topic_syntax='<#channel> <new topic>'
helpentry_assign_mode_topic_description='Change topic to <new topic> in <#channel>'
}
module_assign_mode_UNLOAD() {
return 0
}
module_assign_mode_REHASH() {
return 0
}
module_assign_mode_handler_op() {
local sender="$1"
local sendon_channel="$2"
local parameters="$3"
if [[ "$parameters" =~ ^(#[^ ]+)\ ([^ ]+) ]]; then
local channel="${BASH_REMATCH[1]}"
local nick="${BASH_REMATCH[2]}"
if access_check_capab "op" "$sender" "$channel"; then
send_modes "$channel" "+o $nick"
else
access_fail "$sender" "make the bot op somebody" "op"
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "op" "<#channel> <nick>"
fi
}
module_assign_mode_handler_deop() {
local sender="$1"
local sendon_channel="$2"
local parameters="$3"
if [[ "$parameters" =~ ^(#[^ ]+)\ ([^ ]+) ]]; then
local channel="${BASH_REMATCH[1]}"
local nick="${BASH_REMATCH[2]}"
if access_check_capab "op" "$sender" "$channel"; then
send_modes "$channel" "-o $nick"
else
access_fail "$sender" "make the bot deop somebody" "op"
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "deop" "<#channel> <nick>"
fi
}
module_assign_mode_handler_halfop() {
local sender="$1"
local sendon_channel="$2"
local parameters="$3"
if [[ "$parameters" =~ ^(#[^ ]+)\ ([^ ]+) ]]; then
local channel="${BASH_REMATCH[1]}"
local nick="${BASH_REMATCH[2]}"
if access_check_capab "halfop" "$sender" "$channel"; then
send_modes "$channel" "+h $nick"
else
access_fail "$sender" "make the bot halfop somebody" "halfop"
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "halfop" "<#channel> <nick>"
fi
}
module_assign_mode_handler_dehalfop() {
local sender="$1"
local sendon_channel="$2"
local parameters="$3"
if [[ "$parameters" =~ ^(#[^ ]+)\ ([^ ]+) ]]; then
local channel="${BASH_REMATCH[1]}"
local nick="${BASH_REMATCH[2]}"
if access_check_capab "halfop" "$sender" "$channel"; then
send_modes "$channel" "-h $nick"
else
access_fail "$sender" "make the bot dehalfop somebody" "halfop"
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "dehalfop" "<#channel> <nick>"
fi
}
module_assign_mode_handler_voice() {
local sender="$1"
local sendon_channel="$2"
local parameters="$3"
if [[ "$parameters" =~ ^(#[^ ]+)\ ([^ ]+) ]]; then
local channel="${BASH_REMATCH[1]}"
local nick="${BASH_REMATCH[2]}"
if access_check_capab "voice" "$sender" "$channel"; then
send_modes "$channel" "+v $nick"
else
access_fail "$sender" "make the bot give voice to somebody" "voice"
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "voice" "<#channel> <nick>"
fi
}
module_assign_mode_handler_devoice() {
local sender="$1"
local sendon_channel="$2"
local parameters="$3"
if [[ "$parameters" =~ ^(#[^ ]+)\ ([^ ]+) ]]; then
local channel="${BASH_REMATCH[1]}"
local nick="${BASH_REMATCH[2]}"
if access_check_capab "voice" "$sender" "$channel"; then
send_modes "$channel" "-v $nick"
else
access_fail "$sender" "make the bot take voice from somebody" "voice"
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "devoice" "<#channel> <nick>"
fi
}
module_assign_mode_handler_protect() {
local sender="$1"
local sendon_channel="$2"
local parameters="$3"
if [[ "$parameters" =~ ^(#[^ ]+)\ ([^ ]+) ]]; then
local channel="${BASH_REMATCH[1]}"
local nick="${BASH_REMATCH[2]}"
if access_check_capab "protect" "$sender" "$channel"; then
send_modes "$channel" "+a $nick"
else
access_fail "$sender" "make the bot protect somebody" "protect"
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "protect" "<#channel> <nick>"
fi
}
module_assign_mode_handler_deprotect() {
local sender="$1"
local sendon_channel="$2"
local parameters="$3"
if [[ "$parameters" =~ ^(#[^ ]+)\ ([^ ]+) ]]; then
local channel="${BASH_REMATCH[1]}"
local nick="${BASH_REMATCH[2]}"
if access_check_capab "protect" "$sender" "$channel"; then
send_modes "$channel" "-a $nick"
else
access_fail "$sender" "make the bot deprotect somebody" "protect"
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "deprotect" "<#channel> <nick>"
fi
}
module_assign_mode_handler_topic() {
local sender="$1"
local sendon_channel="$2"
local parameters="$3"
if [[ "$parameters" =~ ^(#[^ ]+)\ (.+) ]]; then
local channel="${BASH_REMATCH[1]}"
local message="${BASH_REMATCH[2]}"
if access_check_capab "topic" "$sender" "$channel"; then
send_topic "$channel" "$message"
else
access_fail "$sender" "change the topic" "topic"
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "topic" "<#channel> <new topic>"
fi
}

63
modules/m_autojoin.sh Normal file
View File

@ -0,0 +1,63 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## This module does autojoin after connect.
#---------------------------------------------------------------------
module_autojoin_INIT() {
modinit_API='2'
modinit_HOOKS='after_connect'
helpentry_module_autojoin_description="Provides support for autojoining channels."
}
module_autojoin_UNLOAD() {
unset module_autojoin_join_from_config
}
module_autojoin_REHASH() {
module_autojoin_join_from_config
return 0
}
#---------------------------------------------------------------------
## Autojoin channels from config.
## @Type Private
#---------------------------------------------------------------------
module_autojoin_join_from_config() {
local channel
for channel in "${config_module_autojoin_channels[@]}"; do
# No quotes around channel because second word of it may be a key
# and list_contains just uses the first 2 arguments so a
# third one will be ignored.
if ! list_contains "channels_current" $channel; then
log_info "Joining $channel"
# No quotes here because then second argument can be a key
channels_join $channel
sleep 2
fi
done
}
# Called after bot has connected
module_autojoin_after_connect() {
module_autojoin_join_from_config
}

View File

@ -0,0 +1,45 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## For debugging, report any unknown numerics.
#---------------------------------------------------------------------
module_check_numerics_INIT() {
modinit_API='2'
modinit_HOOKS='on_numeric'
helpentry_module_check_numerics_description="Debugging module to check if any numeric we get is unknown."
}
module_check_numerics_UNLOAD() {
return 0
}
module_check_numerics_REHASH() {
return 0
}
module_check_numerics_on_numeric() {
# Make sure it is in base 10 here.
if [[ -z "${numerics[10#${1}]}" ]]; then
log_warning_file unknown_data.log "Unknown numeric $1 Data: $2"
fi
}

103
modules/m_commands.sh Normal file
View File

@ -0,0 +1,103 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# Copyright (C) 2007-2008 Vsevolod Kozlov #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Command-related utility commands
#---------------------------------------------------------------------
module_commands_INIT() {
modinit_API='2'
modinit_HOOKS=''
commands_register "$1" 'provides' || return 1
commands_register "$1" 'commands' || return 1
helpentry_module_commands_description="Provides a set of command-related commands."
helpentry_commands_provides_syntax='<command>'
helpentry_commands_provides_description='Shows which module provides command <command>'
helpentry_commands_commands_syntax='[<module>]'
helpentry_commands_commands_description='Lists commands available in <module>. If module name is not given, lists all commands'
}
module_commands_UNLOAD() {
return 0
}
module_commands_REHASH() {
return 0
}
module_commands_handler_provides() {
local sender="$1"
local parameters="$3"
if [[ $parameters =~ ^([a-zA-Z0-9][^ ]*)( [^ ]+)? ]]; then # regex suggested by AnMaster
local command_name="${BASH_REMATCH[1]}${BASH_REMATCH[2]}"
local target
if [[ $2 =~ ^# ]]; then
target="$2"
else
parse_hostmask_nick "$sender" 'target'
fi
local module_name
commands_provides "$command_name" module_name
if [[ -z $module_name ]]; then # No such command
send_msg "$target" "Command \"$command_name\" does not exist."
else
send_msg "$target" "Command \"$command_name\" is provided by module \"$module_name\""
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "provides" "<modulename>"
fi
}
module_commands_handler_commands() {
local parameters="$3"
local target
if [[ $2 =~ ^# ]]; then
target="$2"
else
parse_hostmask_nick "$1" 'target'
fi
if [[ -z $parameters ]]; then
send_msg "$target" "${format_bold}Available commands${format_bold}: ${commands_commands//,/, }"
else
# So we got a parameter
local module_name
if [[ $parameters =~ ^([^ ]+)\ *$ ]]; then
module_name="${BASH_REMATCH[1]}"
else
send_notice "$target" "\"$parameters\" is not a valid module name"
return 1
fi
local commands_in_module
commands_in_module "$module_name" 'commands_in_module'
if [[ $commands_in_module ]]; then
send_msg "$target" "${format_bold}Available commands (in module \"$module_name\")${format_bold}: ${commands_in_module//,/, }"
elif list_contains "modules_loaded" "$module_name"; then
send_notice "$target" "Module \"$module_name\" provides no commands"
else
send_notice "$target" "Module \"$module_name\" is not loaded"
fi
fi
}

91
modules/m_ctcp.sh Normal file
View File

@ -0,0 +1,91 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Handle CTCP
#---------------------------------------------------------------------
module_ctcp_INIT() {
modinit_API='2'
modinit_HOOKS='after_load on_PRIVMSG'
helpentry_module_ctcp_description="Answers CTCP requests."
}
module_ctcp_UNLOAD() {
return 0
}
module_ctcp_REHASH() {
return 0
}
module_ctcp_after_load() {
if [[ -z $config_module_ctcp_version_reply ]]; then
log_error "VERSION reply (config_module_ctcp_version_reply) must be set in config to use CTCP module."
return 1
fi
}
# Called on a PRIVMSG
#
# $1 = from who (n!u@h)
# $2 = to who (channel or botnick)
# $3 = the message
module_ctcp_on_PRIVMSG() {
local sender="$1"
local query="$3"
# We can't use regex here. For some unknown reason bash drops \001 from
# regex.
if [[ $query = $'\001'* ]]; then
# Get rid of \001 in the string.
local data="${query//$'\001'}"
local ctcp_command ctcp_parameters
# Split it up into command and any parameters.
read -r ctcp_command ctcp_parameters <<< "$data"
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
case "$ctcp_command" in
"CLIENTINFO")
send_nctcp "$sendernick" "CLIENTINFO CLIENTINFO PING SOURCE TIME VERSION"
;;
"PING")
send_nctcp "$sendernick" "PING $ctcp_parameters"
;;
"SOURCE")
send_nctcp "$sendernick" "SOURCE http://envbot.org"
;;
"TIME")
send_nctcp "$sendernick" "TIME $(date +'%Y-%m-%d %k:%M:%S')"
;;
"VERSION")
send_nctcp "$sendernick" "VERSION $config_module_ctcp_version_reply"
;;
*)
# So we didn't handle this CTCP? Return 0 then, someone else may want it.
return 0
;;
esac
# See above. We didn't fall back to not handle it and did not return
# so therefore we must have handled it.
return 1
fi
return 0
}

84
modules/m_dice.sh Normal file
View File

@ -0,0 +1,84 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# Copyright (C) 2007-2008 Vsevolod Kozlov #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Rolls dies.
#---------------------------------------------------------------------
module_dice_INIT() {
modinit_API='2'
modinit_HOOKS=''
commands_register "$1" 'roll' || return 1
helpentry_module_dice_description="Rolls dies for you."
helpentry_dice_roll_syntax="<dies>d<sides>"
helpentry_dice_roll_description="Rolls <dies> dies, each <sides> sides."
}
module_dice_UNLOAD() {
return 0
}
module_dice_REHASH() {
return 0
}
module_dice_handler_roll() {
local sender="$1"
local parameters="$3"
if [[ $parameters =~ ^([0-9]+)d([0-9]+)$ ]]; then
local how_much_times="${BASH_REMATCH[1]}"
local how_many_sides="${BASH_REMATCH[2]}"
local target=
if [[ $2 =~ ^# ]]; then
target="$2"
else
parse_hostmask_nick "$sender" 'target'
fi
local insane=0
# Chech if number of dies and sides are sane.
if (( ($how_many_sides < 2 || $how_many_sides > 100)
|| ($how_much_times < 1 || $how_much_times > 100) )); then
log_warning "Tried to roll $how_much_times dies $how_many_sides sides each!"
log_warning "This is above the allowed maximum or below the allowed minimum, and was aborted."
send_msg "$target" "You can't roll that."
return 0
fi
# Roll $how_much_times dies, each with $how_many_sides sides.
local result=""
local total=0
for (( i=0; $i < $how_much_times; i+=1 )); do
local rolled=$(( ($RANDOM % $how_many_sides) + 1 ))
result+="$rolled, "
((total += $rolled))
done
result=${result%, }
if [[ $how_much_times != 1 ]]; then
result+=" with the grand total of $total"
fi
send_msg "$target" "You rolled ${result}."
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "roll" "<dies>d<sides>"
fi
}

75
modules/m_die.sh Normal file
View File

@ -0,0 +1,75 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# Copyright (C) 2007-2008 EmErgE <halt.system@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Quit the bot.
#---------------------------------------------------------------------
module_die_INIT() {
modinit_API='2'
modinit_HOOKS=''
commands_register "$1" 'die' || return 1
commands_register "$1" 'restart' || return 1
helpentry_module_die_description="Commands to shut down and restart bot."
helpentry_die_die_syntax='[reason]'
helpentry_die_die_description='Quit with an optional quit reason.'
helpentry_die_restart_syntax='[reason]'
helpentry_die_restart_description='Disconnect the bot with an optional quit reason, then rerun itself.'
}
module_die_UNLOAD() {
return 0
}
module_die_REHASH() {
return 0
}
module_die_handler_die() {
local sender="$1"
if access_check_owner "$sender"; then
local parameters="$3"
access_log_action "$sender" "made the bot die with reason: $parameters"
local reason=
if [[ $parameters ]]; then
reason=": $parameters"
fi
bot_quit "Dying ($sender)$reason"
else
access_fail "$sender" "make the bot die" "owner"
fi
}
module_die_handler_restart() {
local sender="$1"
if access_check_owner "$sender"; then
local parameters="$3"
access_log_action "$sender" "made the bot restart with reason: $parameters"
local reason=
if [[ $parameters ]]; then
reason=": $parameters"
fi
bot_restart "Restarting ($sender)$reason"
else
access_fail "$sender" "make the bot restart" "owner"
fi
}

54
modules/m_dumpvars.sh Normal file
View File

@ -0,0 +1,54 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Debug module, dump all variables to console.
#---------------------------------------------------------------------
module_dumpvars_INIT() {
modinit_API='2'
modinit_HOOKS=''
commands_register "$1" 'dumpvars' || return 1
helpentry_module_dumpvars_description="Debugging module to dump all variables in the bot."
helpentry_dumpvars_dumpvars_syntax=''
helpentry_dumpvars_dumpvars_description='Dump all variables to STDOUT.'
}
module_dumpvars_UNLOAD() {
return 0
}
module_dumpvars_REHASH() {
return 0
}
module_dumpvars_handler_dumpvars() {
local sender="$1"
if access_check_owner "$sender"; then
# This is hackish, we only display
# lines unique to "file" 1.
# Also remove one variable that may fill our scrollback.
access_log_action "$sender" "a dump of variables"
comm -2 -3 <(declare) <(declare -f) | grep -Ev '^module_quote_quotes'
else
access_fail "$sender" "dump variables to STDOUT" "owner"
fi
}

461
modules/m_factoids.sh Normal file
View File

@ -0,0 +1,461 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Simple factoids module using SQLite3
#---------------------------------------------------------------------
module_factoids_INIT() {
modinit_API='2'
modinit_HOOKS='after_load on_PRIVMSG'
commands_register "$1" 'learn' || return 1
commands_register "$1" 'forget' || return 1
commands_register "$1" 'lock_factoid' 'lock factoid' || return 1
commands_register "$1" 'unlock_factoid' 'unlock factoid' || return 1
commands_register "$1" 'whatis' || return 1
commands_register "$1" 'factoid_stats' 'factoid stats' || return 1
helpentry_module_factoids_description="Provides a factoid database."
helpentry_factoids_learn_syntax='<key> (as|is|are|=) <value>'
helpentry_factoids_learn_description='Teach the bot a new factoid.'
helpentry_factoids_forget_syntax='<key>'
helpentry_factoids_forget_description='Make the bot forget the factoid <key>.'
helpentry_factoids_lock_factoid_syntax='<key>'
helpentry_factoids_lock_factoid_description='Prevent normal users from changing the factoid <key>.'
helpentry_factoids_unlock_factoid_syntax='<key>'
helpentry_factoids_unlock_factoid_description='Allow changes to a previously locked factoid <key>.'
helpentry_factoids_whatis_syntax='<key>'
helpentry_factoids_whatis_description='Look up the factoid <key>.'
helpentry_factoids_factoid_stats_syntax=''
helpentry_factoids_factoid_stats_description='Report some statistics on the factoid database.'
}
module_factoids_UNLOAD() {
# Ok this is a LOT. I hope I got all...
unset module_factoids_set module_factoids_remove module_factoids_parse_assignment
unset module_factoids_parse_key module_factoids_parse_value
unset module_factoids_set_INSERT_or_UPDATE module_factoids_send_factoid
unset module_factoids_get_count module_factoids_get_locked_count
unset module_factoids_is_locked module_factoids_lock module_factoids_unlock
unset module_factoids_SELECT module_factoids_INSERT module_factoids_UPDATE module_factoids_DELETE
}
module_factoids_REHASH() {
return 0
}
# Called after module has loaded.
module_factoids_after_load() {
modules_depends_register "factoids" "sqlite3" || {
# This error reporting is hackish, will fix later.
if ! list_contains "modules_loaded" "sqlite3"; then
log_error "The factoids module depends upon the SQLite3 module being loaded."
fi
return 1
}
if [[ -z $config_module_factoids_table ]]; then
log_error "Factiods table (config_module_factoids_table) must be set in config if you want to use factoids module."
return 1
fi
if ! module_sqlite3_table_exists "$config_module_factoids_table"; then
log_error "factoids module: $config_module_factoids_table does not exist in the database file."
log_error "factoids module: See comment in doc/factoids.sql for how to create the table."
fi
}
#---------------------------------------------------------------------
## Get an item from DB
## @Type Private
## @param Key
## @Stdout The result of the database query.
#---------------------------------------------------------------------
module_factoids_SELECT() {
#$ sqlite3 -list data/factoids.sqlite "SELECT value from factoids WHERE name='factoids';"
#A system that stores useful bits of information
module_sqlite3_exec_sql "SELECT value FROM $config_module_factoids_table WHERE name='$(module_sqlite3_clean_string "$1")';"
}
#---------------------------------------------------------------------
## Insert a new item into DB
## @Type Private
## @param key
## @param value
## @param hostmask of person who added it
#---------------------------------------------------------------------
module_factoids_INSERT() {
module_sqlite3_exec_sql \
"INSERT INTO $config_module_factoids_table (name, value, who) VALUES('$(module_sqlite3_clean_string "$1")', '$(module_sqlite3_clean_string "$2")', '$(module_sqlite3_clean_string "$3")');"
}
#---------------------------------------------------------------------
## Change the item in DB
## @Type Private
## @param key
## @param new value
## @param hostmask of person who changed it
#---------------------------------------------------------------------
module_factoids_UPDATE() {
module_sqlite3_exec_sql \
"UPDATE $config_module_factoids_table SET value='$(module_sqlite3_clean_string "$2")', who='$(module_sqlite3_clean_string "$3")' WHERE name='$(module_sqlite3_clean_string "$1")';"
}
#---------------------------------------------------------------------
## Remove an item
## @Type Private
## @param key
#---------------------------------------------------------------------
module_factoids_DELETE() {
module_sqlite3_exec_sql "DELETE FROM $config_module_factoids_table WHERE name='$(module_sqlite3_clean_string "$1")';"
}
#---------------------------------------------------------------------
## How many factoids are there
## @Type Private
## @Stdout Count of factoids.
#---------------------------------------------------------------------
module_factoids_get_count() {
module_sqlite3_exec_sql "SELECT COUNT(name) FROM $config_module_factoids_table;"
}
#---------------------------------------------------------------------
## How many locked factoids are there
## @Type Private
## @Stdout Count of locked factoids.
#---------------------------------------------------------------------
module_factoids_get_locked_count() {
module_sqlite3_exec_sql "SELECT COUNT(name) FROM $config_module_factoids_table WHERE is_locked='1';"
}
#---------------------------------------------------------------------
## Check if factoid is locked or not.
## @Type Private
## @param key
## @return 0 locked
## @return 1 not locked
#---------------------------------------------------------------------
module_factoids_is_locked() {
local lock="$(module_sqlite3_exec_sql "SELECT is_locked FROM $config_module_factoids_table WHERE name='$(module_sqlite3_clean_string "$1")';")"
if [[ $lock == "1" ]]; then
return 0
else
return 1
fi
}
#---------------------------------------------------------------------
## Lock a factoid against changes from non-owners
## @Type Private
## @param key
#---------------------------------------------------------------------
module_factoids_lock() {
module_sqlite3_exec_sql "UPDATE $config_module_factoids_table SET is_locked='1' WHERE name='$(module_sqlite3_clean_string "$1")';"
}
#---------------------------------------------------------------------
## Unlock a factoid from protection against non-owners
## @Type Private
## @param key
#---------------------------------------------------------------------
module_factoids_unlock() {
module_sqlite3_exec_sql "UPDATE $config_module_factoids_table SET is_locked='0' WHERE name='$(module_sqlite3_clean_string "$1")';"
}
#---------------------------------------------------------------------
## Wrapper, call either INSERT or UPDATE
## @Type Private
## @param key
## @param value
## @param hostmask of person set it
#---------------------------------------------------------------------
module_factoids_set_INSERT_or_UPDATE() {
if [[ $(module_factoids_SELECT "$1") ]]; then
module_factoids_UPDATE "$1" "$2" "$3"
else
module_factoids_INSERT "$1" "$2" "$3"
fi
}
#---------------------------------------------------------------------
## Wrapper, call either INSERT or UPDATE
## @Type Private
## @param key
## @param value
## @param sender
## @param channel
#---------------------------------------------------------------------
module_factoids_set() {
local key="$1"
local value="$2"
local sender="$3"
local channel="$4"
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
if module_factoids_is_locked "$key"; then
if access_check_capab "factoid_admin" "$sender" "GLOBAL"; then
module_factoids_set_INSERT_or_UPDATE "$key" "$value" "$sender"
send_msg "$channel" "Ok ${sendernick}, I will remember, $key is $value"
else
access_fail "$sender" "change a locked factoid" "factoid_admin"
fi
else
module_factoids_set_INSERT_or_UPDATE "$key" "$value" "$sender"
send_msg "$channel" "Ok ${sendernick}, I will remember, $key is $value"
fi
}
#---------------------------------------------------------------------
## Wrapper, check access
## @Type Private
## @param key
## @param sender
## @param channel
#---------------------------------------------------------------------
module_factoids_remove() {
local key="$1"
local sender="$2"
local channel="$3"
local value="$(module_factoids_SELECT "$(tr '[:upper:]' '[:lower:]' <<< "$key")")"
if [[ "$value" ]]; then
if module_factoids_is_locked "$key"; then
if access_check_capab "factoid_admin" "$sender" "GLOBAL"; then
module_factoids_DELETE "$key"
send_msg "$channel" "I forgot $key"
else
access_fail "$sender" "remove a locked factoid" "factoid_admin"
fi
else
module_factoids_DELETE "$key"
send_msg "$channel" "I forgot $key"
fi
else
send_msg "$channel" "I didn't have a factoid matching \"$key\""
fi
}
#---------------------------------------------------------------------
## Send the factoid:
## @Type Private
## @param To where (channel or nick)
## @param What factoid.
#---------------------------------------------------------------------
module_factoids_send_factoid() {
local channel="$1"
local key="$2"
local value="$(module_factoids_SELECT "$(tr '[:upper:]' '[:lower:]' <<< "$key")")"
if [[ "$value" ]]; then
if [[ $value =~ ^\<REPLY\>\ *(.*) ]]; then
send_msg "$channel" "${BASH_REMATCH[1]}"
elif [[ $value =~ ^\<ACTION\>\ *(.*) ]]; then
send_ctcp "$channel" "ACTION ${BASH_REMATCH[1]}"
else
send_msg "$channel" "$key is $value"
fi
else
send_msg "$channel" "I don't know what \"$key\" is."
fi
}
#---------------------------------------------------------------------
## Parse assignment:
## @Type Private
## @param String to parse
## @Note Will return using Global variables
## @Globals $module_factoids_parse_key $module_factoids_parse_value
#---------------------------------------------------------------------
module_factoids_parse_assignment() {
local word key value
# Have we hit a separator yet?
local state=0
while read -rd ' ' word; do
case "$state" in
0)
# If state is 1 the rest is value
if [[ "$word" =~ ^(as|is|are|=)$ ]]; then
state=1
else
key+=" $word"
fi
;;
1)
value+=" $word"
;;
esac
# Extra space at end is intended, to make read work correctly.
done <<< "$1 "
# And clean spaces, fastest way
read -ra module_factoids_parse_key <<< "$key"
read -ra module_factoids_parse_value <<< "$value"
}
module_factoids_handler_learn() {
local sender="$1"
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
local channel="$2"
if ! [[ $2 =~ ^# ]]; then
channel="$sendernick"
fi
local parameters="$3"
if [[ "$parameters" =~ ^(.+)\ (as|is|are|=)\ (.+) ]]; then
# Do the actual parsing elsewhere:
module_factoids_parse_assignment "$parameters"
local key="${module_factoids_parse_key[*]}"
local value="${module_factoids_parse_value[*]}"
unset module_factoids_parse_key module_factoids_parse_value
module_factoids_set "$(tr '[:upper:]' '[:lower:]' <<< "$key")" "$value" "$sender" "$channel"
else
feedback_bad_syntax "$sendernick" "learn" "<key> (as|is|are|=) <value>"
fi
return 1
}
module_factoids_handler_forget() {
local sender="$1"
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
local channel="$2"
if ! [[ $2 =~ ^# ]]; then
channel="$sendernick"
fi
local parameters="$3"
if [[ "$parameters" =~ ^(.+) ]]; then
local key="${BASH_REMATCH[1]}"
module_factoids_remove "$(tr '[:upper:]' '[:lower:]' <<< "$key")" "$sender" "$channel"
else
feedback_bad_syntax "$sendernick" "forget" "<key>"
fi
}
module_factoids_handler_lock_factoid() {
local sender="$1"
if access_check_capab "factoid_admin" "$sender" "GLOBAL"; then
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
local channel="$2"
if ! [[ $2 =~ ^# ]]; then
channel="$sendernick"
fi
local parameters="$3"
if [[ "$parameters" =~ ^(.+) ]]; then
local key="${BASH_REMATCH[1]}"
module_factoids_lock "$(tr '[:upper:]' '[:lower:]' <<< "$key")"
send_msg "$channel" "Ok ${sendernick}, the factoid \"$key\" is now protected from changes"
else
feedback_bad_syntax "$sendernick" "lock" "<key>"
fi
else
access_fail "$sender" "lock a factoid" "factoid_admin"
fi
}
module_factoids_handler_unlock_factoid() {
local sender="$1"
if access_check_capab "factoid_admin" "$sender" "GLOBAL"; then
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
local channel="$2"
if ! [[ $2 =~ ^# ]]; then
channel="$sendernick"
fi
local parameters="$3"
if [[ "$parameters" =~ ^(.+) ]]; then
local key="${BASH_REMATCH[1]}"
module_factoids_unlock "$(tr '[:upper:]' '[:lower:]' <<< "$key")"
send_msg "$channel" "Ok ${sendernick}, the factoid \"$key\" is no longer protected from changes"
else
feedback_bad_syntax "$sendernick" "lock" "<key>"
fi
else
access_fail "$sender" "lock a factoid" "factoid_admin"
fi
}
module_factoids_handler_whatis() {
local sender="$1"
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
local channel="$2"
if ! [[ $2 =~ ^# ]]; then
channel="$sendernick"
fi
local parameters="$3"
if [[ "$parameters" =~ ^(.+) ]]; then
local key="${BASH_REMATCH[1]}"
module_factoids_send_factoid "$channel" "$key"
else
feedback_bad_syntax "$sendernick" "whatis" "<key>"
fi
}
module_factoids_handler_factoid_stats() {
local sender="$1"
local channel="$2"
if ! [[ $2 =~ ^# ]]; then
parse_hostmask_nick "$sender" 'channel'
fi
local count="$(module_factoids_get_count)"
local lockedcount="$(module_factoids_get_locked_count)"
if [[ "$count" ]]; then
send_msg "$channel" "There are $count items in my factoid database. $lockedcount of the factoids are locked."
fi
}
module_factoids_on_PRIVMSG() {
local sender="$1"
local channel="$2"
if ! [[ $2 =~ ^# ]]; then
parse_hostmask_nick "$sender" 'channel'
fi
local query="$3"
# Answer question in channel if we got a factoid.
if [[ "$query" =~ ^((what|where|who|why|how)\ )?((is|are|were|was|to|can I find)\ )?([^\?]+)\?? ]]; then
local key="${BASH_REMATCH[@]: -1}"
local value="$(module_factoids_SELECT "$(tr '[:upper:]' '[:lower:]' <<< "$key")")"
if [[ "$value" ]]; then
module_factoids_send_factoid "$channel" "$key"
return 1
fi
fi
return 0
}

138
modules/m_faq.sh Normal file
View File

@ -0,0 +1,138 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Simple FAQ module
#---------------------------------------------------------------------
module_faq_INIT() {
modinit_API='2'
modinit_HOOKS='after_load'
commands_register "$1" 'faq' || return 1
helpentry_module_faq_description="FAQ from a file."
helpentry_faq_faq_syntax='[number|string]'
helpentry_faq_faq_description='Show the <number>th faq item or search for a <string> in all the faq items.'
}
module_faq_UNLOAD() {
unset module_faq_array module_faq_last_query_item
unset module_faq_load module_faq_last_query_time
}
module_faq_REHASH() {
module_faq_load
}
#---------------------------------------------------------------------
## Load or reload FAQ items
## @Type Private
#---------------------------------------------------------------------
module_faq_load() {
local i=0
unset module_faq_array
if [[ -z "$config_module_faq_file" ]]; then
log_error "faq module: You need to set config_module_faq_file in your config!"
return 1
elif [[ -r "$config_module_faq_file" ]]; then
while read -d $'\n' line ;do
# Skip empty lines
if [[ "$line" ]]; then
(( i++ ))
module_faq_array[$i]="$line"
fi
done < "${config_module_faq_file}"
log_info 'Loaded FAQ items'
return 0
else
log_error "faq module: Cannot load '${config_module_faq_file}'. File doesn't exist or can't be read."
return 1
fi
}
# Called after module has loaded.
module_faq_after_load() {
module_faq_last_query_item='null'
module_faq_last_query_time='null'
module_faq_load
}
# Called on a PRIVMSG
#
# $1 = from who (n!u@h)
# $2 = to who (channel or botnick)
# $3 = the message
module_faq_handler_faq() {
local sender="$1"
local channel="$2"
# If it isn't in a channel send message back to person who send it,
# otherwise send in channel
if ! [[ $2 =~ ^# ]]; then
parse_hostmask_nick "$sender" 'channel'
fi
local query="$3"
if [[ "$query" ]]; then
if [[ "$query" == "reload" ]]; then
if access_check_capab "faq_admin" "$sender" "GLOBAL"; then
send_msg "$channel" "Reloading FAQ items..."
module_faq_load
send_msg "$channel" "Done."
else
access_fail "$sender" "reload faq items" "faq_admin"
fi
return 0
fi
# Is it a flood? Then 1.
local ok=0
if [[ "$module_faq_last_query_item" == "$line" ]]; then
time_check_interval "$module_faq_last_query_time" 60 || ok=1
fi
if [[ $ok -eq 0 ]] ; then # Must be at least 1 min old or different query...
time_get_current 'module_faq_last_query_time'
# Update anti-flood variables
module_faq_last_query_item="$line"
module_faq_last_query="$query_time"
if [[ "$query" =~ ^\ *([0-9]+)\ *$ ]]; then
local index="${BASH_REMATCH[1]}"
if [[ "${module_faq_array[$index]}" ]]; then
send_msg "$channel" "${module_faq_array[$index]}"
else
send_msg "$channel" "That FAQ item doesn't exist"
fi
# Check length of search to be at least 3 chars
elif [[ "${#query}" -ge 3 ]] ; then
local i=0
while [[ $i -lt "${#module_faq_array[*]}" ]] ; do
(( i++ ))
# FIXME: This code is hard to read.
# This module needs rewriting...
if grep -qiFm 1 "$query" <<< "${module_faq_array[$i]}" ; then
send_msg "$channel" "${module_faq_array[$i]}"
break 1
fi
done
fi
else
log_error "FLOOD DETECTED in FAQ module"
fi
fi
}

151
modules/m_help.sh Normal file
View File

@ -0,0 +1,151 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# Copyright (C) 2007-2008 Vsevolod Kozlov #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Provides help command.
#---------------------------------------------------------------------
module_help_INIT() {
modinit_API='2'
modinit_HOOKS=''
commands_register "$1" 'help' || return 1
commands_register "$1" 'modinfo' || return 1
helpentry_module_help_description="Provides help and information for commands and modules."
helpentry_help_help_syntax='<command>'
helpentry_help_help_description='Displays help for command <command>'
helpentry_help_modinfo_syntax='<module>'
helpentry_help_modinfo_description='Displays a description for module <module>'
}
module_help_UNLOAD() {
unset module_help_fetch_module_function_data
unset module_help_fetch_module_data
}
module_help_REHASH() {
return 0
}
module_help_fetch_module_function_data() {
local module_name="$1"
local function_name="$2"
local target_syntax="$3"
local target_description="$4"
local varname_syntax="helpentry_${module_name}_${function_name}_syntax"
local varname_description="helpentry_${module_name}_${function_name}_description"
if [[ -z ${!varname_description} ]]; then
return 1
fi
printf -v "$target_description" '%s' "${!varname_description}"
if [[ ${!varname_syntax} ]]; then
printf -v "$target_syntax" '%s' " ${!varname_syntax}"
fi
}
module_help_fetch_module_data() {
local module_name="$1"
local target_description="$2"
local varname_description="helpentry_module_${module_name}_description"
if [[ -z ${!varname_description} ]]; then
return 1
fi
printf -v "$target_description" '%s' "${!varname_description}"
}
module_help_handler_help() {
local sender="$1"
local parameters="$3"
if [[ $parameters =~ ^([a-zA-Z0-9][^ ]*)( [^ ]+)? ]]; then
local command_name="${BASH_REMATCH[1]}${BASH_REMATCH[2]}"
# Look where we will reply to. We will not reply in the channel, even if the request was made in a channel, unless appropriate option is set
local target
if [[ $2 =~ ^# && $config_module_help_reply_in_channel == 1 ]]; then
target="$2"
else
parse_hostmask_nick "$sender" 'target'
fi
# Get the module name the command belongs to.
local module_name=
commands_provides "$command_name" 'module_name'
# Extract the function name.
local function_name=
hash_get 'commands_list' "$command_name" 'function_name'
if [[ $function_name =~ ^module_${module_name}_handler_(.+)$ ]]; then
function_name="${BASH_REMATCH[1]}"
fi
# Finally get the data for a specific function in specific module.
local syntax=
local description=
module_help_fetch_module_function_data "$module_name" "$function_name" syntax description || {
send_notice "$target" "Sorry, no help for ${format_bold}${command_name}${format_bold}"
return
}
# And send it back to the user.
if [[ $config_module_help_reply_in_one_line == 1 ]]; then
send_notice "$target" "${format_bold}${command_name}${format_bold}$syntax -- $description"
else
send_notice "$target" "${format_bold}${command_name}${format_bold}$syntax"
send_notice "$target" "$description"
fi
else
local sendernick=
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "help" "<command>"
fi
}
module_help_handler_modinfo() {
local sender="$1"
local parameters="$3"
if [[ $parameters =~ ^([^ ]+) ]]; then
local module_name="${BASH_REMATCH[1]}"
# See module_help_handler_help
local target
if [[ $2 =~ ^# && $config_module_help_reply_in_channel == 1 ]]; then
target="$2"
else
parse_hostmask_nick "$sender" 'target'
fi
local description=
module_help_fetch_module_data "$module_name" description || {
send_notice "$target" "Sorry, no information for module ${format_bold}${module_name}${format_bold}"
return
}
if [[ $config_module_help_reply_in_one_line == 1 ]]; then
send_notice "$target" "${format_bold}${module_name}${format_bold} -- $description"
else
send_notice "$target" "${format_bold}${module_name}${format_bold}"
send_notice "$target" "$description"
fi
else
local sendernick=
parse_hostmask_nick "$sender" sendernick
feedback_bad_syntax "$sendernick" "modinfo" "<module>"
fi
}

92
modules/m_join.sh Normal file
View File

@ -0,0 +1,92 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# Copyright (C) 2007-2008 EmErgE <halt.system@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Join/part
#---------------------------------------------------------------------
module_join_INIT() {
modinit_API='2'
modinit_HOOKS=''
commands_register "$1" 'join' || return 1
commands_register "$1" 'part' || return 1
helpentry_module_join_description="Join/part commands."
helpentry_join_join_syntax='<#channel> [<key>]'
helpentry_join_join_description='Join a <#channel>, with an optional channel <key>.'
helpentry_join_part_syntax='<#channel> [<reason>]"'
helpentry_join_part_description='Part a <#channel> with an optional <reason>.'
}
module_join_UNLOAD() {
return 0
}
module_join_REHASH() {
return 0
}
module_join_handler_part() {
local sender="$1"
local parameters="$3"
if [[ "$parameters" =~ ^(#[^ ]+)(\ (.+))? ]]; then
local channel="${BASH_REMATCH[1]}"
local reason="${BASH_REMATCH[3]}"
if access_check_capab "join" "$sender" "$channel"; then
if [[ -z "$reason" ]]; then
channels_part "$channel"
else
channels_part "$channel" "$reason"
fi
else
access_fail "$sender" "make the bot part channel" "join"
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "part" "<#channel> [<reason>]"
fi
}
module_join_handler_join() {
local sender="$1"
local parameters="$3"
if [[ "$parameters" =~ ^(#[^ ]+)(\ [^ ]+)? ]]; then
local channel="${BASH_REMATCH[1]}"
local key="${BASH_REMATCH[2]}"
if access_check_capab "join" "$sender" "$channel"; then
key="${key## }"
if [[ -z "$key" ]]; then
channels_join "${channel}"
else
channels_join "${channel}" "$key"
fi
else
access_fail "$sender" "make the join channel" "join"
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "join" "<#channel> [<key>]"
fi
}

252
modules/m_karma.sh Normal file
View File

@ -0,0 +1,252 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Karma module
#---------------------------------------------------------------------
module_karma_INIT() {
modinit_API='2'
modinit_HOOKS='after_load on_PRIVMSG'
commands_register "$1" 'karma' || return 1
helpentry_module_karma_description="Provides karma support. Use ++ and -- after a string in a channel to change karma."
helpentry_karma_karma_syntax='<string>'
helpentry_karma_karma_description='Get current karma for <string>.'
}
module_karma_UNLOAD() {
unset module_karma_SELECT
unset module_karma_INSERT module_karma_UPDATE module_karma_set_INSERT_or_UPDATE
unset module_karma_substract module_karma_add module_karma_check
unset module_karma_is_nick
unset module_karma_check_return
return 0
}
module_karma_REHASH() {
return 0
}
module_karma_after_load() {
modules_depends_register "karma" "sqlite3" || {
# This error reporting is hackish, will fix later.
if ! list_contains "modules_loaded" "sqlite3"; then
log_error "The karma module depends upon the SQLite3 module being loaded."
fi
return 1
}
if [[ -z $config_module_karma_table ]]; then
log_error "Karma table (config_module_karma_table) must be set in config to use the karma module."
return 1
fi
if ! module_sqlite3_table_exists "$config_module_karma_table"; then
log_error "karma module: $config_module_karma_table does not exist in the database file."
log_error "karma module: See comment in doc/karma.sql for how to create the table."
fi
}
#---------------------------------------------------------------------
## Get an item from DB
## @Type Private
## @param key
## @Stdout The result of the database query.
#---------------------------------------------------------------------
module_karma_SELECT() {
module_sqlite3_exec_sql "SELECT rating FROM $config_module_karma_table WHERE target='$(module_sqlite3_clean_string "$1")';"
}
#---------------------------------------------------------------------
## Insert a new item into DB
## @Type Private
## @param key
## @param karma
#---------------------------------------------------------------------
module_karma_INSERT() {
module_sqlite3_exec_sql \
"INSERT INTO $config_module_karma_table (target, rating) VALUES('$(module_sqlite3_clean_string "$1")', '$(module_sqlite3_clean_string "$2")');"
}
#---------------------------------------------------------------------
## Change the item in DB
## @Type Private
## @param key
## @param karma
#---------------------------------------------------------------------
module_karma_UPDATE() {
module_sqlite3_exec_sql \
"UPDATE $config_module_karma_table SET rating='$(module_sqlite3_clean_string "$2")' WHERE target='$(module_sqlite3_clean_string "$1")';"
}
#---------------------------------------------------------------------
## Wrapper, call either INSERT or UPDATE
## @Type Private
## @param key
## @param karma
#---------------------------------------------------------------------
module_karma_set_INSERT_or_UPDATE() {
if [[ $(module_karma_SELECT "$1") ]]; then
module_karma_UPDATE "$1" "$2"
else
module_karma_INSERT "$1" "$2"
fi
}
#---------------------------------------------------------------------
## Remove 1 from key
## @Type Private
## @param key to remove from.
#---------------------------------------------------------------------
module_karma_substract() {
# Clean spaces and convert to lower case
local keyarray
read -ra keyarray <<< "$1"
local key="$(tr '[:upper:]' '[:lower:]' <<< "${keyarray[*]}")"
local old="$(module_karma_SELECT "$key")"
# -1 + any old value (yes looks backwards but works)
local new=-1
if [[ "$old" ]]; then
(( new += old ))
fi
module_karma_set_INSERT_or_UPDATE "$key" "$new"
}
#---------------------------------------------------------------------
## Add 1 from key
## @Type Private
## @param key to add to.
#---------------------------------------------------------------------
module_karma_add() {
# Clean spaces and convert to lower case
local keyarray
read -ra keyarray <<< "$1"
local key="$(tr '[:upper:]' '[:lower:]' <<< "${keyarray[*]}")"
local old="$(module_karma_SELECT "$key")"
# 1 + any old value
local new=1
if [[ "$old" ]]; then
(( new += old ))
fi
module_karma_set_INSERT_or_UPDATE "$key" "$new"
}
#---------------------------------------------------------------------
## Return karma value for key
## The result is returned in $module_karma_check_return
## @Type Private
## @param key to return karma for
## @Globals $module_karma_check_return
#---------------------------------------------------------------------
module_karma_check() {
# Clean spaces and convert to lower case
local keyarray
read -ra keyarray <<< "$1"
local key="$(tr '[:upper:]' '[:lower:]' <<< "${keyarray[*]}")"
module_karma_check_return="$(module_karma_SELECT "$key")"
if [[ -z "$module_karma_check_return" ]]; then
module_karma_check_return=0
fi
}
#---------------------------------------------------------------------
## Check if the key is the nick of sender.
## @Type Private
## @param key
## @param sender
## @return 0 If nick and key are same
## @return 1 Otherwise
#---------------------------------------------------------------------
module_karma_is_nick() {
local keyarray
read -ra keyarray <<< "$1"
local key="$(tr '[:upper:]' '[:lower:]' <<< "${keyarray[*]}")"
local sendernick
parse_hostmask_nick "$2" 'sendernick'
local nickarray
read -ra nickarray <<< "$(tr '[:upper:]' '[:lower:]' <<< "$sendernick")"
local nick="${nickarray[*]}"
if [[ "$key" = "$nick" ]]; then
return 0
fi
return 1
}
# Called on a PRIVMSG
#
# $1 = from who (n!u@h)
# $2 = to who (channel or botnick)
# $3 = the message
module_karma_on_PRIVMSG() {
local sender="$1"
local query="$3"
local sendon_channel
# If it isn't in a channel send message back to person who sent it,
# otherwise send in channel
if [[ $2 =~ ^# ]]; then
sendon_channel="$2"
# An item must begin with an alphanumeric char.
if [[ "$query" =~ ^([a-zA-Z0-9].*)\+\+$ ]]; then
local key="${BASH_REMATCH[1]}"
if module_karma_is_nick "$key" "$sender"; then
send_msg "$sendon_channel" "You can't change karma of yourself."
else
module_karma_add "$key"
fi
elif [[ "$query" =~ ^([a-zA-Z0-9].*)--$ ]]; then
local key="${BASH_REMATCH[1]}"
if module_karma_is_nick "$key" "$sender"; then
send_msg "$sendon_channel" "You can't change karma of yourself."
else
module_karma_substract "$key"
fi
fi
else
parse_hostmask_nick "$sender" 'sendon_channel'
# Karma is only possible in channels
if [[ "$query" =~ ^[a-zA-Z0-9].*(--|\+\+)$ ]]; then
send_notice "$sendon_channel" "You can only change karma in channels."
return 1
fi
fi
return 0
}
module_karma_handler_karma() {
local sender="$1"
local sendon_channel
if [[ $2 =~ ^# ]]; then
sendon_channel="$2"
else
parse_hostmask_nick "$sender" 'sendon_channel'
fi
local parameters="$3"
if [[ $parameters =~ ^(.+)$ ]]; then
local key="${BASH_REMATCH[1]}"
module_karma_check "$key"
send_msg "$sendon_channel" "Karma for $key is $module_karma_check_return"
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "karma" "<string>"
fi
}

145
modules/m_kick_ban.sh Normal file
View File

@ -0,0 +1,145 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# Copyright (C) 2007-2008 EmErgE <halt.system@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Kicking and banning.
#---------------------------------------------------------------------
module_kick_ban_INIT() {
modinit_API='2'
modinit_HOOKS='after_load after_connect on_numeric'
unset module_kick_ban_next_unset module_kick_ban_timed_bans
commands_register "$1" 'kick' || return 1
commands_register "$1" 'ban' || return 1
helpentry_module_kick_ban_description="Provides kick and ban commands."
helpentry_kick_ban_kick_syntax='[<#channel>] <nick> <reason>'
helpentry_kick_ban_kick_description='Kick someone from a channel. Channel parameter only needed if not sent in a channel.'
helpentry_kick_ban_ban_syntax='<#channel> <nick> [<duration>]'
helpentry_kick_ban_ban_description='Ban someone from a channel. Duration is optional and defaults to infinite.'
}
module_kick_ban_UNLOAD() {
unset module_kick_ban_TBAN_supported
}
module_kick_ban_REHASH() {
return 0
}
# Lets check if TBAN is supported
# :photon.kuonet-ng.org 461 envbot TBAN :Not enough parameters.
# :photon.kuonet-ng.org 304 envbot :SYNTAX TBAN <channel> <duration> <banmask>
module_kick_ban_after_connect() {
module_kick_ban_TBAN_supported=0
send_raw "TBAN"
}
# HACK: If module is loaded after connect, module_kick_ban_after_connect won't
# get called, therefore lets check if we are connected here and check for
# TBAN here if that is the case.
module_kick_ban_after_load() {
if [[ $server_connected -eq 1 ]]; then
module_kick_ban_TBAN_supported=0
send_raw "TBAN"
fi
}
module_kick_ban_on_numeric() {
if [[ $1 == $numeric_ERR_NEEDMOREPARAMS ]]; then
if [[ "$2" =~ ^TBAN\ : ]]; then
module_kick_ban_TBAN_supported=1
fi
fi
}
module_kick_ban_handler_kick() {
# Accept this anywhere, unless someone can give a good reason not to.
local sender="$1"
local sendon_channel="$2"
local parameters="$3"
if [[ $parameters =~ ^((#[^ ]+)\ )(.*) ]]; then
local channel="${BASH_REMATCH[2]}"
parameters="${BASH_REMATCH[3]}"
else
if ! [[ $channel =~ ^# ]]; then
if [[ $sendon_channel =~ ^# ]]; then
local channel="$sendon_channel"
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "kick" "[<#channel>] <nick> <reason> # Channel must be send when the message is not sent in a channel"
return 0
fi
fi
fi
if [[ "$parameters" =~ ^([^ ]+)\ (.+) ]]; then
local nick="${BASH_REMATCH[1]}"
local kickmessage="${BASH_REMATCH[2]}"
if access_check_capab "kick" "$sender" "$channel"; then
send_raw "KICK $channel $nick :$kickmessage"
access_log_action "$sender" "kicked $nick from $channel with kick message: $kickmessage"
else
access_fail "$sender" "make the bot kick somebody" "kick"
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "kick" "[<#channel>] <nick> <reason> # Channel must be send when the message is not sent in a channel"
fi
}
module_kick_ban_handler_ban() {
local sender="$1"
local sendon_channel="$2"
local parameters="$3"
if [[ "$parameters" =~ ^(#[^ ]+)\ ([^ ]+)(\ ([0-9]+))? ]]; then
local channel="${BASH_REMATCH[1]}"
local nick="${BASH_REMATCH[2]}"
# Optional parameter.
local duration="${BASH_REMATCH[4]}"
if access_check_capab "ban" "$sender" "$channel"; then
if [[ $duration ]]; then
# send_modes "$channel" "+b" get_hostmask $nick <-- not implemented yet
if [[ $module_kick_ban_TBAN_supported -eq 1 ]]; then
send_raw "TBAN $channel $duration $nick"
else
send_modes "$channel" "+b $nick"
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
send_notice "$sendernick" "Sorry ban will not be timed, this IRCd didn't support TBAN command when I checked before."
fi
else
send_modes "$channel" "+b $nick"
fi
access_log_action "$sender" "banned $nick from $channel"
else
access_fail "$sender" "make the bot ban somebody" "ban"
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "ban" "<#channel> <nick> [<duration>]"
fi
}

178
modules/m_modules.sh Normal file
View File

@ -0,0 +1,178 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Manage (load/unload/list) modules.
#---------------------------------------------------------------------
module_modules_INIT() {
modinit_API='2'
modinit_HOOKS=''
commands_register "$1" 'modload' || return 1
commands_register "$1" 'modunload' || return 1
commands_register "$1" 'modreload' || return 1
commands_register "$1" 'modlist' || return 1
helpentry_module_modules_description="Exposes the internal module loading and unloading support to owners."
helpentry_modules_modload_syntax='<module name>'
helpentry_modules_modload_description='Try to load a module.'
helpentry_modules_modunload_syntax='<module name>'
helpentry_modules_modunload_description='Try to unload a module.'
helpentry_modules_modreload_syntax='<module name>'
helpentry_modules_modreload_description='Try to unload and reload a module.'
helpentry_modules_modlist_syntax=''
helpentry_modules_modlist_description='List currently loaded moudules.'
}
module_modules_UNLOAD() {
unset module_modules_doload module_modules_dounload
}
module_modules_REHASH() {
return 0
}
#---------------------------------------------------------------------
## Load a module
## @param Module to load
## @param Sender nick
#---------------------------------------------------------------------
module_modules_doload() {
local target_module="$1"
local sendernick="$2"
modules_load "$target_module"
local status_message status=$?
case $status in
0) status_message="Loaded \"$target_module\" successfully" ;;
2) status_message="Module \"$target_module\" is already loaded" ;;
3) status_message="Failed to source \"$target_module\" in safe subshell, see log for details" ;;
4) status_message="Failed to source \"$target_module\"" ;;
5) status_message="Module \"$target_module\" could not be found" ;;
6) status_message="Getting hooks from \"$target_module\" failed" ;;
7) status_message="after_load failed for \"$target_module\", see log for details" ;;
*) status_message="Unknown error (code $status) for \"$target_module\"" ;;
esac
send_notice "$sendernick" "$status_message"
return $status
}
#---------------------------------------------------------------------
## Unload a module
## @param Module to unload
## @param Sender nick
#---------------------------------------------------------------------
module_modules_dounload() {
local target_module="$1"
local sendernick="$2"
if [[ $target_module == modules ]]; then
send_msg "$sendernick" \
"You can't unload/reload the modules module using itself. (The hackish way would be to use the eval module for this.)"
return 1
fi
modules_unload "$target_module"
local status_message status=$?
case $status in
0) status_message="Unloaded \"$target_module\" successfully" ;;
2) status_message="Module \"$target_module\" is not loaded" ;;
3) status_message="Module \"$target_module\" can't be unloaded, some these module(s) depend(s) on it: $(modules_depends_list_deps "$target_module")" ;;
*) status_message="Unknown error (code $status) for \"$target_module\"" ;;
esac
send_notice "$sendernick" "$status_message"
return $status
}
module_modules_handler_modload() {
# Accept this anywhere, unless someone can give a good reason not to.
local sender="$1"
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
local parameters="$3"
if [[ "$parameters" =~ ^([^ ]+) ]]; then
local target_module="${BASH_REMATCH[1]}"
if access_check_owner "$sender"; then
access_log_action "$sender" "loaded the module $target_module"
module_modules_doload "$target_module" "$sendernick"
else
access_fail "$sender" "load a module" "owner"
fi
else
feedback_bad_syntax "$sendernick" "modload" "<module name>"
fi
}
module_modules_handler_modunload() {
local sender="$1"
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
local parameters="$3"
if [[ "$parameters" =~ ^([^ ]+) ]]; then
local target_module="${BASH_REMATCH[1]}"
if access_check_owner "$sender"; then
access_log_action "$sender" "unloaded the module $target_module"
module_modules_dounload "$target_module" "$sendernick"
else
access_fail "$sender" "unload a module" "owner"
fi
else
feedback_bad_syntax "$sendernick" "modunload" "<module name>"
fi
}
module_modules_handler_modreload() {
local sender="$1"
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
local parameters="$3"
if [[ "$parameters" =~ ^([^ ]+) ]]; then
local target_module="${BASH_REMATCH[1]}"
if access_check_owner "$sender"; then
access_log_action "$sender" "reloaded the module $target_module"
module_modules_dounload "$target_module" "$sendernick"
if [[ $? = 0 ]]; then
module_modules_doload "$target_module" "$sendernick"
else
send_notice "$sendernick" "Reload of $target_module failed because it could not be unloaded."
fi
else
access_fail "$sender" "reload a module" "owner"
fi
else
feedback_bad_syntax "$sendernick" "modreload" "<module name>"
fi
}
module_modules_handler_modlist() {
local sender="$1"
local parameters="$3"
local target
if [[ $2 =~ ^# ]]; then
target="$2"
else
parse_hostmask_nick "$sender" 'target'
fi
local modlist="${modules_loaded## }"
modlist="${modlist%% }"
send_msg "$target" "${format_bold}Modules currently loaded${format_bold}: ${modlist// / }"
}

316
modules/m_nicktracking.sh Normal file
View File

@ -0,0 +1,316 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Provides nick tracking API for other modules.
#---------------------------------------------------------------------
module_nicktracking_INIT() {
modinit_API='2'
modinit_HOOKS='after_load before_connect on_numeric on_NICK on_QUIT on_KICK on_PART on_JOIN'
helpentry_module_nicktracking_description="Provides nicktracking backend for other modules."
}
module_nicktracking_UNLOAD() {
unset module_nicktracking_channels
hash_reset module_nicktracking_channels_nicks
hash_reset module_nicktracking_nicks
# Private functions
unset module_nicktracking_clear_nick module_nicktracking_clear_chan
unset module_nicktracking_parse_names
unset module_nicktracking_add_channel_nick
unset module_nicktracking_remove_channel_nick
# API functions
unset module_nicktracking_get_hostmask_by_nick
unset module_nicktracking_get_channel_nicks
return 0
}
module_nicktracking_REHASH() {
return 0
}
#################
# API functions #
#################
#---------------------------------------------------------------------
## Return hostmask of a nick
## @Type API
## @param Nick to find hostmask for
## @param Variable to return hostmask in
## @Note If no nick is found (or data about the nick
## @Note is missing currently), the return variable will be empty.
#---------------------------------------------------------------------
module_nicktracking_get_hostmask_by_nick() {
hash_get 'module_nicktracking_nicks' "$(tr '[:upper:]' '[:lower:]' <<< "$1")" "$2"
}
#---------------------------------------------------------------------
## Return list of nicks on a channel
## @Type API
## @param Channel to check
## @param Variable to return space separated list in
## @return 0 Channel data exists.
## @return 1 We don't track this channel.
#---------------------------------------------------------------------
module_nicktracking_get_channel_nicks() {
if list_contains 'module_nicktracking_channels' "$1"; then
hash_get 'module_nicktracking_channels_nicks' "$1" "$2"
return 0
else
return 1
fi
}
#####################
# Private functions #
#####################
#---------------------------------------------------------------------
## Check if a nick should be removed
## @Type Private
## @param Nick to check
#---------------------------------------------------------------------
module_nicktracking_clear_nick() {
# If not on a channel any more, remove knowledge about nick.
if ! hash_search 'module_nicktracking_channels_nicks' "$1"; then
hash_unset 'module_nicktracking_nicks' "$1"
fi
}
#---------------------------------------------------------------------
## Clear a channel (if we part it or such)
## @Type Private
## @param Channel name
#---------------------------------------------------------------------
module_nicktracking_clear_chan() {
list_remove 'module_nicktracking_channels' "$1" 'module_nicktracking_channels'
# Get list and then unset it.
local nicks=
hash_get 'module_nicktracking_channels_nicks' "$1" 'nicks'
hash_unset 'module_nicktracking_channels_nicks' "$1"
# Sigh, this isn't fast I know...
local nick
for nick in $nicks; do
module_nicktracking_clear_nick "$nick"
done
}
#---------------------------------------------------------------------
## Parse RPL_NAMREPLY data.
## @Type Private
## @param NAMES data
#---------------------------------------------------------------------
module_nicktracking_parse_names() {
if [[ $1 =~ ^[=*@]?\ *(#[^ ]+)\ +:(.+) ]]; then
local channel="${BASH_REMATCH[1]}"
local nicks="${BASH_REMATCH[2]}"
local entry nick realnick
# Loop through the entries
for entry in $nicks; do
# This will work both with and without NAMESX
if [[ $entry =~ [$server_PREFIX_prefixes]*([^ ]+) ]]; then
nick="${BASH_REMATCH[1]}"
# Is UHNAMES enabled?
# If yes lets take care of hostmask.
if [[ $server_UHNAMES -eq 1 ]]; then
parse_hostmask_nick "$nick" 'realnick'
realnick="$(tr '[:upper:]' '[:lower:]' <<< "$realnick")"
hash_set 'module_nicktracking_nicks' "$realnick" "$nick"
# Add to nick list of channel if not in list
hash_contains 'module_nicktracking_channels_nicks' "$channel" "$realnick" || \
hash_append 'module_nicktracking_channels_nicks' "$channel" "$realnick"
else
realnick="$(tr '[:upper:]' '[:lower:]' <<< "$nick")"
# Add to nick list of channel if not in list
hash_contains 'module_nicktracking_channels_nicks' "$channel" "$realnick" || \
hash_append 'module_nicktracking_channels_nicks' "$channel" "$realnick"
fi
else
log_error_file unknown_data.log "module_nicktracking_parse_names: Uh uh, regex for inner loop is bad, couldn't parse: $nick"
log_error_file unknown_data.log "module_nicktracking_parse_names: Please report a bug with the above message"
fi
done
else
log_error_file unknown_data.log "module_nicktracking_parse_names: Uh uh, outer regex is bad, couldn't parse: $1"
log_error_file unknown_data.log "module_nicktracking_parse_names: Please report a bug with the above message"
fi
return 0
}
#---------------------------------------------------------------------
## Parse RPL_WHOREPLY data.
## @Type Private
## @param WHO data
#---------------------------------------------------------------------
module_nicktracking_parse_who() {
# Read the who data into an array then extract the data from the array.
local whodata
read -ra whodata <<< "$1"
local channel="${whodata[0]}"
local ident="${whodata[1]}"
local host="${whodata[2]}"
local nick="${whodata[4]}"
local lowernick="$(tr '[:upper:]' '[:lower:]' <<< "$nick")"
# Set the hash tables
hash_set 'module_nicktracking_nicks' "$lowernick" "${nick}!${ident}@${host}"
# We don't want to add twice
hash_contains 'module_nicktracking_channels_nicks' "$channel" "$lowernick" || \
hash_append 'module_nicktracking_channels_nicks' "$channel" "$lowernick"
}
#---------------------------------------------------------------------
## Add a nick to a channel
## @Type Private
## @param Channel
## @param Hostmask
## @param Nick
#---------------------------------------------------------------------
module_nicktracking_add_channel_nick() {
local nick="$(tr '[:upper:]' '[:lower:]' <<< "$3")"
hash_append 'module_nicktracking_channels_nicks' "$1" "$nick"
hash_set 'module_nicktracking_nicks' "$nick" "$2"
}
#---------------------------------------------------------------------
## Remove a nick from a channel
## @Type Private
## @param Channel
## @param Nick
#---------------------------------------------------------------------
module_nicktracking_remove_channel_nick() {
local nick="$(tr '[:upper:]' '[:lower:]' <<< "$2")"
hash_substract 'module_nicktracking_channels_nicks' "$1" "$nick"
module_nicktracking_clear_nick "$nick"
}
#########
# Hooks #
#########
module_nicktracking_after_load() {
# Handle case of loading while bot is running
if [[ $server_connected -eq 1 ]]; then
module_nicktracking_channels="$channels_current"
local channel
for channel in $module_nicktracking_channels; do
send_raw "NAMES $channel"
# We have to send a WHO #channel if servers doesn't support UHNAMES.
if [[ $server_UHNAMES -eq 0 ]]; then
send_raw "WHO $channel"
fi
done
fi
}
module_nicktracking_before_connect() {
# Reset state.
unset module_nicktracking_channels
hash_reset module_nicktracking_channels_nicks
hash_reset module_nicktracking_nicks
return 0
}
##########################
# Message handling hooks #
##########################
module_nicktracking_on_numeric() {
case $1 in
"$numeric_RPL_NAMREPLY")
# TODO: Parse NAMES
module_nicktracking_parse_names "$2"
;;
"$numeric_RPL_WHOREPLY")
module_nicktracking_parse_who "$2"
;;
esac
}
module_nicktracking_on_NICK() {
local oldnick oldident oldhost oldentry
parse_hostmask "$1" 'oldnick' 'oldident' 'oldhost'
local oldlowercase="$(tr '[:upper:]' '[:lower:]' <<< "$oldnick")"
local newlowercase="$(tr '[:upper:]' '[:lower:]' <<< "$2")"
# Remove old and add new.
hash_get 'module_nicktracking_nicks' "$oldlowercase" 'oldentry'
hash_unset 'module_nicktracking_nicks' "$oldlowercase"
hash_set 'module_nicktracking_nicks' "$newlowercase" "${2}!${oldident}@${oldhost}"
local channel
# Loop through the channels
for channel in $module_nicktracking_channels; do
hash_replace 'module_nicktracking_channels_nicks' "$channel" "$oldnick" "$newlowercase"
done
return 0
}
module_nicktracking_on_QUIT() {
local whoquit=
parse_hostmask_nick "$1" 'whoquit'
local nick="$(tr '[:upper:]' '[:lower:]' <<< "$whoquit")"
hash_unset 'module_nicktracking_nicks' "$nick"
local channel
# Remove from channel
for channel in $module_nicktracking_channels; do
hash_substract 'module_nicktracking_channels_nicks' "$channel" "$nick"
done
}
module_nicktracking_on_KICK() {
local whogotkicked="$3"
if [[ $whogotkicked == $server_nick_current ]]; then
module_nicktracking_clear_chan "$2"
else
module_nicktracking_remove_channel_nick "$2" "$whogotkicked"
fi
}
module_nicktracking_on_PART() {
# Check if it was us
local whoparted=
parse_hostmask_nick "$1" 'whoparted'
if [[ $whoparted == $server_nick_current ]]; then
module_nicktracking_clear_chan "$2"
else
module_nicktracking_remove_channel_nick "$2" "$whoparted"
fi
}
module_nicktracking_on_JOIN() {
local whojoined=
parse_hostmask_nick "$1" 'whojoined'
if [[ $whojoined == $server_nick_current ]]; then
module_nicktracking_channels+=" $2"
hash_set 'module_nicktracking_channels_nicks' "$2" "$server_nick_current"
# We have to send a WHO #channel if servers doesn't support UHNAMES.
if [[ $server_UHNAMES -eq 0 ]]; then
send_raw "WHO $2"
fi
else
module_nicktracking_add_channel_nick "$2" "$1" "$whojoined"
fi
}

87
modules/m_ping.sh Normal file
View File

@ -0,0 +1,87 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Keeps track of latency
#---------------------------------------------------------------------
# TODO: Redo with stored "on pong info".
module_ping_INIT() {
modinit_API='2'
modinit_HOOKS='on_PONG'
commands_register "$1" 'ping' || return 1
commands_register "$1" 'latency' || return 1
helpentry_module_ping_description="Provides latency tracking."
helpentry_ping_ping_syntax=''
helpentry_ping_ping_description='Respond to sender with "PONG!"'
helpentry_ping_latency_syntax=''
helpentry_ping_latency_description='Report current latency to server.'
}
module_ping_UNLOAD() {
return 0
}
module_ping_REHASH() {
return 0
}
module_ping_on_PONG() {
# Is data time_sender?
if [[ $3 =~ ([0-9]+)_(#?[A-Za-z0-9][^ ]+) ]]; then
local time="${BASH_REMATCH[1]}"
local target="${BASH_REMATCH[2]}"
local latency
(( latency = envbot_time - $time ))
local msg=
case $latency in
0) msg="less than one second" ;;
1) msg="1 second" ;;
*) msg="$latency seconds" ;;
esac
send_msg "$target" "Latency is $msg"
fi
}
module_ping_handler_ping() {
local target
local sender_nick
parse_hostmask_nick "$1" 'sender_nick'
if [[ $2 =~ ^# ]]; then
target="$2"
else
target="$sender_nick"
fi
send_msg "$target" "$sender_nick: PONG!"
}
module_ping_handler_latency() {
local target
if [[ $2 =~ ^# ]]; then
target="$2"
else
parse_hostmask_nick "$1" 'target'
fi
send_raw "PING :${envbot_time}_${target}"
}

85
modules/m_quote.sh Normal file
View File

@ -0,0 +1,85 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 EmErgE <halt.system@gmail.com> #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Quotes module
#---------------------------------------------------------------------
module_quote_INIT() {
modinit_API='2'
modinit_HOOKS='after_load'
commands_register "$1" 'quote' || return 1
helpentry_module_quote_description="Provides command for random quotes from a file."
helpentry_quote_quote_syntax=''
helpentry_quote_quote_description='Return a random quote.'
}
module_quote_UNLOAD() {
unset module_quote_load
unset module_quote_quotes
}
module_quote_REHASH() {
module_quote_load
}
#---------------------------------------------------------------------
## Load quotes from file
## @Type Private
#---------------------------------------------------------------------
module_quote_load() {
local i=0 line
unset module_quote_quotes
if [[ -z "$config_module_quotes_file" ]]; then
log_error "quotes module: You need to set config_module_quotes_file in your config!"
return 1
elif [[ -r "$config_module_quotes_file" ]]; then
local IFS=$'\n'
module_quote_quotes=( $(<"${config_module_quotes_file}") )
unset IFS
log_info 'Loaded Quotes.'
return 0
else
log_error "quotes module: Quotes failed to load: Cannot load \"$config_module_quotes_file\". File doesn't exist."
return 1
fi
}
module_quote_after_load() {
# Return code from last command in a function
# will be return code for the function by default.
module_quote_load
}
module_quote_handler_quote() {
local sender="$1"
local channel="$2"
# If it isn't in a channel send message back to person who send it,
# otherwise send in channel
if ! [[ $2 =~ ^# ]]; then
parse_hostmask_nick "$sender" 'channel'
fi
local number="$RANDOM"
(( number %= ${#module_quote_quotes[*]} ))
send_msg "$channel" "${module_quote_quotes[$number]}"
}

74
modules/m_rehash.sh Normal file
View File

@ -0,0 +1,74 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Rehashing
#---------------------------------------------------------------------
module_rehash_INIT() {
modinit_API='2'
modinit_HOOKS=''
commands_register "$1" 'rehash' || return 1
helpentry_module_rehash_description="Exposes the internal rehash support to bot owners."
helpentry_rehash_rehash_syntax=''
helpentry_rehash_rehash_description='Reload configuration file.'
}
module_rehash_UNLOAD() {
unset module_rehash_dorehash
}
module_rehash_REHASH() {
return 0
}
#---------------------------------------------------------------------
## Rehash config
## @Type Private
## @param Sender
#---------------------------------------------------------------------
module_rehash_dorehash() {
local sender="$1" status_message
config_rehash
local status=$?
case $status in
0) status_message="Rehash successful. (Also any loaded modules not listed in config have been unloaded.)" ;;
2) status_message="The new config is not the same version as the bot. Rehash won't work." ;;
3) status_message="Failed to source it, but the bot should not be in an undefined state." ;;
4) status_message="Configuration validation on new config failed, but the bot should not be in an undefined state." ;;
5) status_message="Failed to source it and the bot may be in an undefined state." ;;
*) status_message="Unknown error (code $status)" ;;
esac
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
send_notice "$sendernick" "$status_message"
}
module_rehash_handler_rehash() {
local sender="$1"
if access_check_owner "$sender"; then
access_log_action "$sender" "did a rehash"
module_rehash_dorehash "$sender"
else
access_fail "$sender" "load a module" "owner"
fi
}

98
modules/m_say.sh Normal file
View File

@ -0,0 +1,98 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Allow owners to make to bot say something
#---------------------------------------------------------------------
module_say_INIT() {
modinit_API='2'
modinit_HOOKS=''
commands_register "$1" 'say' || return 1
commands_register "$1" 'act' || return 1
helpentry_module_say_description="Provides say and act commands."
helpentry_say_act_syntax='<target> <message>'
helpentry_say_act_description='Send a <message> to <target> (nick or channel).'
helpentry_say_act_syntax='<target> <message>'
helpentry_say_act_description='Peform the <message> as a /me to <target> (nick or channel).'
}
module_say_UNLOAD() {
return 0
}
module_say_REHASH() {
return 0
}
module_say_handler_say() {
local sender="$1"
local parameters="$3"
if [[ "$parameters" =~ ^([^ ]+)\ (.+) ]]; then
local target="${BASH_REMATCH[1]}"
local message="${BASH_REMATCH[2]}"
local scope
# Is it a channel?
if [[ $target =~ ^# ]]; then
scope="$target"
else
scope="MSG"
fi
if access_check_capab "say" "$sender" "$scope"; then
access_log_action "$sender" "made the bot say \"$message\" in/to \"$target\""
send_msg "$target" "$message"
else
access_fail "$sender" "make the bot talk with say" "say"
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "say" "<target> <message> # Where target is a nick or channel"
fi
}
module_say_handler_act() {
local sender="$1"
local parameters="$3"
if [[ "$parameters" =~ ^([^ ]+)\ (.+) ]]; then
local target="${BASH_REMATCH[1]}"
local message="${BASH_REMATCH[2]}"
local scope
# Is it a channel?
if [[ $target =~ ^# ]]; then
scope="$target"
else
scope="MSG"
fi
if access_check_capab "say" "$sender" "$scope"; then
access_log_action "$sender" "made the bot act \"$message\" in/to \"$target\""
send_ctcp "$target" "ACTION ${message}"
else
access_fail "$sender" "make the bot act" "say"
fi
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "act" "<target> <message> # Where target is a nick or channel"
fi
}

209
modules/m_seen.sh Normal file
View File

@ -0,0 +1,209 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Simple seen module using SQLite3
#---------------------------------------------------------------------
module_seen_INIT() {
modinit_API='2'
modinit_HOOKS='after_load on_PRIVMSG'
commands_register "$1" 'seen' || return 1
helpentry_module_seen_description="Provides last seen information."
helpentry_seen_seen_syntax='<nick>'
helpentry_seen_seen_description='Report when the bot last saw <nick>.'
}
module_seen_UNLOAD() {
unset module_seen_exec_sql module_seen_SELECT module_seen_INSERT module_seen_UPDATE
unset module_seen_set_INSERT_or_UPDATE
unset module_seen_store module_seen_find
}
module_seen_REHASH() {
return 0
}
# Called after module has loaded.
module_seen_after_load() {
modules_depends_register "seen" "sqlite3" || {
# This error reporting is hackish, will fix later.
if ! list_contains "modules_loaded" "sqlite3"; then
log_error "The seen module depends upon the SQLite3 module being loaded."
fi
return 1
}
if [[ -z $config_module_seen_table ]]; then
log_error "\"Seen table\" (config_module_seen_table) must be set in config."
return 1
fi
if ! module_sqlite3_table_exists "$config_module_seen_table"; then
log_error "seen module: $config_module_seen_table does not exist in the database file."
log_error "seen module: See comment in doc/seen.sql for how to create the table."
fi
}
#---------------------------------------------------------------------
## Get the data about nick
## @Type Private
## @param The nick
## @Stdout The result of the database query.
#---------------------------------------------------------------------
module_seen_SELECT() {
module_sqlite3_exec_sql "SELECT timestamp, channel, message FROM $config_module_seen_table WHERE nick='$(module_sqlite3_clean_string "$1")';"
}
#---------------------------------------------------------------------
## Insert a new item into DB
## @Type Private
## @param Nick
## @param Channel
## @param Timestamp
## @param Query
#---------------------------------------------------------------------
module_seen_INSERT() {
module_sqlite3_exec_sql \
"INSERT INTO $config_module_seen_table (nick, channel, timestamp, message) VALUES('$(module_sqlite3_clean_string "$1")', '$(module_sqlite3_clean_string "$2")', '$(module_sqlite3_clean_string "$3")', '$(module_sqlite3_clean_string "$4")');"
}
#---------------------------------------------------------------------
## Change the item in DB
## @Type Private
## @param Nick
## @param Channel
## @param Timestamp
## @param Message
#---------------------------------------------------------------------
module_seen_UPDATE() {
module_sqlite3_exec_sql \
"UPDATE $config_module_seen_table SET channel='$(module_sqlite3_clean_string "$2")', timestamp='$(module_sqlite3_clean_string "$3")', message='$(module_sqlite3_clean_string "$4")' WHERE nick='$(module_sqlite3_clean_string "$1")';"
}
#---------------------------------------------------------------------
## Wrapper, call either INSERT or UPDATE
## @Type Private
## @param Nick
## @param Channel
## @param Timestamp
## @param Message
#---------------------------------------------------------------------
module_seen_set_INSERT_or_UPDATE() {
if [[ $(module_seen_SELECT "$1") ]]; then
module_seen_UPDATE "$1" "$2" "$3" "$4"
else
module_seen_INSERT "$1" "$2" "$3" "$4"
fi
}
#---------------------------------------------------------------------
## Store a line
## @Type Private
## @param Sender
## @param Channel
## @param Timestamp
## @param Query
#---------------------------------------------------------------------
module_seen_store() {
# Clean spaces, fastest way for this
local query
read -ra query <<< "$4"
local sendernick
parse_hostmask_nick "$1" 'sendernick'
module_seen_set_INSERT_or_UPDATE "$(echo -n "$sendernick" | tr '[:upper:]' '[:lower:]')" "$2" "$3" "${query[*]}"
}
#---------------------------------------------------------------------
## Look up a nick and send info to a channel/nick
## @Type Private
## @param Sender
## @param Channel
## @param Nick to look up
#---------------------------------------------------------------------
module_seen_find() {
local sender="$1"
local channel="$2"
local nick="$(tr '[:upper:]' '[:lower:]' <<< "$3")"
local sender_nick=
parse_hostmask_nick "$sender" 'sender_nick'
# Classical ones. We just HAVE to do them.
if [[ "$nick" == "$(tr '[:upper:]' '[:lower:]' <<< "$server_nick_current")" ]]; then
send_msg "$channel" "$sender_nick, you found me!"
return 0
elif [[ "$nick" == "$(tr '[:upper:]' '[:lower:]' <<< "$sender_nick")" ]]; then
send_ctcp "$channel" "ACTION holds up a mirror for $sender_nick"
return 0
fi
local match="$(module_seen_SELECT "$nick")"
if [[ $match ]]; then
# So we got a match
# Lets use regex
if [[ $match =~ ([0-9]+)\|(#[^ |]+)\|(.*) ]]; then
local found_timestamp="${BASH_REMATCH[1]}"
local found_channel="${BASH_REMATCH[2]}"
local found_message="${BASH_REMATCH[3]}"
if [[ $found_message =~ ^ACTION\ (.*) ]]; then
found_message="* $3 ${BASH_REMATCH[1]}"
fi
local difference frmtdiff
time_get_current 'difference'
(( difference -= found_timestamp ))
time_format_difference "$difference" 'frmtdiff'
send_msg "$channel" "$3 was last seen $frmtdiff ago in $found_channel saying \"$found_message\""
fi
else
send_msg "$channel" "Sorry, I have not seen $3."
fi
}
module_seen_on_PRIVMSG() {
local sender="$1"
local channel="$2"
local query="$3"
# If in channel, store
if [[ $channel =~ ^# ]]; then
local now=
time_get_current 'now'
module_seen_store "$sender" "$channel" "$now" "$query"
# If not in channel respond to any commands in /msg
else
parse_hostmask_nick "$sender" 'channel'
fi
}
module_seen_handler_seen() {
local sender="$1"
local channel="$2"
if ! [[ $2 =~ ^# ]]; then
parse_hostmask_nick "$sender" 'channel'
fi
# Lets look up messages
local parameters="$3"
if [[ "$parameters" =~ ^([^ ]+) ]]; then
local nick="${BASH_REMATCH[1]}"
module_seen_find "$sender" "$channel" "$nick"
else
local sendernick
parse_hostmask_nick "$sender" 'sendernick'
feedback_bad_syntax "$sendernick" "seen" "<nick>"
fi
}

53
modules/m_sendraw.sh Normal file
View File

@ -0,0 +1,53 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Allow owners to make bot send any line.
## THIS IS FOR DEBUGGING MAINLY.
#---------------------------------------------------------------------
module_sendraw_INIT() {
modinit_API='2'
modinit_HOOKS=''
commands_register "$1" 'raw' || return 1
helpentry_module_sendraw_description="Provides raw command to send raw data."
helpentry_sendraw_raw_syntax='<line>'
helpentry_sendraw_raw_description='Send the <line> to the IRC server.'
}
module_sendraw_UNLOAD() {
return 0
}
module_sendraw_REHASH() {
return 0
}
module_sendraw_handler_raw() {
local sender="$1"
if access_check_capab "sendraw" "$sender" "GLOBAL"; then
local parameters="$3"
access_log_action "$sender" "make the bot send a raw line: $parameters"
send_raw "$parameters"
else
access_fail "$sender" "send a raw line" "sendraw"
fi
}

87
modules/m_services.sh Normal file
View File

@ -0,0 +1,87 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Identify to NickServ
#---------------------------------------------------------------------
module_services_INIT() {
modinit_API='2'
modinit_HOOKS='on_connect after_load after_disconnect'
helpentry_module_services_description="Provides support for identifying with services."
}
module_services_UNLOAD() {
unset module_services_ghost module_services_nickserv_command
}
module_services_REHASH() {
return 0
}
module_services_after_load() {
module_services_ghost=0
if [[ $config_module_services_server_alias -eq 0 ]]; then
module_services_nickserv_command="PRIVMSG $config_module_services_nickserv_name :"
else
module_services_nickserv_command="$config_module_services_nickserv_name "
fi
}
# Called for each line on connect
module_services_on_connect() {
local line="$1"
if [[ "$line" =~ ^:[^\ ]+\ +([0-9]{3})\ +([^ ]+)\ +(.*) ]]; then
local numeric="${BASH_REMATCH[1]}"
local numeric="${BASH_REMATCH[1]}"
# Check if this is a numeric we will handle.
case "$numeric" in
"$numeric_ERR_NICKNAMEINUSE"|"$numeric_ERR_ERRONEUSNICKNAME")
module_services_ghost=1
;;
"$numeric_RPL_ENDOFMOTD"|"$numeric_ERR_NOMOTD")
if [[ $config_module_services_style == 'atheme' ]]; then
send_raw_flood_nolog "NickServ IDENTIFY (password)" "${module_services_nickserv_command}IDENTIFY $config_firstnick $config_module_services_nickserv_passwd"
fi
if [[ $module_services_ghost == 1 ]]; then
log_info_stdout "Recovering ghost"
send_raw_flood_nolog "NickServ GHOST (password)" "${module_services_nickserv_command}GHOST $config_firstnick $config_module_services_nickserv_passwd"
# Try to release too, just in case.
send_raw_flood_nolog "NickServ RELEASE (password)" "${module_services_nickserv_command}RELEASE $config_firstnick $config_module_services_nickserv_passwd"
sleep 2
send_nick "$config_firstnick"
# HACK: This is a workaround for bug #21
server_nick_current="$config_firstnick"
fi
log_info_stdout "Identifying..."
if [[ $config_module_services_style != 'atheme' ]]; then
send_raw_flood_nolog "NickServ IDENTIFY (password)" "${module_services_nickserv_command}IDENTIFY $config_module_services_nickserv_passwd"
fi
sleep 1
;;
esac
fi
}
module_services_after_disconnect() {
# Reset state.
module_services_ghost=0
}

89
modules/m_sqlite3.sh Normal file
View File

@ -0,0 +1,89 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## This module allows other modules to access a SQLite3 database in a
## "simple" way.
#---------------------------------------------------------------------
module_sqlite3_INIT() {
modinit_API='2'
modinit_HOOKS='after_load'
helpentry_module_sqlite3_description="Provides sqlite3 database backend for other modules."
}
module_sqlite3_UNLOAD() {
unset module_sqlite3_clean_string module_sqlite3_exec_sql module_sqlite3_table_exists
}
module_sqlite3_REHASH() {
return 0
}
# Called after module has loaded.
module_sqlite3_after_load() {
# Check (silently) for sqlite3
if ! hash sqlite3 > /dev/null 2>&1; then
log_error "Couldn't find sqlite3 command line tool. The sqlite3 module depend on that tool."
return 1
fi
if [[ -z $config_module_sqlite3_database ]]; then
log_error "You must set config_module_sqlite3_database in your config to use the sqlite3 module."
return 1
fi
if ! [[ -r $config_module_sqlite3_database ]]; then
log_error "sqlite3 module: Database file doesn't exist or can't be read!"
log_error "sqlite3 module: To create one follow the comments in one (or several) of the sql files in the doc directory."
return 1
fi
}
#---------------------------------------------------------------------
## Make string safe for SQLite3.
## @Type API
## @param String to clean
## @Note IMPORTANT FOR SECURITY!: Only use the result inside single
## @Note quotes ('), NEVER inside double quotes (").
## @Note The output isn't safe for that.
#---------------------------------------------------------------------
module_sqlite3_clean_string() {
sed "s/'/''/g" <<< "$1"
}
#---------------------------------------------------------------------
## Run the query against the data base.
## @Type API
## @param Query to run
#---------------------------------------------------------------------
module_sqlite3_exec_sql() {
sqlite3 -list "$config_module_sqlite3_database" "$1"
}
#---------------------------------------------------------------------
## Check if a table exists in the database file.
## @Type API
## @param The table name to check for
## @return 0 If table exists
## @return 1 If table doesn't exist.
#---------------------------------------------------------------------
module_sqlite3_table_exists() {
sqlite3 -list "$config_module_sqlite3_database" ".tables" | grep -qw "$1"
}

63
modules/m_umodes.sh Normal file
View File

@ -0,0 +1,63 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Set umodes when connecting
#---------------------------------------------------------------------
module_umodes_INIT() {
modinit_API='2'
modinit_HOOKS='after_connect after_load'
helpentry_module_umodes_description="Provides support for setting umodes on connect."
}
module_umodes_UNLOAD() {
unset module_umodes_set_modes
}
module_umodes_REHASH() {
module_umodes_set_modes
return 0
}
#---------------------------------------------------------------------
## Set the umodes
## @Type Private
#---------------------------------------------------------------------
module_umodes_set_modes() {
if [[ $config_module_umodes_default_umodes ]]; then
log_info "Setting umodes: $config_module_umodes_default_umodes"
send_umodes "$config_module_umodes_default_umodes"
fi
}
# Called after bot has connected
module_umodes_after_connect() {
module_umodes_set_modes
}
# Called after bot has connected
module_umodes_after_load() {
# Check if connected first
if [[ $server_connected -eq 1 ]]; then
module_umodes_set_modes
fi
}

56
modules/m_uptime.sh Normal file
View File

@ -0,0 +1,56 @@
#!/usr/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# Copyright (C) 2007-2008 Vsevolod Kozlov #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Bot's uptime command.
#---------------------------------------------------------------------
module_uptime_INIT() {
modinit_API='2'
modinit_HOOKS=''
commands_register "$1" 'uptime' || return 1
helpentry_module_uptime_description="Provides a command to show bot's uptime."
helpentry_uptime_uptime_syntax=''
helpentry_uptime_uptime_description='Shows the uptime for the bot.'
}
module_uptime_UNLOAD() {
return 0
}
module_uptime_REHASH() {
return 0
}
module_uptime_handler_uptime() {
local sender="$1"
local formatted_time=
time_format_difference $SECONDS formatted_time
local target=
if [[ $2 =~ ^# ]]; then
target="$2"
else
parse_hostmask_nick "$sender" target
fi
send_msg "$target" "The bot has been up for $formatted_time."
}

22
tools/bashdoc/ChangeLog Normal file
View File

@ -0,0 +1,22 @@
Sun Oct 14 17:24:19 CEST 2007; Arvid Norlander
The last two days I have changed so much on bashdoc that I lost count of it
Some nice things:
Cleaned up code.
XHTML and CSS.
Made bashdoc able to document variables.
Fix many bugs.
Sun Aug 13 13:08:12 PDT 2006; Unknown
Fix bug which caused either extra garbage around function links in src or
skipped some interlinks.
Release bashdoc-0.1.8
Sun Jan 22 16:22:45 EST 2006; Unknown
ChangeLog: Started
src2html: changed sed to fix the weird invalid range end erors
Changes suggested by Jaka Kranjc
generate-smgl-docs: Add handy example doc generation
script, written by and for SourceMage
generate-smgl-docs: Patch by Jaka Kranjc
generate-smgl-docs: Add -q to bashdoc call to limit useless output
Release: bashdoc-0.1.7

726
tools/bashdoc/bashdoc.sh Executable file
View File

@ -0,0 +1,726 @@
#!/usr/bin/env bash
# -*- coding: utf-8 -*-
#--------------------------
## @Synopsis Reads specialy formated shell scripts and creates docs
## @Copyright Copyright 2003, Paul Mahon
## @Copyright Copyright 2007, Arvid Norlander
## @License GPL v2
## Parses comments between lines of '#---'
## Lines to be parsed start with ##. All tags start with @.
## Lines without a tag are considered simple description of the section.
## If the line following the comment block doesn't start with 'function'
## the it's assumed that the comment is for the whole file. Only the first
## non-function comment block will be used, the other will be ignored.
## <p>
## Multiple identical tags are allowed, the contents are appended and separated
## with a space. @param tags are treated specials and are assumed to be in order.
## <p>
## There is an additional &lt;@function FUNCTION_NAME&gt; tag that can be embeded
## in any bashdoc comment. It will be transformed into a link to that function.
## Note, this will only work for functions that are defined in the same script.
## <p><pre>
## Usage: [OPTIONS] [--] script [ script ...]
## -p, --project project Name of the project
## -o, --output directory Specifies the directory you want the resulting html to go into
## -c, --nocss Do not write default CSS file.
## -e, --exclusive tag Only output if the block has this tag
## -q, --quiet Quiet the output
## -h, --help Display this help and exit
## -V, --version Output version information and exit
## -- No more arguments, only scripts
## script The script you want documented
##</pre>
##
#--------------------------
# Make env sane
unset LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY
unset LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS
unset LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION
export LC_ALL=C
export LANG=C
# Check bash version. We need at least 3.2.x
# Lets not use anything like =~ here because
# that may not work on old bash versions.
if [[ "$(awk -F. '{print $1 $2}' <<< $BASH_VERSION)" -lt 32 ]]; then
echo "Sorry your bash version is too old!"
echo "You need at least version 3.2 of bash"
echo "Please install a newer version:"
echo " * Either use your distro's packages"
echo " * Or see http://www.gnu.org/software/bash/"
exit 2
fi
# To make set -x more usable
export PS4='(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]} : '
VERSION="0.1.8"
HEADERS="<!-- Generated by bashdoc version $VERSION, on $(date +'%Y-%m-%d'). -->
<link rel=\"stylesheet\" href=\"style.css\" type=\"text/css\" />
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />"
GOOD=$'\e[32;01m'
WARN=$'\e[33;01m'
BAD=$'\e[31;01m'
NORMAL=$'\e[0m'
#--------------------------
## Output error message
## @param Message
## @Stderr Formated message
#--------------------------
print_error () {
echo -e " ${BAD}*${NORMAL} $*" >&2
}
#--------------------------
## Output warning message
## @param Message
## @Stderr Formated message
#--------------------------
print_warn () {
echo -e " ${WARN}*${NORMAL} $*" >&2
}
#--------------------------
## Output info message
## @param Message
## @Stderr Formated message
#--------------------------
print_info () {
echo -e " ${GOOD}*${NORMAL} $*" >&2
}
#--------------------------
## Output debug message
## @param Message
## @Stderr Formated message
#--------------------------
print_debug () {
echo -e " $*" >&2
}
#--------------------------
## @Arguments -r: recursive, -o [directory]: output html
## Parses arguments for this script
## @Gobals RECURSIVE, OUT_DIR
#--------------------------
function args()
{
local retVal=0
QUIET=0
while true ; do
case $1 in
-p|--project)
PROJECT="$2"
(( retVal+=2 ))
shift 2
;;
-o|--output)
OUT_DIR="$2"
(( retVal+=2 ))
shift 2
;;
-c|--nocss)
NOCSS="1"
(( retVal+=2 ))
shift 1
;;
-h|--help)
usage
exit 0
;;
-V|--version)
version
exit 0
;;
-e|--exclusive)
EXCLUSIVE="${2%%=*}"
EXCLUSIVE_VAL="${2#*=}"
(( retVal+=2 ))
shift 2
;;
-q|--quiet)
(( QUIET+=1 ))
(( retVal+=1 ))
shift 1
;;
--)
(( retVal++ ))
return $retVal
;;
-*)
usage
exit 0
;;
*)
[[ -e $1 ]] && return $retVal
echo "$1 doesn't exist."
usage
exit 1
;;
esac
done
}
#-------------------------
## Version for this script
## @Stdout Version information
#-------------------------
function version()
{
echo "bashdoc $VERSION - Generate HTML documentation from bash scripts"
echo ''
echo 'Copyright (C) 2003 Paul Mahon'
echo 'Copyright (C) 2007 Arvid Norlander'
echo 'This is free software; see the source for copying conditions. There is NO'
echo 'warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.'
echo ''
echo 'Written by Paul Mahon and modified by Arvid Norlander'
}
#-------------------------
## Usage for this script
## @Stdout Usage information
#-------------------------
function usage()
{
cat <<- EOF
bashdoc generates HTML documentation from bash scripts.
Usage: $(basename $0) [OPTIONS] [--] script [script ...]
Options:
-p, --project project Name of the project
-o, --output directory Specifies the directory you want the resulting html to go into
-c, --nocss Do not write default CSS file.
-e, --exclusive tag Only output if the block has this tag
-q, --quiet Quiet the output
-h, --help Display this help and exit
-V, --version Output version information and exit
-- No more arguments, only scripts
script The script you want documented
Examples:
bashdoc.sh -p bashdoc -o docs/ bashdoc.sh Generate documentation for this program.
bashdoc.sh -p appname -o docs/ -e Type=API someapp.sh Generate documentation for someapp.sh,
exclude items that do not include the tag
@Type API
EOF
}
#--------------------------
## Reads until it has read an entire comment block. A block starts with
## <br><pre>#---</pre></br>
## Alone on a line, and continues until the next
## <br><pre>#---</pre></br>
## All comment lines inside should have ## at the start or they
## will be ignored.
##
## @return 0 Possibly more blocks
## @return 1 Unexpected end of file
## @return 2 Expected end of file, no more blocks
## @Stdin Reads a chunk
## @Stdout Block with starting '##' removed
## @Globals paramDesc, retDesc, desc, block, split, out_comment_block
#--------------------------
function get_comment_block()
{
local inComment commentBlock lastLine=""
commentBlock=""
while read LINE ; do
(( srcLine++ ))
if [[ ${LINE:0:4} == '#---' ]] ; then
if [[ $inComment ]] ; then
out_comment_block="$commentBlock"
# I'm not sure why this is needed but it fixes incorrect line number
(( srcLine++ ))
return 0
else
inComment=yes
fi
elif [[ ${LINE:0:2} != '##' ]] && [[ $inComment ]] ; then
[[ $QUIET -lt 1 ]] && print_warn "Line $srcLine of $FILE isn't a doc comment! Ignoring."
[[ $QUIET -lt 1 ]] && print_warn "Line in question is: $LINE"
elif [[ $inComment ]] ; then
commentBlock="$commentBlock"$'\n'${LINE####}
fi
done
#If we make it out here, we hit the end of the file
if [[ $commentBlock ]] ; then
#If there is a comment block started, then it never ended
[[ $QUIET -lt 2 ]] && print_error "Unfinished comment block:"
[[ $QUIET -lt 2 ]] && print_error "$commentBlock"
return 1
else
return 2
fi
}
#-----------------------
## Parses the comments from stdin. Also reads the (non-commented)
## function name. Mostly uses <@function parse_block> and
## <@function output_parsed_block> to do the read work.
## @Stdin Reads line after comment block
## @Globals paramDesc, retDesc, desc, block, split, out_comment_block
#-----------------------
function parse_comments()
{
#We use a lot of $( echo ... ) in here to trim the blanks
local funcLine funcName
paramDesc=()
retDesc=()
local FIRST_BLOCK="yes"
local skipRead
local outBlock=""
local lastOutBlock=""
srcLine=0
# 1 = function
# 2 = variable
itemtype=0
while true ; do
paramNames=()
paramDesc=()
split=()
retDesc=()
desc=""
itemtype=0
unset out_comment_block
get_comment_block
[[ $? -gt 0 ]] && break
block="$out_comment_block"
if [[ $skipRead ]] ; then
skipRead=""
else
funcLine=""
funcName=""
read funcLine
fi
# Is it a (global) variable?
# Check before function to catch arrays.
if [[ ${funcLine} =~ ^(declare -r +)?([a-zA-Z_][a-zA-Z0-9_]*)=.+$ ]]; then
varName="${BASH_REMATCH[@]: -1}"
itemtype=2
# Is it a function?
elif [[ ${funcLine%%[[:blank:]]*} == function ]] || [[ ${funcLine} =~ ^[^\ ]+\ *\(\)\ *\{?$ ]]; then
funcName=$( echo ${funcLine#function} )
funcName=$( echo ${funcName%%()*} )
itemtype=1
fi
if [[ $funcName ]] || [[ $varName ]] || [[ $FIRST_BLOCK ]] ; then
# Only bother with this block if it is a function block or
# the first script block
#This fills in paramDesc[*], tag_*, retDesc
parse_block
lastOutBlock="$outBlock"
outBlock=$(output_parsed_block)
if [[ $FIRST_BLOCK ]] && [[ ! $funcName ]] && [[ ! $varName ]]; then
FIRST_BLOCK=""
fi
if [[ $EXCLUSIVE ]] ; then
# If this is first block, include it anyway.
if [[ $funcName ]] || [[ $varName ]]; then
local i="tag_${EXCLUSIVE}"
if [[ ${!i} != $EXCLUSIVE_VAL ]] ; then
if [[ $itemtype = 2 ]]; then
funcName="$varName"
fi
print_debug "$funcName block ignored, no $EXCLUSIVE=$EXCLUSIVE_VAL tag."
# Code duplication but hard to avoid
for i in ${!tag_*} ; do
unset $i
done
continue
fi
fi
fi
for i in ${!tag_*} ; do
unset $i
done
if [[ $funcName ]]; then
FUNC_LIST="$FUNC_LIST $funcName"
elif [[ $varName ]]; then
VAR_LIST="$VAR_LIST $varName"
fi
unset funcName varName
echo "$outBlock"
else
[[ $QUIET -lt 2 ]] && print_warn "Ignoring non-first non-function/variable comment block"
[[ $QUIET -lt 1 ]] && print_warn "$block"
fi
done
}
#---------------------
## Create HTML from the non-special tags
## @param var or func (is this for a variable or function)
## @Stdout HTMLized tags
#---------------------
function output_parsed_tags() {
local i
for i in ${!tag_*} ; do
# Convert _ in tags to space. Looks better.
echo " <h3 class=\"othertag ${1}othertag ${i/tag_/tag-}\">$(sed 's/_/ /g' <<< "${i#tag_}")</h3>"
# This may be fun, allow special formatting by tag.
echo " <p class=\"othertag ${1}othertag ${i/tag_/tag-}\">"
echo " ${!i}"
echo " </p>"
unset $i
done
}
#---------------------
## Outputs the parsed information in a nice pretty format.
## @Stdout formated documentation
## @Globals paramDesc, retDesc, desc, block, split
#---------------------
function output_parsed_block()
{
echo "<hr />"
if [[ $itemtype -eq 1 ]] && [[ $funcName ]]; then
echo "<!-- Block for $funcName -->"
echo " <h2 id=\"$funcName\" class=\"function\">function <strong>$funcName</strong>()</h2>"
echo " <h3>Parameters:</h3>"
echo " <ul class=\"paramerters\">"
if [[ ${#paramDesc[*]} -gt 0 ]] ; then
for(( i=0; i<"${#paramDesc[@]}"; i++ )) ; do
echo " <li class=\"paramerters\">\$$[i+1]: ${paramDesc[i]}</li>"
done
else
echo "<li>None</li>"
fi
echo " </ul>"
if [[ ${#retDesc[*]} -gt 0 ]] ; then
echo " <h3>Returns:</h3>"
echo " <ul class=\"returns\">"
for(( i=0; i<"${#retDesc[@]}"; i++ )) ; do
echo " <li class=\"returns\">${retDesc[i]}</li>"
done
echo " </ul>"
fi
output_parsed_tags func
[[ $desc ]] && echo "<h3>Description</h3><p class=\"description funcdescription\">$desc</p>"
elif [[ $itemtype -eq 2 ]]; then
echo "<!-- Block for $varName -->"
echo " <h2 id=\"$varName\" class=\"variable\">variable <strong>$varName</strong></h2>"
output_parsed_tags var
[[ $desc ]] && echo "<h3>Description</h3><p class=\"description vardescription\">$desc</p>"
else
echo '<!-- Header for whole script -->'
echo "<h1>$FILE</h1>"
echo " <p class=\"filedescription\">$desc</p>"
echo "$desc" >> $SCRIPT_DESC
for i in ${!tag_*} ; do
echo " <h3 class=\"fileothertag ${i/tag_/tag-}\">${i#tag_}</h3>"
echo " <p class=\"fileothertag ${i/tag_/tag-}\">${!i}</p>"
unset $i
done
fi
}
#---------------
## Does the real work of the parsing. Tags start with @. Special
## tags are @return and @param. Doc lines without a tag are
## considered description.
## @Globals paramDesc, retDesc, desc, block, split
#---------------
function parse_block()
{
local tag
local backIFS="$IFS"
IFS=$'\n'
for LINE in $block; do
IFS="$backIFS"
LINE=$( echo $LINE )
if [[ ${LINE:0:1} == '@' ]] ; then
split_tag split $LINE
case ${split} in
@param)
#paramNames[${#paramNames[*]}]=${split[1]}
paramDesc=( "${paramDesc[@]}" "${split[1]}" )
;;
@return)
retDesc=( "${retDesc[@]}" "${split[1]}" )
;;
@*)
tag=${split[0]#@}
local i="tag_${tag}"
if [[ ${!i} ]] ; then
local varname="tag_${tag}"
eval "tag_${tag}=\"\${!varname}"$'\n'"\${split[1]}\""
else
eval "tag_${tag}=\"\${split[1]}\""
fi
;;
*)
print_error "We shouldn't get here... it was a tag, but not a tag?"
;;
esac
else
desc="$desc"$'\n'"$LINE"
fi
done
IFS="$backIFS"
}
#----------------
## Splits a line that starts with a tag into tag and data.
## @param Variable you want the result put into. Array is format is ( tag, data ).
## @param Tag
## @param Data
## @Globals The variable in $1 will get the results
#----------------
function split_tag()
{
local out="${1}" ; shift
local tag=$( echo ${1} ) ; shift
# local key=$( echo ${1} ) ; shift
local value=$( echo $* )
eval "$out=( \"\$tag\" \"\${value}\" )"
}
#--------------------
## Outputs a header for script pages
## @Stdout html header
## @param Script name
#--------------------
function script_header()
{
cat <<- EOF > $OUT_FILE
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
$HEADERS
<title>$1 - $PROJECT</title>
</head>
<body>
<p class="right">
<a href="script_list.html">Script Index</a>
</p>
EOF
}
# Initialise project variables
OUT_DIR=$( dirname $0 )
NOCSS=0
args "$@"
shift $?
[[ $OUT_DIR ]] || OUT_DIR="."
# Create output directory in case it doesn't exist
mkdir -p "$OUT_DIR" || {
print_error "Failed to create output directory."
exit 1
}
if [[ $NOCSS = 0 ]]; then
print_info "Writing CSS"
# Copy stylesheet to output directory.
cat <<- EOF > "${OUT_DIR}/style.css"
/* Based on Trac CSS */
body {
background: #fff;
color: #000;
margin: 10px;
padding: 0;
}
body, th, td {
font: normal 13px verdana,arial,'Bitstream Vera Sans',helvetica,sans-serif;
}
h1, h2, h3, h4 {
font-family: arial,verdana,'Bitstream Vera Sans',helvetica,sans-serif;
font-weight: bold;
letter-spacing: -0.018em;
}
h1 { font-size: 19px; margin: .15em 1em 0 0 }
h2 { font-size: 16px; font-weight: normal; }
h3 { font-size: 14px }
hr { border: none; border-top: 1px solid #ccb; margin: 2em 0 }
address { font-style: normal }
img { border: none }
tt { white-space: pre }
:link, :visited {
text-decoration: none;
color: #b00;
border-bottom: 1px dotted #bbb;
}
:link:hover, :visited:hover {
background-color: #eee;
color: #555;
}
h1 :link, h1 :visited ,h2 :link, h2 :visited, h3 :link, h3 :visited,
h4 :link, h4 :visited, h5 :link, h5 :visited, h6 :link, h6 :visited {
color: inherit;
}
/* Partly own stuff: */
.nav body {
margin: 0;
padding: 0;
background: inherit;
color: inherit;
}
.nav ul { font-size: 11px; list-style: none; margin: 0; padding: 0; text-align: left }
.nav li {
display: block;
padding: 0;
margin: 0;
white-space: nowrap;
}
/* Own stuff */
.nav-header {
font-weight: bold;
}
.right { text-align: right }
.tag-Deprecated { color: #e00; }
EOF
else
print_warn "Not writing a stylesheet. You will need to add your own by hand afterwards."
fi
while [[ $# -gt 0 ]] ; do
#Initialise vars for this src
FILE=$1
[[ ! -f $FILE ]] || [[ ! -r $FILE ]] && {
print_error "$FILE is not a file or is not readable, skipping."
shift
continue
}
print_info "Parsing $FILE"
shift
OUT_FILE=${FILE#/} #Remove leading /
OUT_FILE="$OUT_DIR/${OUT_FILE//\//.}.html"
FUNC_FILE="${OUT_FILE%.html}.funcs"
VAR_FILE="${OUT_FILE%.html}.vars"
SCRIPT_DESC="${OUT_FILE%.html}.desc"
# Store real name (reuse in script list)
REAL_NAME_FILE="${OUT_FILE%.html}.name"
echo -n "${FILE#/}" > "$REAL_NAME_FILE"
FUNC_LIST=""
VAR_LIST=""
#Start this src's html file
script_header "$FILE"
# Parse and write out function list
{
parse_comments < $FILE
echo "$FUNC_LIST" > $FUNC_FILE
echo "$VAR_LIST" > $VAR_FILE
# Convert references like <@function file,functioname> into links
} | sed -e 's!<@[[:blank:]]*function \([^,>]*\)[[:blank:]]*>!<a href="#\1">\1</a>!g' \
-e 's!<@[[:blank:]]*function \([^,>]*\),[[:blank:]]*\([^>]*\)[[:blank:]]*>!<a href="\1#\2">\1</a>!g' >> $OUT_FILE
#Close off the html for this src
cat <<- EOF >> $OUT_FILE
</body>
</html>
EOF
done #Go on to next src
#Now for tying the scripts all together
pushd $OUT_DIR >/dev/null
print_info "Writing function list"
# Start page that will have all the function calls
cat <<- EOF > function_list.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
$HEADERS
<title>Functions of $PROJECT</title>
</head>
<body class="nav">
<ul class="nav">
EOF
echo "<li class=\"nav nav-header\">Functions</li>" >> function_list.html
# Merge function lists of all sources, sort by function name
for i in *.funcs ; do
for f in $( cat $i ) ; do
echo "$f <li class=\"nav nav-function\"><a href=\"${i%.funcs}.html#$f\" target=\"main\">$f</a></li>"
done
done | sort | cut -d' ' -f2- >> function_list.html
echo "<li class=\"nav nav-header\">Variables</li>" >> function_list.html
for i in *.vars ; do
for v in $( cat $i ) ; do
echo "$v <li class=\"nav nav-variable\"><a href=\"${i%.vars}.html#$v\" target=\"main\">$v</a></li>"
done
done | sort | cut -d' ' -f2- >> function_list.html
# Close off the html for the global function list
cat <<- EOF >> function_list.html
</ul>
</body>
</html>
EOF
print_info "Writing script list"
# Start the list of scripts
TITLE="Scripts"
[[ $PROJECT ]] && TITLE="$PROJECT Script Documentation"
cat <<- EOF > script_list.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
$HEADERS
<title>Scripts of $PROJECT</title>
</head>
<body>
<h1>$TITLE</h1>
<hr />
<dl>
EOF
# List all the sources + descriptions, sort by script dir/name
for i in *.name ; do
name=${i%.name}
echo "${name} $(cat "$i")"
done | sort | while read LINE realname; do
echo "<dt><a href=\"${LINE}.html\">$realname</a></dt>"
echo "<dd>"
cat ${LINE}.desc 2>/dev/null || { [[ $QUIET -lt 2 ]] && print_warn "$LINE has no description."; }
echo "</dd>"
done >> script_list.html
# Close off the html for the global script list
cat <<- EOF >> script_list.html
</dl>
</body>
</html>
EOF
print_info "Writing index file"
# Create the index file for the whole shbang
cat <<- EOF > index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
$HEADERS
<title>BashDoc - $PROJECT</title>
</head>
<frameset cols="25%,*">
<frame src="function_list.html" name="function_list" />
<frame src="script_list.html" name="main" />
</frameset>
</html>
EOF
# Remove the temporary .desc and .name files, leave the .func and .vars files, someone may want them later.
rm *.desc
rm *.name
popd >/dev/null

102
tools/build_numerics.sh Executable file
View File

@ -0,0 +1,102 @@
#!/usr/bin/env bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Generate list of numerics from the numerics.txt<br />
## Output to STDOUT.<br />
## Run this using make numerics in the main directory.
#---------------------------------------------------------------------
# Clean up env, just in case.
unset LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY
unset LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS
unset LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION
export LC_ALL=C
export LANG=C
cat << EOF
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
###########################################################################
# #
# WARNING THIS FILE IS AUTOGENERATED. ANY CHANGES WILL BE OVERWRITTEN! #
# See the source in tools/numerics.txt for comments about some numerics #
# This file was generated with tools/build_numerics.sh #
# #
###########################################################################
#---------------------------------------------------------------------
## Auto-generated list of numerics from tools/numerics.txt<br />
## This file contains a list of numerics that we currently use.
## It is therefore incomplete.<br />
## Because the list of variables in this file is so long, please see
## it's source for more details.
#---------------------------------------------------------------------
##########################
# Name -> number mapping #
##########################
EOF
# The numerics above are special case, otherwise bash strips leading 0.
# Yes a bash file with .txt..
source tools/numerics.txt || { echo 'Failed to source.' >&2; exit 1; }
for index in ${!numeric[*]}; do
printf "numeric_%s='%03i'\n" "${numeric[$index]}" "$index"
done
# Same special case as above.
cat << EOF
##########################
# Number -> name mapping #
##########################
EOF
for index in ${!numeric[*]}; do
echo "numerics[$index]='${numeric[$index]}'"
done
cat << EOF
# End of generated file.
EOF

215
tools/numerics.txt Normal file
View File

@ -0,0 +1,215 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an irc bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
# This file contains a list of numerics that we *currently use*.
# It is therefore incomplete.
# Sources:
# RFC 1459
# RFC 2812
# http://www.alien.net.au/irc/irc2numerics.html
# http://www.inspircd.org/wiki/List_Of_Numerics
# These are from RFC 1459, if not mentioned otherwise
##########################
# Number -> name mapping #
##########################
# During connect, these are sent. They are NOT part of RFC 1459.
# For some format of the parameters varies between servers.
numeric[1]=RPL_WELCOME # "Welcome to <network>"
numeric[2]=RPL_YOURHOST # "Your host is <servername>, running version <ver>"
numeric[3]=RPL_CREATED
numeric[4]=RPL_MYINFO # "<servername> <version> <available user modes> <available channel modes>"
numeric[5]=RPL_ISUPPORT # Not in any RFC. See http://www.irc.org/tech_docs/005.html for incomplete list.
numeric[6]=RPL_MAP # Not from any RFC
numeric[7]=RPL_MAPEND # Not from any RFC
numeric[8]=RPL_SNOMASK # Not from any RFC, used on Unreal
# Command replies.
numeric[205]=RPL_TRACEUSER
numeric[213]=RPL_STATSCLINE
numeric[219]=RPL_ENDOFSTATS
numeric[221]=RPL_UMODEIS
numeric[223]=RPL_STATSELINE # Not from any RFC. Conflicting use on different IRCds.
numeric[232]=RPL_RULES # Unreal usage. Conflicting use on different IRCds.
numeric[242]=RPL_STATSUPTIME
numeric[250]=RPL_STATSCONN # Not from any RFC. Conflicting use on different IRCds.
numeric[251]=RPL_LUSERCLIENT
numeric[252]=RPL_LUSEROP
numeric[253]=RPL_LUSERUNKNOWN
numeric[254]=RPL_LUSERCHANNELS
numeric[255]=RPL_LUSERME
numeric[256]=RPL_ADMINME
numeric[257]=RPL_ADMINLOC1
numeric[258]=RPL_ADMINLOC2
numeric[259]=RPL_ADMINEMAIL
numeric[263]=RPL_TRYAGAIN
numeric[265]=RPL_LOCALUSERS # Not from any RFC.
numeric[266]=RPL_GLOBALUSERS # Not from any RFC.
numeric[271]=RPL_SILELIST # Not from any RFC
numeric[272]=RPL_ENDOFSILELIST # Not from any RFC
numeric[301]=RPL_AWAY
numeric[302]=RPL_USERHOST
numeric[303]=RPL_ISON
numeric[304]=RPL_TEXT # Not from any RFC I think. And on InspIRCd this is used for syntax hints.
numeric[305]=RPL_UNAWAY
numeric[306]=RPL_UNAWAY
numeric[307]=RPL_WHOISREGNICK # Not from any RFC. Used on Unreal.
numeric[308]=RPL_RULESSTART # Unreal usage. Conflicting use on different IRCds.
numeric[309]=RPL_ENDOFRULES # Unreal usage. Conflicting use on different IRCds.
numeric[310]=RPL_WHOISHELPOP # Unreal usage. Conflicting use on different IRCds.
numeric[311]=RPL_WHOISUSER
numeric[312]=RPL_WHOISSERVER
numeric[313]=RPL_WHOISOPERATOR
numeric[314]=RPL_WHOWASUSER
numeric[315]=RPL_ENDOFWHO
numeric[317]=RPL_WHOISIDLE
numeric[318]=RPL_ENDOFWHOIS
numeric[319]=RPL_WHOISCHANNELS
numeric[320]=RPL_WHOISSPECIAL # Not from any RFC. Numeric called other things on other ircds.
# RPL_WHOISSPECIAL is what it is called on Unreal.
# Used for "connecting using ssl" on InspIRCd. Also used for SWHOIS.
# On hyperion used for "is identified to services".
numeric[321]=RPL_LISTSTART
numeric[322]=RPL_LIST
numeric[323]=RPL_LISTEND
numeric[324]=RPL_CHANNELMODEIS
numeric[329]=RPL_CREATIONTIME # Not from any RFC. Used on InspIRCd at least.
numeric[330]=RPL_WHOISACCOUNT # Not from any RFC. This is how it is used on InspIRCd with m_services_account.
numeric[331]=RPL_NOTOPIC
numeric[332]=RPL_TOPIC
numeric[333]=RPL_TOPICWHOTIME # Not from any RFC.
numeric[340]=RPL_USERIP # Not from any RFC.
numeric[341]=RPL_INVITING
numeric[346]=RPL_INVITELIST
numeric[347]=RPL_ENDOFINVITELIST
numeric[348]=RPL_EXCEPTLIST
numeric[349]=RPL_ENDOFEXCEPTLIST
numeric[351]=RPL_VERSION
numeric[352]=RPL_WHOREPLY
numeric[353]=RPL_NAMREPLY
numeric[364]=RPL_LINKS
numeric[365]=RPL_ENDOFLINKS
numeric[366]=RPL_ENDOFNAMES
numeric[367]=RPL_BANLIST
numeric[368]=RPL_ENDOFBANLIST
numeric[369]=RPL_ENDOFWHOWAS
numeric[371]=RPL_INFO
numeric[372]=RPL_MOTD
numeric[374]=RPL_ENDOFINFO
numeric[375]=RPL_MOTDSTART
numeric[376]=RPL_ENDOFMOTD
numeric[378]=RPL_WHOISHOST
numeric[381]=RPL_YOUREOPER
numeric[382]=RPL_REHASHING
numeric[391]=RPL_TIME
numeric[396]=RPL_HOSTHIDDEN # Not from any RFC.
# Errors
numeric[401]=ERR_NOSUCHNICK
numeric[402]=ERR_NOSUCHSERVER
numeric[403]=ERR_NOSUCHCHANNEL
numeric[404]=ERR_CANNOTSENDTOCHAN
numeric[405]=ERR_TOOMANYCHANNELS
numeric[406]=ERR_WASNOSUCHNICK
numeric[407]=ERR_TOOMANYTARGETS
numeric[412]=ERR_NOTEXTTOSEND
numeric[416]=ERR_TOOMANYMATCHES # Not from any RFC.
numeric[421]=ERR_UNKNOWNCOMMAND
numeric[422]=ERR_NOMOTD
numeric[432]=ERR_ERRONEUSNICKNAME # Bad/forbidden nickname
numeric[433]=ERR_NICKNAMEINUSE # Nick in use
numeric[438]=ERR_NICKTOOFAST # Not from any RFC. Used on ircu and Unreal (at least).
numeric[441]=ERR_USERNOTINCHANNEL
numeric[442]=ERR_NOTONCHANNEL
numeric[443]=ERR_USERONCHANNEL
numeric[445]=ERR_SUMMONDISABLED # Yep, most (all?) do nowdays :)
numeric[446]=ERR_USERSDISABLED # Yep, most (all?) do nowdays :)
numeric[447]=ERR_NONICKCHANGE # Not from any RFC.
numeric[460]=ERR_NOTFORHALFOPS # Not from any RFC. Unreal got this at least.
numeric[461]=ERR_NEEDMOREPARAMS
numeric[462]=ERR_ALREADYREGISTERED
numeric[468]=ERR_ONLYSERVERSCANCHANGE # Not from any RFC.
numeric[470]=ERR_LINKCHANNEL # Not from any RFC.
# InspIRCd example: :#channel has become full, so you are automatically being transferred to the linked channel #otherchannel
numeric[471]=ERR_CHANNELISFULL
numeric[472]=ERR_UNKNOWNMODE
numeric[473]=ERR_INVITEONLYCHAN
numeric[474]=ERR_BANNEDFROMCHAN
numeric[475]=ERR_BADCHANNELKEY
numeric[477]=ERR_NEEDREGGEDNICK # Not from any RFC.
numeric[478]=ERR_BANLISTFULL
numeric[480]=ERR_CANNOTKNOCK # Not from any RFC.
numeric[481]=ERR_NOPRIVILEGES
numeric[482]=ERR_CHANOPRIVSNEEDED
numeric[484]=ERR_ATTACKDENY # Name on Unreal. No idea use on Unreal.
# InspIRCd: 484 <nick> <channel> :Can't kick user <nick> from channel (+Q set)
numeric[489]=ERR_SECUREONLYCHAN # Not from any RFC. Used on Unreal and InspIRCd at least.
numeric[490]=ERR_ALLMUSTUSESSL # InspIRCd specific numeric. I made up this name, I don't know correct name.
# 490 <nick> <channel> :all members of the channel must be connected via SSL
numeric[491]=ERR_NOOPERHOST
numeric[495]=ERR_NOREJOINONKICK # InspIRCd specific numeric. I made up this name, I don't know correct name.
# 495 <nick> <channel> :You cannot rejoin this channel yet after being kicked (+J)
numeric[499]=ERR_CHANOWNPRIVNEEDED # Not from any RFC. Unreal got this at least.
numeric[501]=ERR_UMODEUNKNOWNFLAG # Some send this for unknown umodes. not all.
numeric[502]=ERR_USERSDONTMATCH # Trying to change mode for other user.
# Others. Not from any RFC but semi standard.
numeric[600]=RPL_LOGON # Unreal, InspIRCd and more
numeric[601]=RPL_LOGOFF # Unreal, InspIRCd and more
numeric[602]=RPL_WATCHOFF # Unreal, InspIRCd and more
numeric[604]=RPL_NOWON # Unreal, InspIRCd and more
numeric[605]=RPL_NOWOFF # Unreal, InspIRCd and more
numeric[606]=RPL_WATCHLIST # Unreal, InspIRCd and more
numeric[607]=RPL_ENDOFWATCHLIST # Unreal, InspIRCd and more
numeric[671]=RPL_WHOISSECURE # Used on Unreal for ssl clients.
# IRCd specific, these are InspIRCd ones unless said otherwise.
# As we can't include more than one meaning for every numeric conflicting
# ones may be added as comments. Modules depending on 9xx numerics should use the raw value.
# As I mainly use InspIRCd I prioritize those. ;)
numeric[900]=RPL_MODULES
numeric[901]=RPL_ENDOFMODULES
numeric[902]=RPL_COMMANDS # 902 <nick> :<command> <module name> <minimum parameters>
numeric[903]=RPL_ENDOFCOMMANDS # 903 <nick> :End of COMMANDS list
numeric[936]=ERR_CENSORED # 936 <nick> <channel> <word> :Your message contained a censored word, and was blocked
numeric[937]=ERR_ALREDYCENSORED # 937 <nick> <channel> :The word %s is already on the spamfilter list
numeric[938]=ERR_NOTCENSORED # 938 <nick> <channel> :No such spamfilter word is set
numeric[939]=ERR_SPAMFILTERLISTFULL # 939 <nick> <channel> :Channel spamfilter list is full
numeric[940]=RPL_ENDOFSPAMFILTER # 940 <nick> <channel> :End of channel spamfilter list
numeric[941]=RPL_SPAMFILTER # 941 <nick> <channel> <spamfilter>
numeric[942]=ERR_INVALIDNICK # 942 <nick> <nick> :Invalid user specified.
numeric[950]=RPL_SILENCEREMOVED # 950 <nick> <nick> :Removed <nick>!*@* from silence list
numeric[951]=RPL_SILENCEADDED # 951 <nick> <nick> :Added <nick>!*@* to silence list
numeric[952]=ERR_ALREADYSILENCE # 952 <nick> <nick> :<nick> is already on your silence list
numeric[972]=ERR_CANNOTDOCOMMAND # Unreal uses 972 (ERR_CANNOTDOCOMMAND) for umode +q, and other failed kicks.
# According to http://www.alien.net.au/irc/irc2numerics.html:
# "Works similarly to all of KineIRCd's CANNOT* numerics. This one indicates that a
# command could not be performed for an arbitrary reason. For example, a halfop trying to kick an op."
numeric[974]=ERR_CANNOTCHANGECHANMODE # Unreal uses 974 (ERR_CANNOTCHANGECHANMODE ?) for ERR_ALLMUSTUSESSL.

99
transport/dev-tcp.sh Normal file
View File

@ -0,0 +1,99 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## A transport module using /dev/tcp
#---------------------------------------------------------------------
# A list of features supported
# These are used: ipv4, ipv6, ssl, nossl, bind
transport_supports="ipv4 ipv6 nossl"
# Check if all the stuff needed to use this transport is available
# Return status
# 0 yes
# 1 no
transport_check_support() {
# If anyone can tell me how to check if /dev/tcp is supported
# without trying to make a connection (that could fail for so
# many other reasons), please contact me.
echo "NOTE: It is possible that this transport is not supported on your system"
echo " However, there is no way it can be checked except trying to connect."
echo " If you see an error below try netcat or socat transport instead."
return 0
}
# Try to connect
# Parameters
# $1 hostname/IP
# $2 port
# $3 If 1 use SSL. If the module does not support it, just ignore it.
# $4 IP to bind to if any and if supported
# If the module does not support it, just ignore it.
# Return status
# 0 if Ok
# 1 if connection failed
transport_connect() {
exec 3<&-
exec 3<> "/dev/tcp/${1}/${2}"
time_get_current 'transport_lastvalidtime'
}
# Called to close connection
# No parameters, no return code check
transport_disconnect() {
exec 3<&-
# To force code to consider this disconnected.
transport_lastvalidtime=0
}
# Return status
# 0 If connection is still alive
# 1 If it isn't.
transport_alive() {
local newtime=
time_get_current 'newtime'
(( newtime - transport_lastvalidtime > 300 )) && return 1
return 0
}
# Return a line in the variable line.
# Return status
# 0 If Ok
# 1 If connection failed
transport_read_line() {
read -ru 3 line
# Fail.
if [[ $? -ne 0 ]]; then
return 1
else
time_get_current 'transport_lastvalidtime'
fi
line=${line//$'\r'/}
}
# Send a line
# Parameters
# $* send this
# Return code not checked.
transport_write_line() {
echo "$*" >&3
}

121
transport/gnutls.sh Normal file
View File

@ -0,0 +1,121 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## A transport module using gnutls-cli
#---------------------------------------------------------------------
# A list of features supported
# These are used: ipv4, ipv6, ssl, nossl, bind
transport_supports="ipv4 ipv6 ssl"
# Check if all the stuff needed to use this transport is available
# Return status
# 0 yes
# 1 no
transport_check_support() {
hash gnutls-cli >/dev/null 2>&1 || {
log_fatal "Can't find gnutls-cli (needed for this transport)"
return 1
}
hash mkfifo >/dev/null 2>&1 || {
log_fatal "Can't find mkfifo (needed for this transport)"
return 1
}
return 0
}
# Try to connect
# Parameters
# $1 hostname/IP
# $2 port
# $3 If 1 use SSL. If the module does not support it, just ignore it.
# $4 IP to bind to if any and if supported
# If the module does not support it, just ignore it.
# Return status
# 0 if Ok
# 1 if connection failed
transport_connect() {
transport_tmp_dir_file="$(mktemp -dt envbot.gnutls.XXXXXXXXXX)" || return 1
# To keep this simple, from client perspective.
# We WRITE to out and READ from in
mkfifo "${transport_tmp_dir_file}/in"
mkfifo "${transport_tmp_dir_file}/out"
exec 3<&-
exec 4<&-
local myargs
[[ $config_server_ssl_accept_invalid -eq 1 ]] && myargs="--insecure"
gnutls-cli "$1" -p "$2" $myargs < "${transport_tmp_dir_file}/out" > "${transport_tmp_dir_file}/in" &
transport_pid="$!"
echo "$transport_pid" >> "${transport_tmp_dir_file}/pid"
exec 3>"${transport_tmp_dir_file}/out"
exec 4<"${transport_tmp_dir_file}/in"
# To be able to wait for error.
sleep 2
kill -0 "$transport_pid" >/dev/null 2>&1 || return 1
time_get_current 'transport_lastvalidtime'
}
# Called to close connection
# No parameters, no return code check
transport_disconnect() {
# It might not be running.
kill "$(< "${transport_tmp_dir_file}/pid")" >/dev/null 2>&1
rm -rf "${transport_tmp_dir_file}"
exec 3<&-
exec 4<&-
# To force code to consider this disconnected.
transport_lastvalidtime=0
}
# Return status
# 0 If connection is still alive
# 1 If it isn't.
transport_alive() {
kill -0 "$transport_pid" >/dev/null 2>&1 || return 1
local newtime=
time_get_current 'newtime'
(( $newtime - $transport_lastvalidtime > 300 )) && return 1
return 0
}
# Return a line in the variable line.
# Return status
# 0 If Ok
# 1 If connection failed
transport_read_line() {
read -ru 4 line
# Fail.
if [[ $? -ne 0 ]]; then
return 1
else
time_get_current 'transport_lastvalidtime'
fi
line=${line//$'\r'/}
}
# Send a line
# Parameters
# $* send this
# Return code not checked.
transport_write_line() {
kill -0 "$transport_pid" >/dev/null 2>&1 && echo "$*" >&3
}

130
transport/netcat.sh Normal file
View File

@ -0,0 +1,130 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## A transport module using netcat
#---------------------------------------------------------------------
# A list of features supported
# These are used: ipv4, ipv6, ssl, nossl, bind
# Yes I know some versions of netcat support encryption and some
# other ones support IPv6. I used GNU netcat and I couldn't find
# a way to detect what is supported in current netcat.
# Also those other netcat variants require you to pass some command
# line argument to enable use of IPv6. (nc6 doesn't)
# netcat got to many problems, use either dev-tcp or socat for
# non-SSL transport really!
transport_supports="ipv4 nossl bind"
# Check if all the stuff needed to use this transport is available
# Return status
# 0 yes
# 1 no
transport_check_support() {
[[ -x "$config_transport_netcat_path" ]] || {
log_fatal "Can't find netcat (needed for this transport)"
return 1
}
hash mkfifo >/dev/null 2>&1 || {
log_fatal "Can't find mkfifo (needed for this transport)"
return 1
}
return 0
}
# Try to connect
# Parameters
# $1 hostname/IP
# $2 port
# $3 If 1 use SSL. If the module does not support it, just ignore it.
# $4 IP to bind to if any and if supported
# If the module does not support it, just ignore it.
# Return status
# 0 if Ok
# 1 if connection failed
transport_connect() {
transport_tmp_dir_file="$(mktemp -dt envbot.netcat.XXXXXXXXXX)" || return 1
# To keep this simple, from client perspective.
# We WRITE to out and READ from in
mkfifo "${transport_tmp_dir_file}/in"
mkfifo "${transport_tmp_dir_file}/out"
exec 3<&-
exec 4<&-
local myargs
if [[ $4 ]]; then
myargs="-s $4"
fi
"$config_transport_netcat_path" "$1" "$2" < "${transport_tmp_dir_file}/out" > "${transport_tmp_dir_file}/in" &
transport_pid="$!"
echo "$transport_pid" >> "${transport_tmp_dir_file}/pid"
exec 3>"${transport_tmp_dir_file}/out"
exec 4<"${transport_tmp_dir_file}/in"
# To be able to wait for error.
sleep 2
kill -0 "$transport_pid" >/dev/null 2>&1 || return 1
time_get_current 'transport_lastvalidtime'
}
# Called to close connection
# No parameters, no return code check
transport_disconnect() {
# It might not be running.
kill "$(< "${transport_tmp_dir_file}/pid")" >/dev/null 2>&1
rm -rf "${transport_tmp_dir_file}"
exec 3<&-
exec 4<&-
# To force code to consider this disconnected.
transport_lastvalidtime=0
}
# Return status
# 0 If connection is still alive
# 1 If it isn't.
transport_alive() {
kill -0 "$transport_pid" >/dev/null 2>&1 || return 1
local newtime=
time_get_current 'newtime'
(( newtime - transport_lastvalidtime > 300 )) && return 1
return 0
}
# Return a line in the variable line.
# Return status
# 0 If Ok
# 1 If connection failed
transport_read_line() {
read -ru 4 line
# Fail.
if [[ $? -ne 0 ]]; then
return 1
else
time_get_current 'transport_lastvalidtime'
fi
line=${line//$'\r'/}
}
# Send a line
# Parameters
# $* send this
# Return code not checked.
transport_write_line() {
kill -0 "$transport_pid" >/dev/null 2>&1 && echo "$*" >&3
}

126
transport/openssl.sh Normal file
View File

@ -0,0 +1,126 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## A transport module using openssl s_client
#---------------------------------------------------------------------
# A list of features supported
# These are used: ipv4, ipv6, ssl, nossl, bind
transport_supports="ipv4 ipv6 ssl"
# Check if all the stuff needed to use this transport is available
# Return status
# 0 yes
# 1 no
transport_check_support() {
hash openssl >/dev/null 2>&1 || {
log_fatal "Can't find openssl (needed for this transport)"
return 1
}
hash mkfifo >/dev/null 2>&1 || {
log_fatal "Can't find mkfifo (needed for this transport)"
return 1
}
return 0
}
# Try to connect
# Parameters
# $1 hostname/IP
# $2 port
# $3 If 1 use SSL. If the module does not support it, just ignore it.
# $4 IP to bind to if any and if supported
# If the module does not support it, just ignore it.
# Return status
# 0 if Ok
# 1 if connection failed
transport_connect() {
transport_tmp_dir_file="$(mktemp -dt envbot.openssl.XXXXXXXXXX)" || return 1
# To keep this simple, from client perspective.
# We WRITE to out and READ from in
mkfifo "${transport_tmp_dir_file}/in"
mkfifo "${transport_tmp_dir_file}/out"
exec 3<&-
exec 4<&-
local myargs
if [[ $config_server_ssl_accept_invalid -eq 1 ]]; then
myargs="-verify 0"
else
myargs="-verify 10"
fi
[[ $config_server_ssl_verbose -ne 1 ]] && myargs+=" -quiet"
openssl s_client -connect "$1:$2" $myargs < "${transport_tmp_dir_file}/out" > "${transport_tmp_dir_file}/in" &
transport_pid="$!"
echo "$transport_pid" >> "${transport_tmp_dir_file}/pid"
exec 3>"${transport_tmp_dir_file}/out"
exec 4<"${transport_tmp_dir_file}/in"
# To be able to wait for error.
sleep 2
kill -0 "$transport_pid" >/dev/null 2>&1 || return 1
time_get_current 'transport_lastvalidtime'
}
# Called to close connection
# No parameters, no return code check
transport_disconnect() {
# It might not be running.
kill "$(< "${transport_tmp_dir_file}/pid")" >/dev/null 2>&1
rm -rf "${transport_tmp_dir_file}"
exec 3<&-
exec 4<&-
# To force code to consider this disconnected.
transport_lastvalidtime=0
}
# Return status
# 0 If connection is still alive
# 1 If it isn't.
transport_alive() {
kill -0 "$transport_pid" >/dev/null 2>&1 || return 1
local newtime=
time_get_current 'newtime'
(( newtime - transport_lastvalidtime > 300 )) && return 1
return 0
}
# Return a line in the variable line.
# Return status
# 0 If Ok
# 1 If connection failed
transport_read_line() {
read -ru 4 line
# Fail.
if [[ $? -ne 0 ]]; then
return 1
else
time_get_current 'transport_lastvalidtime'
fi
line=${line//$'\r'/}
}
# Send a line
# Parameters
# $* send this
# Return code not checked.
transport_write_line() {
kill -0 "$transport_pid" >/dev/null 2>&1 && echo "$*" >&3
}

184
transport/socat.sh Normal file
View File

@ -0,0 +1,184 @@
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## A transport module using socat
#---------------------------------------------------------------------
# A list of features supported
# This is set in transport_check_support
transport_check_support=''
# Check if all the stuff needed to use this transport is available
# Return status
# 0 Yes
# 1 No
transport_check_support() {
hash socat >/dev/null 2>&1 || {
log_fatal "Can't find socat (needed for this transport)"
return 1
}
hash mkfifo >/dev/null 2>&1 || {
log_fatal "Can't find mkfifo (needed for this transport)"
return 1
}
# Build transport_supports
local features="$(socat -V | grep -E 'socat version|define')"
# These seems to always be supported?
transport_supports="nossl bind"
if grep -q WITH_IP4 <<< "$features"; then
transport_supports+=" ipv4"
fi
if grep -q WITH_IP6 <<< "$features"; then
transport_supports+=" ipv6"
fi
if grep -q WITH_OPENSSL <<< "$features"; then
transport_supports+=" ssl"
fi
if [[ -z $config_transport_socat_protocol_family ]]; then
log_fatal "You need to set config_transport_socat_protocol_family in your config to either ipv4 or ipv6."
return 1
fi
# Check for older version
if grep -q "socat version 1.4" <<< "$features"; then
# SSL + IPv6 is not supported with socat-1.4.x
if [[ $config_server_ssl -ne 0 ]]; then
# list_remove is not yet loaded so we can't use that here...
transport_supports="$(sed "s/ipv6//" <<< "$transport_supports")"
fi
# This is to be sure socat-1.4.x works
# Modules should normally never set config_* in them
# This is an exception.
if [[ -z $config_transport_socat_protocol_family ]]; then
config_transport_socat_protocol_family="ipv4"
fi
# Remember version to find what workaround to use in transport_connect()
transport_socat_is_14="1"
else
transport_socat_is_14="0"
fi
return 0
}
# Try to connect
# Parameters
# $1 hostname/IP
# $2 port
# $3 If 1 use SSL. If the module does not support it, just ignore it.
# $4 IP to bind to if any and if supported
# If the module does not support it, just ignore it.
# Return status
# 0 if Ok
# 1 if connection failed
transport_connect() {
transport_tmp_dir_file="$(mktemp -dt envbot.socat.XXXXXXXXXX)" || return 1
# To keep this simple, from client perspective.
# We WRITE to out and READ from in
mkfifo "${transport_tmp_dir_file}/in"
mkfifo "${transport_tmp_dir_file}/out"
exec 3<&-
exec 4<&-
local addrargs socatnewargs
if [[ $3 -eq 1 ]]; then
addrargs="OPENSSL"
# HACK: Support IPv6 with SSL if socat is new enough.
if [[ $transport_socat_is_14 -eq 0 ]]; then
if [[ $config_transport_socat_protocol_family = "ipv6" ]]; then
socatnewargs=",pf=ip6"
elif [[ $config_transport_socat_protocol_family = "ipv4" ]]; then
socatnewargs=",pf=ip4"
fi
fi
elif [[ $config_transport_socat_protocol_family = "ipv6" ]]; then
addrargs="TCP6"
elif [[ $config_transport_socat_protocol_family = "ipv4" ]]; then
addrargs="TCP4"
fi
# Add in hostname and port.
addrargs+=":${1}:${2}"
# Should we bind an IP? Then lets do that.
if [[ $4 ]]; then
addrargs+=",bind=$4"
fi
# If version 1.5 or later add in extra args
if [[ $transport_socat_is_14 -eq 0 ]]; then
addrargs+="${socatnewargs}"
fi
# If we use SSL check if we should verify.
if [[ $3 -eq 1 && $config_server_ssl_accept_invalid -eq 1 ]]; then
addrargs+=",verify=0"
fi
socat STDIO "$addrargs" < "${transport_tmp_dir_file}/out" > "${transport_tmp_dir_file}/in" &
transport_pid="$!"
echo "$transport_pid" >> "${transport_tmp_dir_file}/pid"
exec 3>"${transport_tmp_dir_file}/out"
exec 4<"${transport_tmp_dir_file}/in"
# To be able to wait for error.
sleep 2
kill -0 "$transport_pid" >/dev/null 2>&1 || return 1
time_get_current 'transport_lastvalidtime'
}
# Called to close connection
# No parameters, no return code check
transport_disconnect() {
# It might not be running.
kill "$(< "${transport_tmp_dir_file}/pid")" >/dev/null 2>&1
rm -rf "${transport_tmp_dir_file}"
exec 3<&-
exec 4<&-
# To force code to consider this disconnected.
transport_lastvalidtime=0
}
# Return status
# 0 If connection is still alive
# 1 If it isn't.
transport_alive() {
kill -0 "$transport_pid" >/dev/null 2>&1 || return 1
local newtime=
time_get_current 'newtime'
(( newtime - transport_lastvalidtime > 300 )) && return 1
return 0
}
# Return a line in the variable line.
# Return status
# 0 If Ok
# 1 If connection failed
transport_read_line() {
read -ru 4 line
# Fail.
if [[ $? -ne 0 ]]; then
return 1
else
time_get_current 'transport_lastvalidtime'
fi
line=${line//$'\r'/}
}
# Send a line
# Parameters
# $* send this
# Return code not checked.
transport_write_line() {
kill -0 "$transport_pid" >/dev/null 2>&1 && echo "$*" >&3
}