Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ee0ec3f727 |
27
BUGS
Executable file
27
BUGS
Executable file
@ -0,0 +1,27 @@
|
||||
Investigate:
|
||||
|
||||
[2011-02-10 03:51:47.148968] ALL_RAW_MESSAGES: [':malacoda!~liberius@Rizon-8449AE11.satx.res.rr.com PRIVMSG #eightbar :!eightball Is eightball working?'] from irc.kickassanime.org to None
|
||||
Traceback (most recent call last):
|
||||
File "./tenquestionmarks.py", line 383, in <module>
|
||||
tqm.loop()
|
||||
File "./tenquestionmarks.py", line 125, in loop
|
||||
self.ircobj.process_once()
|
||||
File "./lib/irclib/irclib.py", line 214, in process_once
|
||||
self.process_data(i)
|
||||
File "./lib/irclib/irclib.py", line 183, in process_data
|
||||
c.process_data()
|
||||
File "./lib/irclib/irclib.py", line 571, in process_data
|
||||
self._handle_event(Event(command, prefix, target, [m]))
|
||||
File "./lib/irclib/irclib.py", line 594, in _handle_event
|
||||
self.irclibobj._handle_event(self, event)
|
||||
File "./lib/irclib/irclib.py", line 326, in _handle_event
|
||||
if handler[1](connection, event) == "NO MORE":
|
||||
File "./tenquestionmarks.py", line 79, in _dispatcher
|
||||
irclib.SimpleIRCClient._dispatcher(self,connection,event)
|
||||
File "./lib/irclib/irclib.py", line 1043, in _dispatcher
|
||||
getattr(self, m)(c, e)
|
||||
File "./tenquestionmarks.py", line 163, in on_pubmsg
|
||||
if message.startswith(self.command_prefix):
|
||||
TypeError: expected a character buffer object
|
||||
|
||||
python 2.6.6
|
23
Cargo.toml
23
Cargo.toml
@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name="tenquestionmarks"
|
||||
version="0.0.3"
|
||||
authors=["Adrian Malacoda <adrian.malacoda@monarch-pass.net>"]
|
||||
|
||||
[dependencies]
|
||||
hlua = "0.4.1"
|
||||
discord = "0.8.0"
|
||||
toml = "0.4.5"
|
||||
crossbeam = "0.3.2"
|
||||
rand = "0.4.2"
|
||||
log = "0.4.1"
|
||||
env_logger = "0.5.3"
|
||||
transformable_channels = "0.1.1"
|
||||
time = "0.1"
|
||||
regex = "0.2"
|
||||
multimap = "0.4.0"
|
||||
notify = "4.0.0"
|
||||
irc = "0.11.8" # latest version which supports old openssl version (required by discord)
|
||||
thread_local = "0.3"
|
||||
pvn = { git = "http://gitlab.monarch-pass.net/malacoda/pvn.git" }
|
||||
echobox = { git = "http://gitlab.monarch-pass.net/malacoda/echobox.git" }
|
||||
stc = { git = "http://gitlab.monarch-pass.net/malacoda/stc.git" }
|
17
README.md
17
README.md
@ -1,17 +0,0 @@
|
||||
# tenquestionmarks chat bot
|
||||
tenquestionmarks is an extensible, scriptable chat bot. This iteration is written in rust.
|
||||
|
||||
## Configuration
|
||||
Configuration is done in TOML. By default, tenquestionmarks looks for `tenquestionmarks.toml`.
|
||||
|
||||
As of tenquestionmarks 0.0.3, tenquestionmarks supports a limited form of live configuration reloading. tenquestionmarks monitors the configuration file and, upon detecting changes, will emit a reconfiguration event to all modules to ask them to reconfigure. Reconfiguration is implemented on a per-module basis.
|
||||
|
||||
## Modules
|
||||
tenquestionmarks is a series of modules. Modules produce events and consume events.
|
||||
|
||||
In this particular iteration of tenquestionmarks, there are at most two threads spawned for a module: an event loop thread (which has access to an event emitter and event receiver), and the event dispatcher thread associated with said event emitter. The event dispatcher thread takes events emitted from the event loop thread and pushes them to downstream modules' event receivers. No event dispatcher thread will be spawned for a module with no downstreams.
|
||||
|
||||
As of tenquestionmarks 0.0.3, each module is required to explicitly identify which modules it wishes to send and/or receive events from.
|
||||
|
||||
## Events
|
||||
Events are things such as message, join, quit.
|
12
TODO
Executable file
12
TODO
Executable file
@ -0,0 +1,12 @@
|
||||
Modules:
|
||||
- Welcome
|
||||
- Help
|
||||
- Log
|
||||
- Stats
|
||||
- Forum Integration (smf, mybb)
|
||||
|
||||
Make more object-oriented (i.e. user objects, channel objects instead of strings)
|
||||
|
||||
Documentation
|
||||
|
||||
Release
|
11
TODO.md
11
TODO.md
@ -1,11 +0,0 @@
|
||||
# TODO
|
||||
## 0.0.2
|
||||
### Filters
|
||||
* Basic mechanism for filtering out messages between modules.
|
||||
* Filter tags are of the type `key:value` (e.g. `type:message`, `username:Kuschelyagi`)
|
||||
* Each individual filter is a stack of one or more filter values.
|
||||
* Event must match AT LEAST one of these filters IN FULL in order to be accepted.
|
||||
* For example, module `foo` declares `filters = [["username:Kuschelyagi", "channel:shitpost"]]`. This is a single filter with two tag conditions. This means events must match on BOTH tags to be accepted by `foo`
|
||||
* If, on the other hand, module `foo` instead declares `filters = ["username:Kuschelyagi", "channel:shitpost"]` then these are separate filters and the event must only match on ONE of them.
|
||||
* Other proposed filter notations:
|
||||
* `filters = [{ username = "Kuschelyagi", channel = "shitpost" }]`
|
2858
lib/feedparser/feedparser.py
Executable file
2858
lib/feedparser/feedparser.py
Executable file
File diff suppressed because it is too large
Load Diff
510
lib/irclib/COPYING
Executable file
510
lib/irclib/COPYING
Executable file
@ -0,0 +1,510 @@
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations
|
||||
below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
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 and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
^L
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it
|
||||
becomes a de-facto standard. To achieve this, non-free programs must
|
||||
be allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
^L
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, 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 library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete 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 distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
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 Library or any portion
|
||||
of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
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 Library, 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 Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
^L
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you 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.
|
||||
|
||||
If distribution of 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 satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
^L
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at least
|
||||
three years, to give the same user the materials specified in
|
||||
Subsection 6a, above, for a charge no more than the cost of
|
||||
performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be 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.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
^L
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library 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.
|
||||
|
||||
9. 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 Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
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 with
|
||||
this License.
|
||||
^L
|
||||
11. 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 Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library 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 Library.
|
||||
|
||||
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.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library 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.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser 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 Library
|
||||
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 Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
^L
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
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
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "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
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. 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 LIBRARY 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
|
||||
LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
^L
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms
|
||||
of the ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library.
|
||||
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 library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or
|
||||
your school, if any, to sign a "copyright disclaimer" for the library,
|
||||
if necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James
|
||||
Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
|
||||
|
420
lib/irclib/ChangeLog
Executable file
420
lib/irclib/ChangeLog
Executable file
@ -0,0 +1,420 @@
|
||||
2005-12-24 Keltus <keltus@users.sourceforge.net>
|
||||
* Released version 0.4.6.
|
||||
|
||||
* irclib.py (VERSION):
|
||||
* python-irclib.spec.in:
|
||||
Preparations for version 0.4.6.
|
||||
|
||||
2005-12-23 Keltus <keltus@users.sourceforge.net>
|
||||
* dccsend:
|
||||
* dccreceive:
|
||||
* irclib.py:
|
||||
* ircbot.py:
|
||||
* irccat:
|
||||
* irccat2:
|
||||
* servermap:
|
||||
* testbot.py:
|
||||
Code modernization - String methods used instead of deprecated
|
||||
string functions, keyword 'in' used for membership testing instead
|
||||
of 'has_key' method, etc.
|
||||
|
||||
2005-12-06 Keltus <keltus@users.sourceforge.net>
|
||||
* irclib.py (ServerConnection.process_data): Reversed fix from
|
||||
2005-05-28. This is strange because there was a bug before and
|
||||
now it's gone. Either python changed something, or the IRC
|
||||
networks changed something. Confirmed by peter.
|
||||
|
||||
2005-11-03 Keltus <keltus@users.sourceforge.net>
|
||||
* irclib.py (numeric_events): Renamed numeric code 332 from topic
|
||||
to currenttopic (the message when "/topic <chan>" is sent), so it
|
||||
doesn't collide with TOPIC (the message when the topic is set).
|
||||
|
||||
2005-08-27 Keltus <keltus@users.sourceforge.net>
|
||||
* irclib.py (ServerConnection.disconnect): Fixed infinitely
|
||||
recursive calls when disconnecting with a failed connection. Bug
|
||||
reported by Erik Max Francis.
|
||||
|
||||
2005-08-18 Keltus <keltus@users.sourceforge.net>
|
||||
* irclib.py: Made ServerConnection.disconnect more consistant and
|
||||
changed some functions to use it instead of quit. Previously,
|
||||
disconnect would ignore the quit message, but now it sends a quit
|
||||
message and disconnect. Suggestion by Erik Max Francis.
|
||||
* ircbot.py: Changed to use ServerConnection.disconnect instead of
|
||||
ServerConnection.quit as well.
|
||||
|
||||
2005-05-28 Keltus <keltus@users.sourceforge.net>
|
||||
* irclib.py (ServerConnection.process_data): Fixed quit arguments
|
||||
to return a list rather than a list of a list. Patch from peter.
|
||||
|
||||
2005-05-18 Keltus <keltus@users.sourceforge.net>
|
||||
* Released version 0.4.5.
|
||||
|
||||
* irclib.py (ServerConnection.__init__): Added self.socket = None
|
||||
to be able to process events when ServerConnection is not
|
||||
connected to a socket. Patch from alst.
|
||||
|
||||
* irclib.py (VERSION):
|
||||
* python-irclib.spec.in:
|
||||
Preparations for version 0.4.5.
|
||||
|
||||
2005-04-26 Keltus <keltus@users.sourceforge.net>
|
||||
* irclib.py (IRC.__doc__): Corrected server.process_forever() to
|
||||
irc.process_forever(). Suggestion by olecom.
|
||||
|
||||
2005-04-17 Keltus <keltus@users.sourceforge.net>
|
||||
* irclib.py (ServerConnection.process_data): Moved event
|
||||
translation code.
|
||||
* irclib.py (ServerConnection): Reverted the 2005-01-28 change
|
||||
because it breaks jump_server().
|
||||
* irclib.py: minor comment changes
|
||||
|
||||
2005-04-03 Keltus <keltus@users.sourceforge.net>
|
||||
* irclib.py (protocol_events): Added "pong" and "invite" events.
|
||||
Patch from Adam Mikuta.
|
||||
* irclib.py (ServerConnection.part): Added message parameter.
|
||||
Patch from Adam Mikuta.
|
||||
|
||||
2005-02-23 Keltus <keltus@users.sourceforge.net>
|
||||
* Released version 0.4.4.
|
||||
|
||||
* irclib.py (VERSION):
|
||||
* python-irclib.spec.in:
|
||||
Preparations for version 0.4.4.
|
||||
|
||||
2005-01-28 Keltus <keltus@users.sourceforge.net>
|
||||
* irclib.py: (ServerConnection): Moved
|
||||
self.irclibobj._remove_connection call from close() to
|
||||
disconnect(). Patch from Alexey Nezhdanov.
|
||||
|
||||
2005-01-25 Keltus <keltus@users.sourceforge.net>
|
||||
* irclib.py (ServerConnection.connect): closes socket if a
|
||||
connection does not occur
|
||||
* irclib.py (ServerConnection.connect): "Changing server" ->
|
||||
"Changing servers" (more ubiquitous quit phrase)
|
||||
|
||||
2005-01-23 Keltus <keltus@users.sourceforge.net>
|
||||
|
||||
* irclib.py: Removed depreciated apply functions. python-irclib is
|
||||
now compatible with Python 1.6 and above.
|
||||
* testbot.py: Removed redundant extra start() call
|
||||
|
||||
2005-01-20 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* Released version 0.4.3.
|
||||
* Makefile: Removed more GNU make specific constructs.
|
||||
|
||||
2005-01-19 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* Makefile: Don't require GNU make.
|
||||
|
||||
2005-01-19 Keltus <keltus@users.sourceforge.net>
|
||||
|
||||
* ircbot.py (IRCDict.__iter__): Added __iter__ method for IRCDict.
|
||||
|
||||
2005-01-17 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* ircbot.py (IRCDict.__contains__): Added __contains__method for
|
||||
IRCDict. Patch from Keltus.
|
||||
(SingleServerIRCBot.on_ctcp): Corrected default decoding of CTCP
|
||||
DCC CHAT. Patch from Keltus.
|
||||
|
||||
* irclib.py (VERSION):
|
||||
* python-irclib.spec.in:
|
||||
Preparations for version 0.4.3.
|
||||
|
||||
* debian: Removed Debian package directory since python-irclib is
|
||||
in Debian now.
|
||||
|
||||
* ircbot.py (SingleServerIRCBot._on_namreply): Improved comment
|
||||
about arguments to the function. Patch from Keltus.
|
||||
(Channel.has_allow_external_messages): Renamed from
|
||||
has_message_from_outside_protection. Patch from Keltus.
|
||||
|
||||
* irclib.py (ServerConnection.quit): Added comment about how some
|
||||
IRC servers' treat QUIT messages. Patch from Keltus.
|
||||
|
||||
* ircbot.py (SingleServerIRCBot.jump_server): Improved jump_server
|
||||
behaviour. Patch from Keltus.
|
||||
|
||||
2004-08-04 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* irclib.py (ServerConnection.process_data): Added "bonus" action
|
||||
event that is triggered on CTCP ACTION messages.
|
||||
|
||||
2004-07-09 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* Released version 0.4.2.
|
||||
|
||||
* debian/rules: Remove built *.pyc files before making package.
|
||||
|
||||
* irclib.py (DEBUG):
|
||||
* debian/changelog:
|
||||
* python-irclib.spec.in:
|
||||
Preparations for version 0.4.2.
|
||||
|
||||
* irclib.py (ServerNotConnectedError): New exception.
|
||||
(ServerConnection.send_raw): Fix bug #922446, "Raise
|
||||
IllegalStateException in send_raw when disconnected".
|
||||
|
||||
2003-10-30 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* Released version 0.4.1.
|
||||
|
||||
* debian/examples: Added dccreceive and dccsend as example files
|
||||
in Debian.
|
||||
|
||||
* python-irclib.spec.in: Likewise.
|
||||
|
||||
2003-10-29 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* debian: Added Debian packaging files.
|
||||
|
||||
* setup.py.in: Create setup.py from setup.py.in.
|
||||
|
||||
* python-irclib.spec.in: RPM spec file from Gary Benson.
|
||||
|
||||
* testbot.py (TestBot.on_nicknameinuse): New method.
|
||||
|
||||
* irclib.py (ServerConnection.process_data): Record nickname when
|
||||
welcome message is sent to trap nickname change triggered in a
|
||||
nicknameinuse callback.
|
||||
|
||||
* ircbot.py (SingleServerIRCBot._on_join): Use
|
||||
Connection.get_nickname instead of relying on self._nickname.
|
||||
(SingleServerIRCBot._on_kick): Likewise.
|
||||
(SingleServerIRCBot._on_part): And here too.
|
||||
(SingleServerIRCBot._on_nick): No need to remember nickname change
|
||||
here.
|
||||
|
||||
2003-08-31 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* Released version 0.4.0.
|
||||
|
||||
Implemented DCC support (based on patches from Adam Langley and
|
||||
Marco Bettio):
|
||||
|
||||
* irclib.py (IRC.dcc): New method.
|
||||
(DCCConnectionError): New class.
|
||||
(DCCConnection): New class.
|
||||
(SimpleIRCClient.__init__): Added dcc_connections attribute.
|
||||
(SimpleIRCClient._dcc_disconnect): New method.
|
||||
(SimpleIRCClient.connect): Added localaddress and
|
||||
localport parameters. The socket will be bound accordingly before
|
||||
connecting.
|
||||
(SimpleIRCClient.dcc_connect): New method.
|
||||
(SimpleIRCClient.dcc_listen): New method.
|
||||
(ip_numstr_to_quad): New function.
|
||||
(ip_quad_to_numstr): New function.
|
||||
|
||||
* ircbot.py (SingleServerIRCBot.on_ctcp): Relay DCC CHAT CTCPs to
|
||||
the on_dccchat method.
|
||||
|
||||
* testbot.py: Added support for accepting DCC chats and for
|
||||
initiating DCC chats via a "dcc" command.
|
||||
|
||||
* dccreceive: New example program.
|
||||
|
||||
* dccsend: New example program.
|
||||
|
||||
* Makefile: Added dccreceive and dccsend to dist files.
|
||||
|
||||
Other changes:
|
||||
|
||||
* setup.py: Added.
|
||||
|
||||
* irclib.py (ServerConnection.connect, ServerConnection.user):
|
||||
Send USER command according to RFC 2812.
|
||||
(ServerConnection.connect): Added localaddress and
|
||||
localport parameters. The socket will be bound accordingly before
|
||||
connecting.
|
||||
(ServerConnection.process_data): Ignore empty lines from the
|
||||
server. (Patch by Jason Wies.)
|
||||
(ServerConnection._get_socket): Simplified.
|
||||
(ServerConnection.remove_global_handler): Added. (Patch from
|
||||
Brandon Beck.)
|
||||
|
||||
* ircbot.py (SingleServerIRCBot.on_ctcp): Prepend VERSION reply
|
||||
with VERSION. (Patch from Andrew Gaul.)
|
||||
|
||||
* Makefile: Added setup.py to dist files. Also create zip archive.
|
||||
|
||||
* README: Added requirements and installation sections.
|
||||
|
||||
2002-03-01 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* Released version 0.3.4.
|
||||
|
||||
Corrected problems spotted by Markku Hänninen <hmm@iki.fi>:
|
||||
|
||||
* irccat2 (IRCCat.on_welcome): Added missing connection argument.
|
||||
(IRCCat.on_join): Likewise.
|
||||
(IRCCat.on_disconnect): Likewise.
|
||||
|
||||
* irclib.py (ServerConnection.ison): Bug fix: Join nicks by space
|
||||
instead of commas.
|
||||
|
||||
* irclib.py (ServerConnection.whowas): Bug fix: Let the max
|
||||
argument default to the empty string.
|
||||
|
||||
* irclib.py (numeric_events): Added new events: traceservice,
|
||||
tracereconnect, tryagain, invitelist, endofinvitelist, exceptlist,
|
||||
endofexceptlist, unavailresource, nochanmodes, banlistfull,
|
||||
restricted and uniqopprivsneeded.
|
||||
|
||||
2002-02-17 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* Released version 0.3.3.
|
||||
|
||||
* Makefile, README, .cvsignore: Removed documentation generated by
|
||||
pythondoc. Use pydoc instead.
|
||||
|
||||
* servermap: Removed some excess whitespace.
|
||||
|
||||
* README: Mention http://python-irclib.sourceforge.net.
|
||||
|
||||
* Makefile (dist): Changed archive name from irclib-* to
|
||||
python-irclib-*.
|
||||
|
||||
Changed license from GPL 2 to LGPL 2.1:
|
||||
|
||||
* COPYING: New license text.
|
||||
|
||||
* irclib.py, ircbot.py, servermap: New license header.
|
||||
|
||||
2001-10-21 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* Released version 0.3.2.
|
||||
|
||||
* irclib.py (_parse_modes): Fixed problem found by Tom Morton: the
|
||||
mode parsing code bailed out if a unary mode character didn't have
|
||||
a corresponding argument.
|
||||
|
||||
* irclib.py (_alpha): Fixed bug found by Tom Morton: w was missing
|
||||
in the alphabet used by irc_lower().
|
||||
|
||||
* ircbot.py: Removed redundant import of is_channel.
|
||||
|
||||
* servermap: Clarified copyright and license.
|
||||
|
||||
* irccat: Ditto.
|
||||
|
||||
* irccat2: Ditto.
|
||||
|
||||
2000-12-11 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* Released version 0.3.1.
|
||||
|
||||
* irclib.py (IRC.process_once): Work-around for platform-dependent
|
||||
select() on Windows systems.
|
||||
|
||||
* ircbot.py: Clarification of SingleServerIRCBot doc string.
|
||||
|
||||
2000-11-26 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* Released version 0.3.0.
|
||||
|
||||
* Makefile (dist): Include ircbot.py again.
|
||||
|
||||
* README: Updated.
|
||||
|
||||
* irclib.py (ServerConnection.get_nickname): Renamed from
|
||||
get_nick_name.
|
||||
(ServerConnection._get_socket): Return None if not connected.
|
||||
|
||||
2000-11-25 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* irclib.py (ServerConnection.process_data): all_raw_messages
|
||||
instead of allrawmessages.
|
||||
(IRC._handle_event): Added "all_events" event type.
|
||||
(nm_to_n): Renamed from nick_from_nickmask.
|
||||
(nm_to_uh): Renamed from userhost_from_nickmask.
|
||||
(nm_to_h): Renamed from host_from_nickmask.
|
||||
(nm_to_u): Renamed from user_from_nickmask.
|
||||
(SimpleIRCClient): Created.
|
||||
|
||||
2000-11-22 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* irclib.py (lower_irc_string): Use translation instead.
|
||||
(ServerConnection.process_data): Split non-RFC-compliant lines a
|
||||
bit more intelligently.
|
||||
(ServerConnection.process_data): Removed unnecessary try/except
|
||||
block.
|
||||
(ServerConnection.get_server_name): Return empty server if
|
||||
unknown.
|
||||
(_rfc_1459_command_regexp): Tweaked a bit.
|
||||
|
||||
* ircbot.py: Rewritten.
|
||||
|
||||
2000-11-21 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* irclib.py (IRC.process_forever): Default to processing a bit
|
||||
more often.
|
||||
|
||||
2000-10-29 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* Released version 0.2.4.
|
||||
|
||||
* Makefile (dist): Include generated documentation in
|
||||
distribution.
|
||||
|
||||
* Makefile (doc): Make documentation.
|
||||
|
||||
* irclib.py: Updated documentation.
|
||||
|
||||
* irclib.py (is_channel): Included "!" as channel prefix.
|
||||
|
||||
2000-10-02 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* Released version 0.2.3.
|
||||
|
||||
* irclib.py (ServerConnection.connect): Make socket.connect() work
|
||||
for Python >= 1.6.
|
||||
|
||||
2000-09-26 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* Released version 0.2.2.
|
||||
|
||||
* irclib.py (ServerConnection.user): Fixed erroneous format
|
||||
string.
|
||||
|
||||
2000-09-24 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* Released version 0.2.1.
|
||||
|
||||
* irclib.py (ServerConnection.process_data): Bug fix (didn't keep
|
||||
track of nick name).
|
||||
(IRC.process_once): New method.
|
||||
(ServerConnection.process_data): Bug fix.
|
||||
(IRC.disconnect_all): Created.
|
||||
(IRC.exit): Removed.
|
||||
(ServerConnection.exit): Removed.
|
||||
(ServerConnection.connect): Follow RFC closer.
|
||||
(ServerConnection.user): Follow RFC closer.
|
||||
|
||||
* ircbot.py: Removed.
|
||||
|
||||
* irccat (on_disconnect): Just sys.exit(0).
|
||||
|
||||
* servermap (on_disconnect): Just sys.exit(0).
|
||||
|
||||
* irclib.py: Various documentation and some clean-ups.
|
||||
|
||||
1999-08-21 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* Released version 0.2.0.
|
||||
|
||||
* servermap: Updated to work with irclib 0.2.0.
|
||||
|
||||
* irccat: Updated to work with irclib 0.2.0.
|
||||
|
||||
* ircbot.py: Updated to work with irclib 0.2.0. The bot now
|
||||
checks every minute that it is connected. If it's not, it
|
||||
reconnects.
|
||||
|
||||
* irclib.py: Changes in how to create a ServerConnection object.
|
||||
Made the code for handling disconnection hopefully more robust.
|
||||
Renamed connect() to sconnect().
|
||||
|
||||
1999-06-19 Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
* irclib.py: Released 0.1.0.
|
42
lib/irclib/Makefile
Executable file
42
lib/irclib/Makefile
Executable file
@ -0,0 +1,42 @@
|
||||
VERSION := `sed -n -e '/VERSION = /{s/VERSION = \(.*\), \(.*\), \(.*\)/\1.\2.\3/;p;}' <irclib.py`
|
||||
|
||||
DISTFILES = \
|
||||
COPYING \
|
||||
ChangeLog \
|
||||
Makefile \
|
||||
README \
|
||||
dccreceive \
|
||||
dccsend \
|
||||
ircbot.py \
|
||||
irccat \
|
||||
irccat2 \
|
||||
irclib.py \
|
||||
python-irclib.spec \
|
||||
servermap \
|
||||
setup.py \
|
||||
testbot.py
|
||||
|
||||
PACKAGENAME = python-irclib-$(VERSION)
|
||||
|
||||
all: $(DISTFILES)
|
||||
|
||||
setup.py: setup.py.in
|
||||
sed 's/%%VERSION%%/'$(VERSION)'/g' setup.py.in >setup.py
|
||||
|
||||
python-irclib.spec: python-irclib.spec.in
|
||||
sed 's/%%VERSION%%/'$(VERSION)'/g' python-irclib.spec.in >python-irclib.spec
|
||||
|
||||
dist: $(DISTFILES)
|
||||
mkdir $(PACKAGENAME)
|
||||
cp -r $(DISTFILES) $(PACKAGENAME)
|
||||
tar cvzf $(PACKAGENAME).tar.gz $(PACKAGENAME)
|
||||
zip -r9yq $(PACKAGENAME).zip $(PACKAGENAME)
|
||||
rm -rf $(PACKAGENAME)
|
||||
|
||||
cvstag:
|
||||
ver=$(VERSION); echo cvs tag version_`echo $$ver | sed 's/\./_/g'`
|
||||
|
||||
clean:
|
||||
rm -rf *~ *.pyc build python-irclib.spec setup.py
|
||||
|
||||
.PHONY: all doc dist cvstag clean
|
106
lib/irclib/README
Executable file
106
lib/irclib/README
Executable file
@ -0,0 +1,106 @@
|
||||
irclib -- Internet Relay Chat (IRC) protocol client library
|
||||
-----------------------------------------------------------
|
||||
|
||||
The home of irclib.py is now:
|
||||
|
||||
http://python-irclib.sourceforge.net
|
||||
|
||||
This library is intended to encapsulate the IRC protocol at a quite
|
||||
low level. It provides an event-driven IRC client framework. It has
|
||||
a fairly thorough support for the basic IRC protocol, CTCP and DCC
|
||||
connections.
|
||||
|
||||
In order to understand how to make an IRC client, I'm afraid you more
|
||||
or less must understand the IRC specifications. They are available
|
||||
here:
|
||||
|
||||
http://www.irchelp.org/irchelp/rfc/
|
||||
|
||||
Requirements:
|
||||
|
||||
* Python 1.6 or newer.
|
||||
|
||||
Installation:
|
||||
|
||||
* Run "python setup.py install" or copy irclib.py and/or ircbot.py
|
||||
to an appropriate Python module directory.
|
||||
|
||||
The main features of the IRC client framework are:
|
||||
|
||||
* Abstraction of the IRC protocol.
|
||||
* Handles multiple simultaneous IRC server connections.
|
||||
* Handles server PONGing transparently.
|
||||
* Messages to the IRC server are done by calling methods on an IRC
|
||||
connection object.
|
||||
* Messages from an IRC server triggers events, which can be caught
|
||||
by event handlers.
|
||||
* Reading from and writing to IRC server sockets are normally done
|
||||
by an internal select() loop, but the select()ing may be done by
|
||||
an external main loop.
|
||||
* Functions can be registered to execute at specified times by the
|
||||
event-loop.
|
||||
* Decodes CTCP tagging correctly (hopefully); I haven't seen any
|
||||
other IRC client implementation that handles the CTCP
|
||||
specification subtilties.
|
||||
* A kind of simple, single-server, object-oriented IRC client class
|
||||
that dispatches events to instance methods is included.
|
||||
* DCC connection support.
|
||||
|
||||
Current limitations:
|
||||
|
||||
* The IRC protocol shines through the abstraction a bit too much.
|
||||
* Data is not written asynchronously to the server (and DCC peers),
|
||||
i.e. the write() may block if the TCP buffers are stuffed.
|
||||
* Like most projects, documentation is lacking...
|
||||
|
||||
Unfortunately, this library isn't as well-documented as I would like
|
||||
it to be. I think the best way to get started is to read and
|
||||
understand the example program irccat, which is included in the
|
||||
distribution.
|
||||
|
||||
The following files might be of interest:
|
||||
|
||||
* irclib.py
|
||||
|
||||
The library itself. Read the code along with comments and
|
||||
docstrings to get a grip of what it does. Use it at your own risk
|
||||
and read the source, Luke!
|
||||
|
||||
* irccat
|
||||
|
||||
A simple example of how to use irclib.py. irccat reads text from
|
||||
stdin and writes it to a specified user or channel on an IRC
|
||||
server.
|
||||
|
||||
* irccat2
|
||||
|
||||
The same as above, but using the SimpleIRCClient class.
|
||||
|
||||
* servermap
|
||||
|
||||
Another simple example. servermap connects to an IRC server,
|
||||
finds out what other IRC servers there are in the net and prints
|
||||
a tree-like map of their interconnections.
|
||||
|
||||
* testbot.py
|
||||
|
||||
An example bot that uses the SingleServerIRCBot class from
|
||||
ircbot.py. The bot enters a channel and listens for commands in
|
||||
private messages or channel traffic. It also accepts DCC
|
||||
invitations and echos back sent DCC chat messages.
|
||||
|
||||
* dccreceive
|
||||
|
||||
Receives a file over DCC.
|
||||
|
||||
* dccsend
|
||||
|
||||
Sends a file over DCC.
|
||||
|
||||
Enjoy.
|
||||
|
||||
Maintainer:
|
||||
keltus <keltus@users.sourceforge.net>
|
||||
|
||||
Original Founder:
|
||||
Joel Rosdahl <joel@rosdahl.net>
|
77
lib/irclib/dccreceive
Executable file
77
lib/irclib/dccreceive
Executable file
@ -0,0 +1,77 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# Example program using irclib.py.
|
||||
#
|
||||
# This program is free without restrictions; do anything you like with
|
||||
# it.
|
||||
#
|
||||
# Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
import irclib
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
class DCCReceive(irclib.SimpleIRCClient):
|
||||
def __init__(self):
|
||||
irclib.SimpleIRCClient.__init__(self)
|
||||
self.received_bytes = 0
|
||||
|
||||
def on_ctcp(self, connection, event):
|
||||
args = event.arguments()[1].split()
|
||||
if args[0] != "SEND":
|
||||
return
|
||||
self.filename = os.path.basename(args[1])
|
||||
if os.path.exists(self.filename):
|
||||
print "A file named", self.filename,
|
||||
print "already exists. Refusing to save it."
|
||||
self.connection.quit()
|
||||
self.file = open(self.filename, "w")
|
||||
peeraddress = irclib.ip_numstr_to_quad(args[2])
|
||||
peerport = int(args[3])
|
||||
self.dcc = self.dcc_connect(peeraddress, peerport, "raw")
|
||||
|
||||
def on_dccmsg(self, connection, event):
|
||||
data = event.arguments()[0]
|
||||
self.file.write(data)
|
||||
self.received_bytes = self.received_bytes + len(data)
|
||||
self.dcc.privmsg(struct.pack("!I", self.received_bytes))
|
||||
|
||||
def on_dcc_disconnect(self, connection, event):
|
||||
self.file.close()
|
||||
print "Received file %s (%d bytes)." % (self.filename,
|
||||
self.received_bytes)
|
||||
self.connection.quit()
|
||||
|
||||
def on_disconnect(self, connection, event):
|
||||
sys.exit(0)
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print "Usage: dccreceive <server[:port]> <nickname>"
|
||||
print "\nReceives one file via DCC and then exits. The file is stored in the"
|
||||
print "current directory."
|
||||
sys.exit(1)
|
||||
|
||||
s = sys.argv[1].split(":", 1)
|
||||
server = s[0]
|
||||
if len(s) == 2:
|
||||
try:
|
||||
port = int(s[1])
|
||||
except ValueError:
|
||||
print "Error: Erroneous port."
|
||||
sys.exit(1)
|
||||
else:
|
||||
port = 6667
|
||||
nickname = sys.argv[2]
|
||||
|
||||
c = DCCReceive()
|
||||
try:
|
||||
c.connect(server, port, nickname)
|
||||
except irclib.ServerConnectionError, x:
|
||||
print x
|
||||
sys.exit(1)
|
||||
c.start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
91
lib/irclib/dccsend
Executable file
91
lib/irclib/dccsend
Executable file
@ -0,0 +1,91 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# Example program using irclib.py.
|
||||
#
|
||||
# This program is free without restrictions; do anything you like with
|
||||
# it.
|
||||
#
|
||||
# Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
import irclib
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
class DCCSend(irclib.SimpleIRCClient):
|
||||
def __init__(self, receiver, filename):
|
||||
irclib.SimpleIRCClient.__init__(self)
|
||||
self.receiver = receiver
|
||||
self.filename = filename
|
||||
self.filesize = os.path.getsize(self.filename)
|
||||
self.file = open(filename)
|
||||
self.sent_bytes = 0
|
||||
|
||||
def on_welcome(self, connection, event):
|
||||
self.dcc = self.dcc_listen("raw")
|
||||
self.connection.ctcp("DCC", self.receiver, "SEND %s %s %d %d" % (
|
||||
os.path.basename(self.filename),
|
||||
irclib.ip_quad_to_numstr(self.dcc.localaddress),
|
||||
self.dcc.localport,
|
||||
self.filesize))
|
||||
|
||||
def on_dcc_connect(self, connection, event):
|
||||
if self.filesize == 0:
|
||||
self.dcc.disconnect()
|
||||
return
|
||||
self.send_chunk()
|
||||
|
||||
def on_dcc_disconnect(self, connection, event):
|
||||
print "Sent file %s (%d bytes)." % (self.filename, self.filesize)
|
||||
self.connection.quit()
|
||||
|
||||
def on_dccmsg(self, connection, event):
|
||||
acked = struct.unpack("!I", event.arguments()[0])[0]
|
||||
if acked == self.filesize:
|
||||
self.dcc.disconnect()
|
||||
self.connection.quit()
|
||||
elif acked == self.sent_bytes:
|
||||
self.send_chunk()
|
||||
|
||||
def on_disconnect(self, connection, event):
|
||||
sys.exit(0)
|
||||
|
||||
def on_nosuchnick(self, connection, event):
|
||||
print "No such nickname:", event.arguments()[0]
|
||||
self.connection.quit()
|
||||
|
||||
def send_chunk(self):
|
||||
data = self.file.read(1024)
|
||||
self.dcc.privmsg(data)
|
||||
self.sent_bytes = self.sent_bytes + len(data)
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 5:
|
||||
print "Usage: dccsend <server[:port]> <nickname> <receiver nickname> <filename>"
|
||||
print "\nSends <filename> to <receiver nickname> via DCC and then exits."
|
||||
sys.exit(1)
|
||||
|
||||
s = sys.argv[1].split(":", 1)
|
||||
server = s[0]
|
||||
if len(s) == 2:
|
||||
try:
|
||||
port = int(s[1])
|
||||
except ValueError:
|
||||
print "Error: Erroneous port."
|
||||
sys.exit(1)
|
||||
else:
|
||||
port = 6667
|
||||
nickname = sys.argv[2]
|
||||
receiver = sys.argv[3]
|
||||
filename = sys.argv[4]
|
||||
|
||||
c = DCCSend(receiver, filename)
|
||||
try:
|
||||
c.connect(server, port, nickname)
|
||||
except irclib.ServerConnectionError, x:
|
||||
print x
|
||||
sys.exit(1)
|
||||
c.start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
438
lib/irclib/ircbot.py
Executable file
438
lib/irclib/ircbot.py
Executable file
@ -0,0 +1,438 @@
|
||||
# Copyright (C) 1999--2002 Joel Rosdahl
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Joel Rosdahl <joel@rosdahl.net>
|
||||
#
|
||||
# $Id: ircbot.py,v 1.21 2005/12/23 18:44:43 keltus Exp $
|
||||
|
||||
"""ircbot -- Simple IRC bot library.
|
||||
|
||||
This module contains a single-server IRC bot class that can be used to
|
||||
write simpler bots.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from UserDict import UserDict
|
||||
|
||||
from irclib import SimpleIRCClient
|
||||
from irclib import nm_to_n, irc_lower, all_events
|
||||
from irclib import parse_channel_modes, is_channel
|
||||
from irclib import ServerConnectionError
|
||||
|
||||
class SingleServerIRCBot(SimpleIRCClient):
|
||||
"""A single-server IRC bot class.
|
||||
|
||||
The bot tries to reconnect if it is disconnected.
|
||||
|
||||
The bot keeps track of the channels it has joined, the other
|
||||
clients that are present in the channels and which of those that
|
||||
have operator or voice modes. The "database" is kept in the
|
||||
self.channels attribute, which is an IRCDict of Channels.
|
||||
"""
|
||||
def __init__(self, server_list, nickname, realname, reconnection_interval=60):
|
||||
"""Constructor for SingleServerIRCBot objects.
|
||||
|
||||
Arguments:
|
||||
|
||||
server_list -- A list of tuples (server, port) that
|
||||
defines which servers the bot should try to
|
||||
connect to.
|
||||
|
||||
nickname -- The bot's nickname.
|
||||
|
||||
realname -- The bot's realname.
|
||||
|
||||
reconnection_interval -- How long the bot should wait
|
||||
before trying to reconnect.
|
||||
|
||||
dcc_connections -- A list of initiated/accepted DCC
|
||||
connections.
|
||||
"""
|
||||
|
||||
SimpleIRCClient.__init__(self)
|
||||
self.channels = IRCDict()
|
||||
self.server_list = server_list
|
||||
if not reconnection_interval or reconnection_interval < 0:
|
||||
reconnection_interval = 2**31
|
||||
self.reconnection_interval = reconnection_interval
|
||||
|
||||
self._nickname = nickname
|
||||
self._realname = realname
|
||||
for i in ["disconnect", "join", "kick", "mode",
|
||||
"namreply", "nick", "part", "quit"]:
|
||||
self.connection.add_global_handler(i,
|
||||
getattr(self, "_on_" + i),
|
||||
-10)
|
||||
def _connected_checker(self):
|
||||
"""[Internal]"""
|
||||
if not self.connection.is_connected():
|
||||
self.connection.execute_delayed(self.reconnection_interval,
|
||||
self._connected_checker)
|
||||
self.jump_server()
|
||||
|
||||
def _connect(self):
|
||||
"""[Internal]"""
|
||||
password = None
|
||||
if len(self.server_list[0]) > 2:
|
||||
password = self.server_list[0][2]
|
||||
try:
|
||||
self.connect(self.server_list[0][0],
|
||||
self.server_list[0][1],
|
||||
self._nickname,
|
||||
password,
|
||||
ircname=self._realname)
|
||||
except ServerConnectionError:
|
||||
pass
|
||||
|
||||
def _on_disconnect(self, c, e):
|
||||
"""[Internal]"""
|
||||
self.channels = IRCDict()
|
||||
self.connection.execute_delayed(self.reconnection_interval,
|
||||
self._connected_checker)
|
||||
|
||||
def _on_join(self, c, e):
|
||||
"""[Internal]"""
|
||||
ch = e.target()
|
||||
nick = nm_to_n(e.source())
|
||||
if nick == c.get_nickname():
|
||||
self.channels[ch] = Channel()
|
||||
self.channels[ch].add_user(nick)
|
||||
|
||||
def _on_kick(self, c, e):
|
||||
"""[Internal]"""
|
||||
nick = e.arguments()[0]
|
||||
channel = e.target()
|
||||
|
||||
if nick == c.get_nickname():
|
||||
del self.channels[channel]
|
||||
else:
|
||||
self.channels[channel].remove_user(nick)
|
||||
|
||||
def _on_mode(self, c, e):
|
||||
"""[Internal]"""
|
||||
modes = parse_channel_modes(" ".join(e.arguments()))
|
||||
t = e.target()
|
||||
if is_channel(t):
|
||||
ch = self.channels[t]
|
||||
for mode in modes:
|
||||
if mode[0] == "+":
|
||||
f = ch.set_mode
|
||||
else:
|
||||
f = ch.clear_mode
|
||||
f(mode[1], mode[2])
|
||||
else:
|
||||
# Mode on self... XXX
|
||||
pass
|
||||
|
||||
def _on_namreply(self, c, e):
|
||||
"""[Internal]"""
|
||||
|
||||
# e.arguments()[0] == "@" for secret channels,
|
||||
# "*" for private channels,
|
||||
# "=" for others (public channels)
|
||||
# e.arguments()[1] == channel
|
||||
# e.arguments()[2] == nick list
|
||||
|
||||
ch = e.arguments()[1]
|
||||
for nick in e.arguments()[2].split():
|
||||
if nick[0] == "@":
|
||||
nick = nick[1:]
|
||||
self.channels[ch].set_mode("o", nick)
|
||||
elif nick[0] == "+":
|
||||
nick = nick[1:]
|
||||
self.channels[ch].set_mode("v", nick)
|
||||
self.channels[ch].add_user(nick)
|
||||
|
||||
def _on_nick(self, c, e):
|
||||
"""[Internal]"""
|
||||
before = nm_to_n(e.source())
|
||||
after = e.target()
|
||||
for ch in self.channels.values():
|
||||
if ch.has_user(before):
|
||||
ch.change_nick(before, after)
|
||||
|
||||
def _on_part(self, c, e):
|
||||
"""[Internal]"""
|
||||
nick = nm_to_n(e.source())
|
||||
channel = e.target()
|
||||
|
||||
if nick == c.get_nickname():
|
||||
del self.channels[channel]
|
||||
else:
|
||||
self.channels[channel].remove_user(nick)
|
||||
|
||||
def _on_quit(self, c, e):
|
||||
"""[Internal]"""
|
||||
nick = nm_to_n(e.source())
|
||||
for ch in self.channels.values():
|
||||
if ch.has_user(nick):
|
||||
ch.remove_user(nick)
|
||||
|
||||
def die(self, msg="Bye, cruel world!"):
|
||||
"""Let the bot die.
|
||||
|
||||
Arguments:
|
||||
|
||||
msg -- Quit message.
|
||||
"""
|
||||
|
||||
self.connection.disconnect(msg)
|
||||
sys.exit(0)
|
||||
|
||||
def disconnect(self, msg="I'll be back!"):
|
||||
"""Disconnect the bot.
|
||||
|
||||
The bot will try to reconnect after a while.
|
||||
|
||||
Arguments:
|
||||
|
||||
msg -- Quit message.
|
||||
"""
|
||||
self.connection.disconnect(msg)
|
||||
|
||||
def get_version(self):
|
||||
"""Returns the bot version.
|
||||
|
||||
Used when answering a CTCP VERSION request.
|
||||
"""
|
||||
return "ircbot.py by Joel Rosdahl <joel@rosdahl.net>"
|
||||
|
||||
def jump_server(self, msg="Changing servers"):
|
||||
"""Connect to a new server, possibly disconnecting from the current.
|
||||
|
||||
The bot will skip to next server in the server_list each time
|
||||
jump_server is called.
|
||||
"""
|
||||
if self.connection.is_connected():
|
||||
self.connection.disconnect(msg)
|
||||
|
||||
self.server_list.append(self.server_list.pop(0))
|
||||
self._connect()
|
||||
|
||||
def on_ctcp(self, c, e):
|
||||
"""Default handler for ctcp events.
|
||||
|
||||
Replies to VERSION and PING requests and relays DCC requests
|
||||
to the on_dccchat method.
|
||||
"""
|
||||
if e.arguments()[0] == "VERSION":
|
||||
c.ctcp_reply(nm_to_n(e.source()),
|
||||
"VERSION " + self.get_version())
|
||||
elif e.arguments()[0] == "PING":
|
||||
if len(e.arguments()) > 1:
|
||||
c.ctcp_reply(nm_to_n(e.source()),
|
||||
"PING " + e.arguments()[1])
|
||||
elif e.arguments()[0] == "DCC" and e.arguments()[1].split(" ", 1)[0] == "CHAT":
|
||||
self.on_dccchat(c, e)
|
||||
|
||||
def on_dccchat(self, c, e):
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
"""Start the bot."""
|
||||
self._connect()
|
||||
SimpleIRCClient.start(self)
|
||||
|
||||
|
||||
class IRCDict:
|
||||
"""A dictionary suitable for storing IRC-related things.
|
||||
|
||||
Dictionary keys a and b are considered equal if and only if
|
||||
irc_lower(a) == irc_lower(b)
|
||||
|
||||
Otherwise, it should behave exactly as a normal dictionary.
|
||||
"""
|
||||
|
||||
def __init__(self, dict=None):
|
||||
self.data = {}
|
||||
self.canon_keys = {} # Canonical keys
|
||||
if dict is not None:
|
||||
self.update(dict)
|
||||
def __repr__(self):
|
||||
return repr(self.data)
|
||||
def __cmp__(self, dict):
|
||||
if isinstance(dict, IRCDict):
|
||||
return cmp(self.data, dict.data)
|
||||
else:
|
||||
return cmp(self.data, dict)
|
||||
def __len__(self):
|
||||
return len(self.data)
|
||||
def __getitem__(self, key):
|
||||
return self.data[self.canon_keys[irc_lower(key)]]
|
||||
def __setitem__(self, key, item):
|
||||
if key in self:
|
||||
del self[key]
|
||||
self.data[key] = item
|
||||
self.canon_keys[irc_lower(key)] = key
|
||||
def __delitem__(self, key):
|
||||
ck = irc_lower(key)
|
||||
del self.data[self.canon_keys[ck]]
|
||||
del self.canon_keys[ck]
|
||||
def __iter__(self):
|
||||
return iter(self.data)
|
||||
def __contains__(self, key):
|
||||
return self.has_key(key)
|
||||
def clear(self):
|
||||
self.data.clear()
|
||||
self.canon_keys.clear()
|
||||
def copy(self):
|
||||
if self.__class__ is UserDict:
|
||||
return UserDict(self.data)
|
||||
import copy
|
||||
return copy.copy(self)
|
||||
def keys(self):
|
||||
return self.data.keys()
|
||||
def items(self):
|
||||
return self.data.items()
|
||||
def values(self):
|
||||
return self.data.values()
|
||||
def has_key(self, key):
|
||||
return irc_lower(key) in self.canon_keys
|
||||
def update(self, dict):
|
||||
for k, v in dict.items():
|
||||
self.data[k] = v
|
||||
def get(self, key, failobj=None):
|
||||
return self.data.get(key, failobj)
|
||||
|
||||
|
||||
class Channel:
|
||||
"""A class for keeping information about an IRC channel.
|
||||
|
||||
This class can be improved a lot.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.userdict = IRCDict()
|
||||
self.operdict = IRCDict()
|
||||
self.voiceddict = IRCDict()
|
||||
self.modes = {}
|
||||
|
||||
def users(self):
|
||||
"""Returns an unsorted list of the channel's users."""
|
||||
return self.userdict.keys()
|
||||
|
||||
def opers(self):
|
||||
"""Returns an unsorted list of the channel's operators."""
|
||||
return self.operdict.keys()
|
||||
|
||||
def voiced(self):
|
||||
"""Returns an unsorted list of the persons that have voice
|
||||
mode set in the channel."""
|
||||
return self.voiceddict.keys()
|
||||
|
||||
def has_user(self, nick):
|
||||
"""Check whether the channel has a user."""
|
||||
return nick in self.userdict
|
||||
|
||||
def is_oper(self, nick):
|
||||
"""Check whether a user has operator status in the channel."""
|
||||
return nick in self.operdict
|
||||
|
||||
def is_voiced(self, nick):
|
||||
"""Check whether a user has voice mode set in the channel."""
|
||||
return nick in self.voiceddict
|
||||
|
||||
def add_user(self, nick):
|
||||
self.userdict[nick] = 1
|
||||
|
||||
def remove_user(self, nick):
|
||||
for d in self.userdict, self.operdict, self.voiceddict:
|
||||
if nick in d:
|
||||
del d[nick]
|
||||
|
||||
def change_nick(self, before, after):
|
||||
self.userdict[after] = 1
|
||||
del self.userdict[before]
|
||||
if before in self.operdict:
|
||||
self.operdict[after] = 1
|
||||
del self.operdict[before]
|
||||
if before in self.voiceddict:
|
||||
self.voiceddict[after] = 1
|
||||
del self.voiceddict[before]
|
||||
|
||||
def set_mode(self, mode, value=None):
|
||||
"""Set mode on the channel.
|
||||
|
||||
Arguments:
|
||||
|
||||
mode -- The mode (a single-character string).
|
||||
|
||||
value -- Value
|
||||
"""
|
||||
if mode == "o":
|
||||
self.operdict[value] = 1
|
||||
elif mode == "v":
|
||||
self.voiceddict[value] = 1
|
||||
else:
|
||||
self.modes[mode] = value
|
||||
|
||||
def clear_mode(self, mode, value=None):
|
||||
"""Clear mode on the channel.
|
||||
|
||||
Arguments:
|
||||
|
||||
mode -- The mode (a single-character string).
|
||||
|
||||
value -- Value
|
||||
"""
|
||||
try:
|
||||
if mode == "o":
|
||||
del self.operdict[value]
|
||||
elif mode == "v":
|
||||
del self.voiceddict[value]
|
||||
else:
|
||||
del self.modes[mode]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def has_mode(self, mode):
|
||||
return mode in self.modes
|
||||
|
||||
def is_moderated(self):
|
||||
return self.has_mode("m")
|
||||
|
||||
def is_secret(self):
|
||||
return self.has_mode("s")
|
||||
|
||||
def is_protected(self):
|
||||
return self.has_mode("p")
|
||||
|
||||
def has_topic_lock(self):
|
||||
return self.has_mode("t")
|
||||
|
||||
def is_invite_only(self):
|
||||
return self.has_mode("i")
|
||||
|
||||
def has_allow_external_messages(self):
|
||||
return self.has_mode("n")
|
||||
|
||||
def has_limit(self):
|
||||
return self.has_mode("l")
|
||||
|
||||
def limit(self):
|
||||
if self.has_limit():
|
||||
return self.modes[l]
|
||||
else:
|
||||
return None
|
||||
|
||||
def has_key(self):
|
||||
return self.has_mode("k")
|
||||
|
||||
def key(self):
|
||||
if self.has_key():
|
||||
return self.modes["k"]
|
||||
else:
|
||||
return None
|
64
lib/irclib/irccat
Executable file
64
lib/irclib/irccat
Executable file
@ -0,0 +1,64 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# Example program using irclib.py.
|
||||
#
|
||||
# This program is free without restrictions; do anything you like with
|
||||
# it.
|
||||
#
|
||||
# Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
import irclib
|
||||
import sys
|
||||
|
||||
def on_connect(connection, event):
|
||||
if irclib.is_channel(target):
|
||||
connection.join(target)
|
||||
else:
|
||||
while 1:
|
||||
line = sys.stdin.readline()
|
||||
if not line:
|
||||
break
|
||||
connection.privmsg(target, line)
|
||||
connection.quit("Using irclib.py")
|
||||
|
||||
def on_join(connection, event):
|
||||
while 1:
|
||||
line = sys.stdin.readline()
|
||||
if not line:
|
||||
break
|
||||
connection.privmsg(target, line)
|
||||
connection.quit("Using irclib.py")
|
||||
|
||||
if len(sys.argv) != 4:
|
||||
print "Usage: irccat <server[:port]> <nickname> <target>"
|
||||
print "\ntarget is a nickname or a channel."
|
||||
sys.exit(1)
|
||||
|
||||
def on_disconnect(connection, event):
|
||||
sys.exit(0)
|
||||
|
||||
s = sys.argv[1].split(":", 1)
|
||||
server = s[0]
|
||||
if len(s) == 2:
|
||||
try:
|
||||
port = int(s[1])
|
||||
except ValueError:
|
||||
print "Error: Erroneous port."
|
||||
sys.exit(1)
|
||||
else:
|
||||
port = 6667
|
||||
nickname = sys.argv[2]
|
||||
target = sys.argv[3]
|
||||
|
||||
irc = irclib.IRC()
|
||||
try:
|
||||
c = irc.server().connect(server, port, nickname)
|
||||
except irclib.ServerConnectionError, x:
|
||||
print x
|
||||
sys.exit(1)
|
||||
|
||||
c.add_global_handler("welcome", on_connect)
|
||||
c.add_global_handler("join", on_join)
|
||||
c.add_global_handler("disconnect", on_disconnect)
|
||||
|
||||
irc.process_forever()
|
66
lib/irclib/irccat2
Executable file
66
lib/irclib/irccat2
Executable file
@ -0,0 +1,66 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# Example program using irclib.py.
|
||||
#
|
||||
# This program is free without restrictions; do anything you like with
|
||||
# it.
|
||||
#
|
||||
# Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
import irclib
|
||||
import sys
|
||||
|
||||
class IRCCat(irclib.SimpleIRCClient):
|
||||
def __init__(self, target):
|
||||
irclib.SimpleIRCClient.__init__(self)
|
||||
self.target = target
|
||||
|
||||
def on_welcome(self, connection, event):
|
||||
if irclib.is_channel(self.target):
|
||||
connection.join(self.target)
|
||||
else:
|
||||
self.send_it()
|
||||
|
||||
def on_join(self, connection, event):
|
||||
self.send_it()
|
||||
|
||||
def on_disconnect(self, connection, event):
|
||||
sys.exit(0)
|
||||
|
||||
def send_it(self):
|
||||
while 1:
|
||||
line = sys.stdin.readline()
|
||||
if not line:
|
||||
break
|
||||
self.connection.privmsg(self.target, line)
|
||||
self.connection.quit("Using irclib.py")
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 4:
|
||||
print "Usage: irccat2 <server[:port]> <nickname> <target>"
|
||||
print "\ntarget is a nickname or a channel."
|
||||
sys.exit(1)
|
||||
|
||||
s = sys.argv[1].split(":", 1)
|
||||
server = s[0]
|
||||
if len(s) == 2:
|
||||
try:
|
||||
port = int(s[1])
|
||||
except ValueError:
|
||||
print "Error: Erroneous port."
|
||||
sys.exit(1)
|
||||
else:
|
||||
port = 6667
|
||||
nickname = sys.argv[2]
|
||||
target = sys.argv[3]
|
||||
|
||||
c = IRCCat(target)
|
||||
try:
|
||||
c.connect(server, port, nickname)
|
||||
except irclib.ServerConnectionError, x:
|
||||
print x
|
||||
sys.exit(1)
|
||||
c.start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
1550
lib/irclib/irclib.py
Executable file
1550
lib/irclib/irclib.py
Executable file
File diff suppressed because it is too large
Load Diff
68
lib/irclib/python-irclib.spec
Executable file
68
lib/irclib/python-irclib.spec
Executable file
@ -0,0 +1,68 @@
|
||||
Summary: A set of Python modules for IRC support.
|
||||
Name: python-irclib
|
||||
Version: 0.4.6
|
||||
Release: 1
|
||||
Group: Development/Libraries
|
||||
License: LGPL
|
||||
URL: http://python-irclib.sourceforge.net
|
||||
Source: %{name}-%{version}.tar.gz
|
||||
BuildRoot: %{_tmppath}/%{name}-root
|
||||
Requires: python
|
||||
BuildPrereq: python
|
||||
BuildArch: noarch
|
||||
|
||||
%description
|
||||
This library is intended to encapsulate the IRC protocol at a quite
|
||||
low level. It provides an event-driven IRC client framework. It has
|
||||
a fairly thorough support for the basic IRC protocol, CTCP and DCC
|
||||
connections.
|
||||
|
||||
%prep
|
||||
%setup -q
|
||||
chmod 644 *
|
||||
|
||||
%build
|
||||
python -c "import py_compile; py_compile.compile('irclib.py')"
|
||||
python -c "import py_compile; py_compile.compile('ircbot.py')"
|
||||
|
||||
%install
|
||||
[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
|
||||
%{__mkdir_p} $RPM_BUILD_ROOT/usr/lib/python1.5/site-packages
|
||||
%{__install} -m 644 irclib.py* $RPM_BUILD_ROOT/usr/lib/python1.5/site-packages
|
||||
%{__install} -m 644 ircbot.py* $RPM_BUILD_ROOT/usr/lib/python1.5/site-packages
|
||||
|
||||
%clean
|
||||
[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
|
||||
|
||||
%files
|
||||
%defattr(-,root,root)
|
||||
%doc README ChangeLog COPYING irccat irccat2 servermap testbot.py dccsend dccreceive
|
||||
/usr/lib/python*/site-packages/*
|
||||
|
||||
%changelog
|
||||
* Sat Dec 24 2005 Keltus <keltus@users.sourceforge.net> 0.4.6-1
|
||||
- upgraded to 0.4.6
|
||||
|
||||
* Wed May 18 2005 Keltus <keltus@users.sourceforge.net> 0.4.5-1
|
||||
- upgraded to 0.4.5
|
||||
|
||||
* Wed Feb 23 2005 Keltus <keltus@users.sourceforge.net> 0.4.4-1
|
||||
- upgraded to 0.4.4
|
||||
|
||||
* Sun Jan 19 2005 Joel Rosdahl <joel@rosdahl.net> 0.4.3-1
|
||||
- upgraded to 0.4.3
|
||||
|
||||
* Fri Jul 9 2004 Joel Rosdahl <joel@rosdahl.net> 0.4.2-1
|
||||
- upgraded to 0.4.2
|
||||
|
||||
* Thu Oct 30 2003 Joel Rosdahl <joel@rosdahl.net> 0.4.1-1
|
||||
- upgraded to 0.4.1
|
||||
|
||||
* Mon Sep 1 2002 Gary Benson <gary@inauspicious.org> 0.4.0-1
|
||||
- upgraded to 0.4.0
|
||||
|
||||
* Wed Feb 20 2002 Gary Benson <gary@inauspicious.org> 0.3.4-1
|
||||
- upgraded to 0.3.4
|
||||
|
||||
* Wed Feb 20 2002 Gary Benson <gary@inauspicious.org> 0.3.3-1
|
||||
- initial revision
|
164
lib/irclib/servermap
Executable file
164
lib/irclib/servermap
Executable file
@ -0,0 +1,164 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# Example program using irclib.py.
|
||||
#
|
||||
# Copyright (C) 1999-2002 Joel Rosdahl
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Joel Rosdahl <joel@rosdahl.net>
|
||||
#
|
||||
# servermap connects to an IRC server and finds out what other IRC
|
||||
# servers there are in the net and prints a tree-like map of their
|
||||
# interconnections.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# % ./servermap irc.dal.net somenickname
|
||||
# Connecting to server...
|
||||
# Getting links...
|
||||
#
|
||||
# 26 servers (18 leaves and 8 hubs)
|
||||
#
|
||||
# splitrock.tx.us.dal.net
|
||||
# `-vader.ny.us.dal.net
|
||||
# |-twisted.ma.us.dal.net
|
||||
# |-sodre.nj.us.dal.net
|
||||
# |-glass.oh.us.dal.net
|
||||
# |-distant.ny.us.dal.net
|
||||
# | |-algo.se.eu.dal.net
|
||||
# | | |-borg.se.eu.dal.net
|
||||
# | | | `-ced.se.eu.dal.net
|
||||
# | | |-viking.no.eu.dal.net
|
||||
# | | |-inco.fr.eu.dal.net
|
||||
# | | |-paranoia.se.eu.dal.net
|
||||
# | | |-gaston.se.eu.dal.net
|
||||
# | | | `-powertech.no.eu.dal.net
|
||||
# | | `-algo-u.se.eu.dal.net
|
||||
# | |-philly.pa.us.dal.net
|
||||
# | |-liberty.nj.us.dal.net
|
||||
# | `-jade.va.us.dal.net
|
||||
# `-journey.ca.us.dal.net
|
||||
# |-ion.va.us.dal.net
|
||||
# |-dragons.ca.us.dal.net
|
||||
# |-toronto.on.ca.dal.net
|
||||
# | `-netropolis-r.uk.eu.dal.net
|
||||
# | |-traced.de.eu.dal.net
|
||||
# | `-lineone.uk.eu.dal.net
|
||||
# `-omega.ca.us.dal.net
|
||||
|
||||
import irclib
|
||||
import sys
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
print "Usage: servermap <server[:port]> <nickname>"
|
||||
sys.exit(1)
|
||||
|
||||
links = []
|
||||
|
||||
def on_connect(connection, event):
|
||||
sys.stdout.write("\nGetting links...")
|
||||
sys.stdout.flush()
|
||||
connection.links()
|
||||
|
||||
def on_passwdmismatch(connection, event):
|
||||
print "Password required."
|
||||
sys.exit(1)
|
||||
|
||||
def on_links(connection, event):
|
||||
global links
|
||||
|
||||
links.append((event.arguments()[0],
|
||||
event.arguments()[1],
|
||||
event.arguments()[2]))
|
||||
|
||||
def on_endoflinks(connection, event):
|
||||
global links
|
||||
|
||||
print "\n"
|
||||
|
||||
m = {}
|
||||
for (to_node, from_node, desc) in links:
|
||||
if from_node != to_node:
|
||||
m[from_node] = m.get(from_node, []) + [to_node]
|
||||
|
||||
if connection.get_server_name() in m:
|
||||
if len(m[connection.get_server_name()]) == 1:
|
||||
hubs = len(m) - 1
|
||||
else:
|
||||
hubs = len(m)
|
||||
else:
|
||||
hubs = 0
|
||||
|
||||
print "%d servers (%d leaves and %d hubs)\n" % (len(links), len(links)-hubs, hubs)
|
||||
|
||||
print_tree(0, [], connection.get_server_name(), m)
|
||||
connection.quit("Using irclib.py")
|
||||
|
||||
def on_disconnect(connection, event):
|
||||
sys.exit(0)
|
||||
|
||||
def indent_string(level, active_levels, last):
|
||||
if level == 0:
|
||||
return ""
|
||||
s = ""
|
||||
for i in range(level-1):
|
||||
if i in active_levels:
|
||||
s = s + "| "
|
||||
else:
|
||||
s = s + " "
|
||||
if last:
|
||||
s = s + "`-"
|
||||
else:
|
||||
s = s + "|-"
|
||||
return s
|
||||
|
||||
def print_tree(level, active_levels, root, map, last=0):
|
||||
sys.stdout.write(indent_string(level, active_levels, last)
|
||||
+ root + "\n")
|
||||
if root in map:
|
||||
list = map[root]
|
||||
for r in list[:-1]:
|
||||
print_tree(level+1, active_levels[:]+[level], r, map)
|
||||
print_tree(level+1, active_levels[:], list[-1], map, 1)
|
||||
|
||||
s = sys.argv[1].split(":", 1)
|
||||
server = s[0]
|
||||
if len(s) == 2:
|
||||
try:
|
||||
port = int(s[1])
|
||||
except ValueError:
|
||||
print "Error: Erroneous port."
|
||||
sys.exit(1)
|
||||
else:
|
||||
port = 6667
|
||||
nickname = sys.argv[2]
|
||||
|
||||
irc = irclib.IRC()
|
||||
sys.stdout.write("Connecting to server...")
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
c = irc.server().connect(server, port, nickname)
|
||||
except irclib.ServerConnectionError, x:
|
||||
print x
|
||||
sys.exit(1)
|
||||
|
||||
c.add_global_handler("welcome", on_connect)
|
||||
c.add_global_handler("passwdmismatch", on_passwdmismatch)
|
||||
c.add_global_handler("links", on_links)
|
||||
c.add_global_handler("endoflinks", on_endoflinks)
|
||||
c.add_global_handler("disconnect", on_disconnect)
|
||||
|
||||
irc.process_forever()
|
9
lib/irclib/setup.py
Executable file
9
lib/irclib/setup.py
Executable file
@ -0,0 +1,9 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
from distutils.core import setup
|
||||
setup(name="python-irclib",
|
||||
version="0.4.6",
|
||||
py_modules=["irclib", "ircbot"],
|
||||
author="Joel Rosdahl",
|
||||
author_email="joel@rosdahl.net",
|
||||
url="http://python-irclib.sourceforge.net")
|
118
lib/irclib/testbot.py
Executable file
118
lib/irclib/testbot.py
Executable file
@ -0,0 +1,118 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# Example program using ircbot.py.
|
||||
#
|
||||
# Joel Rosdahl <joel@rosdahl.net>
|
||||
|
||||
"""A simple example bot.
|
||||
|
||||
This is an example bot that uses the SingleServerIRCBot class from
|
||||
ircbot.py. The bot enters a channel and listens for commands in
|
||||
private messages and channel traffic. Commands in channel messages
|
||||
are given by prefixing the text by the bot name followed by a colon.
|
||||
It also responds to DCC CHAT invitations and echos data sent in such
|
||||
sessions.
|
||||
|
||||
The known commands are:
|
||||
|
||||
stats -- Prints some channel information.
|
||||
|
||||
disconnect -- Disconnect the bot. The bot will try to reconnect
|
||||
after 60 seconds.
|
||||
|
||||
die -- Let the bot cease to exist.
|
||||
|
||||
dcc -- Let the bot invite you to a DCC CHAT connection.
|
||||
"""
|
||||
|
||||
from ircbot import SingleServerIRCBot
|
||||
from irclib import nm_to_n, nm_to_h, irc_lower, ip_numstr_to_quad, ip_quad_to_numstr
|
||||
|
||||
class TestBot(SingleServerIRCBot):
|
||||
def __init__(self, channel, nickname, server, port=6667):
|
||||
SingleServerIRCBot.__init__(self, [(server, port)], nickname, nickname)
|
||||
self.channel = channel
|
||||
|
||||
def on_nicknameinuse(self, c, e):
|
||||
c.nick(c.get_nickname() + "_")
|
||||
|
||||
def on_welcome(self, c, e):
|
||||
c.join(self.channel)
|
||||
|
||||
def on_privmsg(self, c, e):
|
||||
self.do_command(e, e.arguments()[0])
|
||||
|
||||
def on_pubmsg(self, c, e):
|
||||
a = e.arguments()[0].split(":", 1)
|
||||
if len(a) > 1 and irc_lower(a[0]) == irc_lower(self.connection.get_nickname()):
|
||||
self.do_command(e, a[1].strip())
|
||||
return
|
||||
|
||||
def on_dccmsg(self, c, e):
|
||||
c.privmsg("You said: " + e.arguments()[0])
|
||||
|
||||
def on_dccchat(self, c, e):
|
||||
if len(e.arguments()) != 2:
|
||||
return
|
||||
args = e.arguments()[1].split()
|
||||
if len(args) == 4:
|
||||
try:
|
||||
address = ip_numstr_to_quad(args[2])
|
||||
port = int(args[3])
|
||||
except ValueError:
|
||||
return
|
||||
self.dcc_connect(address, port)
|
||||
|
||||
def do_command(self, e, cmd):
|
||||
nick = nm_to_n(e.source())
|
||||
c = self.connection
|
||||
|
||||
if cmd == "disconnect":
|
||||
self.disconnect()
|
||||
elif cmd == "die":
|
||||
self.die()
|
||||
elif cmd == "stats":
|
||||
for chname, chobj in self.channels.items():
|
||||
c.notice(nick, "--- Channel statistics ---")
|
||||
c.notice(nick, "Channel: " + chname)
|
||||
users = chobj.users()
|
||||
users.sort()
|
||||
c.notice(nick, "Users: " + ", ".join(users))
|
||||
opers = chobj.opers()
|
||||
opers.sort()
|
||||
c.notice(nick, "Opers: " + ", ".join(opers))
|
||||
voiced = chobj.voiced()
|
||||
voiced.sort()
|
||||
c.notice(nick, "Voiced: " + ", ".join(voiced))
|
||||
elif cmd == "dcc":
|
||||
dcc = self.dcc_listen()
|
||||
c.ctcp("DCC", nick, "CHAT chat %s %d" % (
|
||||
ip_quad_to_numstr(dcc.localaddress),
|
||||
dcc.localport))
|
||||
else:
|
||||
c.notice(nick, "Not understood: " + cmd)
|
||||
|
||||
def main():
|
||||
import sys
|
||||
if len(sys.argv) != 4:
|
||||
print "Usage: testbot <server[:port]> <channel> <nickname>"
|
||||
sys.exit(1)
|
||||
|
||||
s = sys.argv[1].split(":", 1)
|
||||
server = s[0]
|
||||
if len(s) == 2:
|
||||
try:
|
||||
port = int(s[1])
|
||||
except ValueError:
|
||||
print "Error: Erroneous port."
|
||||
sys.exit(1)
|
||||
else:
|
||||
port = 6667
|
||||
channel = sys.argv[2]
|
||||
nickname = sys.argv[3]
|
||||
|
||||
bot = TestBot(channel, nickname, server, port)
|
||||
bot.start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,52 +0,0 @@
|
||||
target_time = {hour=hour, min=minute, sec=second}
|
||||
|
||||
function get_total_day_seconds (time_table)
|
||||
return (((time_table.hour * 60) + (time_table.min)) * 60) + time_table.sec
|
||||
end
|
||||
|
||||
SECONDS_PER_DAY = get_total_day_seconds({hour=24, min=0, sec=0})
|
||||
|
||||
function get_sleep_duration_sec ()
|
||||
current_time = os.time()
|
||||
current_time_table = os.date("*t", current_time)
|
||||
current_day_seconds = get_total_day_seconds(current_time_table)
|
||||
target_day_seconds = get_total_day_seconds(target_time)
|
||||
difference = target_day_seconds - current_day_seconds
|
||||
if difference > 0 then
|
||||
return difference
|
||||
else
|
||||
return SECONDS_PER_DAY + difference
|
||||
end
|
||||
end
|
||||
|
||||
-- Returns full weekday all lowercased (monday, tuesday, etc)
|
||||
function get_weekday ()
|
||||
return string.lower(os.date("%A", os.time()))
|
||||
end
|
||||
|
||||
function sleep (sec)
|
||||
return os.execute("sleep " .. tonumber(sec))
|
||||
end
|
||||
|
||||
function run_dailies (dailies)
|
||||
while true do
|
||||
sleep_duration = get_sleep_duration_sec()
|
||||
print("sleep for " .. sleep_duration)
|
||||
|
||||
if not sleep(sleep_duration) then
|
||||
print("sleep exited abnormally - break")
|
||||
break
|
||||
end
|
||||
|
||||
time_table = os.date("*t", os.time())
|
||||
time_table.weekday = get_weekday()
|
||||
for i, fn in ipairs(dailies) do
|
||||
message = fn(time_table)
|
||||
if message then
|
||||
print("send message " .. message)
|
||||
sender:send({type = "message", channel = channel, message = message})
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
0
modules/__init__.py
Executable file
0
modules/__init__.py
Executable file
28
modules/echobox.py
Executable file
28
modules/echobox.py
Executable file
@ -0,0 +1,28 @@
|
||||
"""An echobox is a device that, upon feeding it an input, stores it and
|
||||
outputs a previously submitted input.
|
||||
Usage: !echobox input"""
|
||||
|
||||
import random
|
||||
import urllib
|
||||
import urllib2
|
||||
|
||||
def echobox(nick,channel,tenquestionmarks,input):
|
||||
"""The echobox command submits some text into an "echobox", or
|
||||
a collection of quotes. After doing so, it outputs a random
|
||||
quote from that colleciton."""
|
||||
|
||||
if input == None or input == "":
|
||||
return tenquestionmarks.html("<b>Echobox error</b>: You need to type something after !echobox, because I'm too lazy to come up with something for you.")
|
||||
|
||||
result = ""
|
||||
if "web_echobox_url" in tenquestionmarks.config()["echobox"]:
|
||||
service_target = "%s?%s" % (tenquestionmarks.config()["echobox"]["web_echobox_url"],urllib.urlencode({"input": input, "nick": nick, "chan": channel}))
|
||||
result = urllib2.urlopen(service_target).read()
|
||||
else:
|
||||
echobox = tenquestionmarks.get_json("echobox.json")
|
||||
if "echoes" not in echobox:
|
||||
echobox["echoes"] = []
|
||||
echobox["echoes"].append(input)
|
||||
tenquestionmarks.put_json("echobox.json",echobox)
|
||||
result = random.choice(echobox["echoes"])
|
||||
return tenquestionmarks.html("<b>Echobox</b>: %s" % (result))
|
7
modules/eightball.py
Executable file
7
modules/eightball.py
Executable file
@ -0,0 +1,7 @@
|
||||
"""Ask the eight-ball a yes/no question, and it will give you a reasonable answer.
|
||||
Usage: !eightball question"""
|
||||
|
||||
import random
|
||||
|
||||
def eightball(nick,channel,tenquestionmarks,question):
|
||||
return tenquestionmarks.html("<b>Eightball:</b> %s" % (random.choice(tenquestionmarks.config()["eightball"]["responses"])))
|
65
modules/help.py
Executable file
65
modules/help.py
Executable file
@ -0,0 +1,65 @@
|
||||
"""Help system for this tenquestionmarks-based bot.
|
||||
Usage:
|
||||
- !tqmhelp -> display an overview of all modules
|
||||
- !tqmhelp module -> display help for a specific module
|
||||
- !tqmhelp module command -> display help for a specific command
|
||||
"""
|
||||
|
||||
import types
|
||||
import inspect
|
||||
|
||||
def tqmhelp(nick,channel,tenquestionmarks,module,command=None):
|
||||
output = []
|
||||
bnick = tenquestionmarks.config()["nick"]
|
||||
if module == "":
|
||||
output.append("%s help index\n" % (bnick))
|
||||
output.append("--------------------------------\n")
|
||||
output.append("In this bot, commands are grouped into modules.\n")
|
||||
output.append("For help with a specific module, type !tqmhelp followed by \n")
|
||||
output.append("the name of the module.\n")
|
||||
output.append("This bot contains the following modules:\n")
|
||||
output.append("--------------------------------\n")
|
||||
for submod in tenquestionmarks.modules():
|
||||
output.append("<b>%s</b>\n" % (submod))
|
||||
submodobj = tenquestionmarks.modules()[submod]
|
||||
if not submodobj.__doc__ == None:
|
||||
output.append(submodobj.__doc__)
|
||||
output.append("\n--------------------------------\n")
|
||||
elif not command == None:
|
||||
try:
|
||||
modobj = tenquestionmarks.modules()[module]
|
||||
except KeyError:
|
||||
return tenquestionmarks.html("<b>Help error</b>: No module named %s" % (module))
|
||||
if not hasattr(modobj,command):
|
||||
return tenquestionmarks.html("<b>Help error</b>: No command %s in module %s" % (command,module))
|
||||
output.append("%s help for command %s.%s\n" % (bnick, module, command))
|
||||
output.append("--------------------------------\n")
|
||||
commandobj = getattr(modobj,command)
|
||||
output.append("<b>Command %s.%s</b>\n" % (module, command))
|
||||
argspec = inspect.getargspec(commandobj)
|
||||
del argspec.args[0]
|
||||
del argspec.args[0]
|
||||
del argspec.args[0]
|
||||
output.append("<b>Usage:</b> !%s.%s %s\n" % (module, command, " ".join(argspec.args)))
|
||||
if not commandobj.__doc__ == None:
|
||||
output.append(commandobj.__doc__)
|
||||
else:
|
||||
try:
|
||||
modobj = tenquestionmarks.modules()[module]
|
||||
except KeyError:
|
||||
return tenquestionmarks.html("<b>Help error</b>: No module named %s" % (module))
|
||||
output.append("%s help for module %s\n" % (bnick, module))
|
||||
output.append("--------------------------------\n")
|
||||
for var in vars(modobj):
|
||||
varvalue = vars(modobj)[var]
|
||||
if not (var.startswith("on_") or var.startswith("_")) and isinstance(varvalue,types.FunctionType):
|
||||
output.append("<b>Command %s.%s</b>\n" % (module, var))
|
||||
argspec = inspect.getargspec(varvalue)
|
||||
del argspec.args[0]
|
||||
del argspec.args[0]
|
||||
del argspec.args[0]
|
||||
output.append("<b>Usage:</b> !%s.%s %s\n" % (module, var, " ".join(argspec.args)))
|
||||
if not varvalue.__doc__ == None:
|
||||
output.append(varvalue.__doc__)
|
||||
output.append("\n--------------------------------\n")
|
||||
return tenquestionmarks.html("".join(output))
|
57
modules/rss.py
Executable file
57
modules/rss.py
Executable file
@ -0,0 +1,57 @@
|
||||
"""RSS feed aggregator. This module contains no user-facing commands."""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import threading
|
||||
import traceback
|
||||
import feedparser
|
||||
|
||||
def on_connected(tenquestionmarks):
|
||||
_rss_loop(tenquestionmarks,tenquestionmarks.config()["rss"]["frequency"])
|
||||
|
||||
def _rss_loop(tenquestionmarks, frequency=900.0):
|
||||
try:
|
||||
old_entries_file = os.path.join(tenquestionmarks.directory(),"old-feed-entries")
|
||||
FILE = open(old_entries_file, "r")
|
||||
filetext = FILE.read()
|
||||
FILE.close()
|
||||
except IOError:
|
||||
filetext = ""
|
||||
open(old_entries_file, "w").close()
|
||||
|
||||
filetext = filetext.decode("UTF-8")
|
||||
for feed in tenquestionmarks.config()["rss"]["feeds"]:
|
||||
feedname = ""
|
||||
if isinstance(tenquestionmarks.config()["rss"]["feeds"],dict):
|
||||
feedname = feed
|
||||
feed = tenquestionmarks.config()["rss"]["feeds"][feed]
|
||||
|
||||
NextFeed = False
|
||||
tenquestionmarks.log("refresh","Refreshing feed %s" % (feed))
|
||||
d = feedparser.parse(feed)
|
||||
for entry in d.entries:
|
||||
title = entry.title
|
||||
try:
|
||||
title = title.encode("ascii")
|
||||
except UnicodeEncodeError, uee:
|
||||
title = tenquestionmarks.degrade_to_ascii(title)
|
||||
except UnicodeDecodeError, ude:
|
||||
title = tenquestionmarks.degrade_to_ascii(title)
|
||||
if title in filetext:
|
||||
tenquestionmarks.log("refresh","Old entry: %s" % (title))
|
||||
NextFeed = True
|
||||
else:
|
||||
FILE = open(old_entries_file, "a")
|
||||
try:
|
||||
FILE.write(title + u"\n")
|
||||
except Exception, e:
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
tenquestionmarks.log("Error","%s %s" % (e,title))
|
||||
FILE.close()
|
||||
tenquestionmarks.queue(tenquestionmarks.config()["rss"]["format"] % ({"title": title, "link": entry.link, "feedname": feedname}))
|
||||
if NextFeed:
|
||||
break
|
||||
|
||||
def refresher(): _rss_loop(tenquestionmarks,frequency)
|
||||
t = threading.Timer(frequency, refresher) # TODO: make this static
|
||||
t.start()
|
33
project.py
Executable file
33
project.py
Executable file
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from monarchpass.beedrill import Project, Action, Listener, do
|
||||
|
||||
class Tenquestionmarks(Project):
|
||||
name = "Tenquestionmarks"
|
||||
author = "Adrian Malacoda"
|
||||
|
||||
@Action(
|
||||
title="Hello World",
|
||||
command="hello"
|
||||
)
|
||||
def hello(self,person="world"):
|
||||
print "Hello {person}!".format(person=person)
|
||||
|
||||
hidave = do(
|
||||
action="hello",
|
||||
command="hidave",
|
||||
kwargs={"person": "Dave"}
|
||||
)
|
||||
|
||||
@Action(
|
||||
title="Goodbye World",
|
||||
command="goodbye",
|
||||
prerequisites=["hello"],
|
||||
clean_directory=False
|
||||
)
|
||||
def goodbye(self):
|
||||
print "Bye!"
|
||||
|
||||
@Listener(before="goodbye")
|
||||
def goodbyeListener(self):
|
||||
print "Listened to goodbye!"
|
@ -1,96 +0,0 @@
|
||||
use toml::value::Table;
|
||||
use event::{Envelope, Event};
|
||||
|
||||
pub trait EventFilter: Sync + Send {
|
||||
fn accept (&self, envelope: &Envelope) -> bool;
|
||||
}
|
||||
|
||||
pub struct AttributeEventFilter {
|
||||
// Attributes that can be filtered out
|
||||
event_type: Option<String>,
|
||||
username: Option<String>,
|
||||
channel: Option<String>,
|
||||
message: Option<String>
|
||||
}
|
||||
|
||||
impl AttributeEventFilter {
|
||||
pub fn new (attributes: &Table) -> AttributeEventFilter {
|
||||
AttributeEventFilter {
|
||||
event_type: attributes.get("type").and_then(|value| value.as_str()).map(|value| String::from(value)),
|
||||
message: attributes.get("message").and_then(|value| value.as_str()).map(|value| String::from(value)),
|
||||
username: attributes.get("username").and_then(|value| value.as_str()).map(|value| String::from(value)),
|
||||
channel: attributes.get("channel").and_then(|value| value.as_str()).map(|value| String::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventFilter for AttributeEventFilter {
|
||||
fn accept (&self, envelope: &Envelope) -> bool {
|
||||
let mut result = true;
|
||||
match &envelope.event {
|
||||
&Event::Message { ref message } => {
|
||||
if let Some(ref event_type) = self.event_type {
|
||||
result = result && event_type == "message";
|
||||
}
|
||||
|
||||
if let Some(ref channel_name) = self.channel {
|
||||
match message.channel {
|
||||
Some(ref channel) => result = result && channel_name == &channel.name,
|
||||
None => result = false
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref username) = self.username {
|
||||
result = result && &message.author.name == username;
|
||||
}
|
||||
},
|
||||
&Event::SelfJoin { ref channel } => {
|
||||
if let Some(ref event_type) = self.event_type {
|
||||
result = result && event_type == "selfjoin";
|
||||
}
|
||||
|
||||
if let Some(ref channel_name) = self.channel {
|
||||
result = result && channel_name == &channel.name;
|
||||
}
|
||||
},
|
||||
&Event::SelfQuit { ref channel } => {
|
||||
if let Some(ref event_type) = self.event_type {
|
||||
result = result && event_type == "selfquit";
|
||||
}
|
||||
|
||||
if let Some(ref channel_name) = self.channel {
|
||||
result = result && channel_name == &channel.name;
|
||||
}
|
||||
},
|
||||
&Event::UserJoin { ref channel, ref user } => {
|
||||
if let Some(ref event_type) = self.event_type {
|
||||
result = result && event_type == "userjoin";
|
||||
}
|
||||
|
||||
if let Some(ref channel_name) = self.channel {
|
||||
result = result && channel_name == &channel.name;
|
||||
}
|
||||
|
||||
if let Some(ref username) = self.username {
|
||||
result = result && &user.name == username;
|
||||
}
|
||||
},
|
||||
&Event::UserQuit { ref channel, ref user } => {
|
||||
if let Some(ref event_type) = self.event_type {
|
||||
result = result && event_type == "userquit";
|
||||
}
|
||||
|
||||
if let Some(ref channel_name) = self.channel {
|
||||
result = result && channel_name == &channel.name;
|
||||
}
|
||||
|
||||
if let Some(ref username) = self.username {
|
||||
result = result && &user.name == username;
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
pub mod filter;
|
||||
|
||||
use toml::value::Table;
|
||||
use {Message, Channel, User};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Event {
|
||||
Message { message: Message }, // A user sends a message
|
||||
|
||||
SelfJoin { channel: Channel }, // We join a channel
|
||||
SelfQuit { channel: Channel }, // We quit a channel
|
||||
|
||||
UserJoin { channel: Channel, user: User }, // A user joins a channel
|
||||
UserQuit { channel: Channel, user: User }, // A user quits a channel
|
||||
UserKick { channel: Channel, user: User }, // A usre is kicked from a channel
|
||||
UserBan { channel: Channel, user: User }, // A user is banned from a channel
|
||||
|
||||
TopicChange { channel: Channel }, // Channel topic is changed,
|
||||
|
||||
Configure { configuration: Table } // Request to reconfigure a module
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Envelope {
|
||||
pub from: String,
|
||||
pub event: Event,
|
||||
}
|
||||
|
||||
impl Envelope {}
|
@ -1,6 +0,0 @@
|
||||
pub fn split_command (input: &str) -> Option<(&str, &str)> {
|
||||
match input.split_whitespace().into_iter().next() {
|
||||
Some(command) => { Some((command, input[command.len()..].trim())) },
|
||||
None => None
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
pub mod command;
|
255
src/lib.rs
255
src/lib.rs
@ -1,255 +0,0 @@
|
||||
extern crate toml;
|
||||
extern crate crossbeam;
|
||||
extern crate discord;
|
||||
extern crate rand;
|
||||
extern crate pvn;
|
||||
extern crate echobox;
|
||||
extern crate transformable_channels;
|
||||
extern crate stc;
|
||||
extern crate regex;
|
||||
extern crate multimap;
|
||||
extern crate irc;
|
||||
extern crate thread_local;
|
||||
|
||||
#[macro_use]
|
||||
extern crate hlua;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use toml::value::Table;
|
||||
|
||||
mod modules;
|
||||
use modules::Module;
|
||||
use modules::loader::{ModuleLoader, ModuleLoaderError};
|
||||
|
||||
mod event;
|
||||
use event::Event;
|
||||
use event::Envelope;
|
||||
use event::filter::{EventFilter, AttributeEventFilter};
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use transformable_channels::mpsc::TransformableSender;
|
||||
use transformable_channels::mpsc::Receiver;
|
||||
use multimap::MultiMap;
|
||||
|
||||
mod helpers;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
pub struct Tenquestionmarks {
|
||||
modules: BTreeMap<String, Module>,
|
||||
subscriptions: MultiMap<String, Subscription>
|
||||
}
|
||||
|
||||
impl Tenquestionmarks {
|
||||
pub fn with_modules (modules: BTreeMap<String, Module>) -> Tenquestionmarks {
|
||||
let mut subscriptions = MultiMap::new();
|
||||
for (name, module) in modules.iter() {
|
||||
for parent in module.parents() {
|
||||
info!("{:?} registered as parent of {:?}", parent, name);
|
||||
subscriptions.insert(parent, Subscription::new(name.to_owned(), &module));
|
||||
}
|
||||
|
||||
for child_name in module.children() {
|
||||
if let Some(ref child) = modules.get(&child_name) {
|
||||
info!("{:?} registered as child of {:?}", child_name, name);
|
||||
subscriptions.insert(name.clone(), Subscription::new(child_name.to_owned(), &child));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tenquestionmarks {
|
||||
modules: modules,
|
||||
subscriptions: subscriptions
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_configuration (configuration: Table) -> Result<Tenquestionmarks, ModuleLoaderError> {
|
||||
let loader = ModuleLoader::new();
|
||||
let modules = loader.load_from_configuration(configuration)?;
|
||||
Result::Ok(Tenquestionmarks::with_modules(modules))
|
||||
}
|
||||
|
||||
pub fn get_module (&self, name: &str) -> Option<&Module> {
|
||||
self.modules.get(name)
|
||||
}
|
||||
|
||||
pub fn reconfigure (&self, configuration: &Table) {
|
||||
for (key, module_configuration) in configuration {
|
||||
if let (Some(module_configuration_table), Some(ref module)) = (module_configuration.as_table(), self.modules.get(key)) {
|
||||
module.reconfigure(module_configuration_table.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run (&self) {
|
||||
crossbeam::scope(|scope| {
|
||||
let mut dispatchers: BTreeMap<&str, Receiver<Envelope>> = BTreeMap::new();
|
||||
|
||||
// Event loop threads.
|
||||
// Event loop threads consume events passed in by other modules' dispatcher threads,
|
||||
// and produce events through their own dispatcher threads.
|
||||
let senders: BTreeMap<&str, Sender<Arc<Envelope>>> = self.modules.iter().map(|(key, module)| {
|
||||
let from = key.clone();
|
||||
let (dispatcher_sender, dispatcher_receiver) = transformable_channels::mpsc::channel();
|
||||
dispatchers.insert(key, dispatcher_receiver);
|
||||
|
||||
let mapped_sender = dispatcher_sender.map(move |event: Event| {
|
||||
Envelope {
|
||||
from: from.clone(),
|
||||
event: event
|
||||
}
|
||||
});
|
||||
|
||||
let (module_sender, module_receiver) = mpsc::channel();
|
||||
info!("Spawning event loop thread for \"{}\"", key);
|
||||
scope.spawn(move || {
|
||||
module.run(Box::new(mapped_sender), module_receiver);
|
||||
info!("Event loop thread for \"{}\" is exiting", key);
|
||||
});
|
||||
module.set_sender(&module_sender);
|
||||
(&key[..], module_sender)
|
||||
}).collect();
|
||||
|
||||
// Dispatcher threads.
|
||||
// Dispatcher threads transmit events produced by parent modules to child modules.
|
||||
for (from, receiver) in dispatchers.into_iter() {
|
||||
if let Some(subscriptions) = self.subscriptions.get_vec(from) {
|
||||
let dispatcher_senders: BTreeMap<&str, (&Subscription, Sender<Arc<Envelope>>)> = senders.iter().filter_map(|(key, value)| {
|
||||
subscriptions.iter().find(|subscription| subscription.name == *key)
|
||||
.map(|subscription| (*key, (subscription, value.clone())))
|
||||
}).collect();
|
||||
|
||||
info!("Spawning dispatcher thread for \"{}\"", from);
|
||||
scope.spawn(move || {
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(envelope) => {
|
||||
let arc_envelope = Arc::new(envelope);
|
||||
for (child_name, &(subscription, ref sender)) in dispatcher_senders.iter() {
|
||||
if subscription.can_handle_event(&arc_envelope) {
|
||||
if let Err(err) = sender.send(arc_envelope.clone()) {
|
||||
debug!("Failed to dispatch event to module \"{}\": {:?}", child_name, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
error!("Failed to receive event from module: \"{}\": {:?}", from, err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("Dispatcher thread for \"{}\" is exiting", from);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Message {
|
||||
content: String,
|
||||
author: User,
|
||||
channel: Option<Channel>
|
||||
}
|
||||
|
||||
impl Message {
|
||||
fn reply (&self, message: &str) {
|
||||
match self.channel {
|
||||
Some(ref channel) => channel.send(message),
|
||||
None => self.author.send(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Channel {
|
||||
name: String,
|
||||
description: String,
|
||||
topic: String,
|
||||
sender: Box<MessageSender>
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
pub fn send (&self, message: &str) {
|
||||
self.sender.send_message(message);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct User {
|
||||
name: String,
|
||||
sender: Box<MessageSender>
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn send (&self, message: &str) {
|
||||
self.sender.send_message(message);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MessageSender : Sync + Send + std::fmt::Debug {
|
||||
fn send_message (&self, _: &str) {}
|
||||
}
|
||||
|
||||
pub struct NullMessageSender {}
|
||||
impl MessageSender for NullMessageSender {}
|
||||
impl std::fmt::Debug for NullMessageSender {
|
||||
fn fmt (&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(formatter, "NullMessageSender")
|
||||
}
|
||||
}
|
||||
|
||||
struct Subscription {
|
||||
pub name: String,
|
||||
pub filters: Vec<Box<EventFilter>>
|
||||
}
|
||||
|
||||
impl Subscription {
|
||||
pub fn new (name: String, module: &Module) -> Subscription {
|
||||
let filters: Vec<Box<EventFilter>> = module.config.get("filters")
|
||||
.and_then(|value| value.as_array())
|
||||
.map(|value| value.to_vec())
|
||||
.unwrap_or(vec![])
|
||||
.into_iter()
|
||||
.map(|value| {
|
||||
match value.as_table() {
|
||||
Some(table) => Some(Box::new(AttributeEventFilter::new(table)) as Box<EventFilter>),
|
||||
None => None
|
||||
}
|
||||
})
|
||||
.filter(|possible_filter| possible_filter.is_some())
|
||||
.map(|possible_filter| possible_filter.unwrap())
|
||||
.collect();
|
||||
|
||||
Subscription {
|
||||
name: name,
|
||||
filters: filters
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_handle_event (&self, envelope: &Envelope) -> bool {
|
||||
if !self.filters.is_empty() {
|
||||
for filter in &self.filters {
|
||||
if filter.accept(envelope) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Refusing to transmit envelope from {:?} to {:?} since envelope was filtered out",
|
||||
envelope.from,
|
||||
self.name
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
84
src/main.rs
84
src/main.rs
@ -1,84 +0,0 @@
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
extern crate tenquestionmarks;
|
||||
use tenquestionmarks::Tenquestionmarks;
|
||||
|
||||
extern crate toml;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate env_logger;
|
||||
|
||||
extern crate time;
|
||||
|
||||
extern crate crossbeam;
|
||||
extern crate notify;
|
||||
use notify::{RecommendedWatcher, Watcher, RecursiveMode, DebouncedEvent};
|
||||
|
||||
fn init_logger () {
|
||||
let mut builder = env_logger::Builder::new();
|
||||
builder.format(|buf, record: &log::Record| {
|
||||
let t = time::now();
|
||||
writeln!(buf, "{} {}:{}: {}", time::strftime("%Y-%m-%d %H:%M:%S", &t).unwrap(), record.level(), record.module_path().unwrap_or("?"), record.args())
|
||||
}).filter(None, log::LevelFilter::Info);;
|
||||
|
||||
if env::var("RUST_LOG").is_ok() {
|
||||
builder.parse(&env::var("RUST_LOG").unwrap());
|
||||
}
|
||||
|
||||
builder.init();
|
||||
}
|
||||
|
||||
fn main () {
|
||||
init_logger();
|
||||
|
||||
let config_file_name = env::args().nth(1).unwrap_or("tenquestionmarks.toml".into());
|
||||
match Tenquestionmarks::from_configuration(read_config_from_file(&config_file_name)) {
|
||||
Ok(tqm) => {
|
||||
info!("tenquestionmarks initialized successfully");
|
||||
crossbeam::scope(|scope| {
|
||||
scope.spawn(|| {
|
||||
let (notify_sender, notify_reciever) = mpsc::channel();
|
||||
let mut watcher: RecommendedWatcher = Watcher::new(notify_sender, Duration::from_secs(2)).expect("Failed to create watcher");
|
||||
watcher.watch(&config_file_name, RecursiveMode::NonRecursive).expect("Failed to watch config file");
|
||||
|
||||
loop {
|
||||
if let Ok(event) = notify_reciever.recv() {
|
||||
if let DebouncedEvent::Write(_) = event {
|
||||
info!("Detected modified config file, reconfiguring");
|
||||
tqm.reconfigure(&read_config_from_file(&config_file_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tqm.run();
|
||||
})
|
||||
},
|
||||
Err(e) => error!("Failed to initialize tenquestionmarks: {:?}", e)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_config_from_file (config_file_name: &str) -> toml::value::Table {
|
||||
if let Ok(mut file) = File::open(&config_file_name) {
|
||||
let mut contents = String::new();
|
||||
if let Err(e) = file.read_to_string(&mut contents) {
|
||||
panic!("Failed to open config file {}: {:?}", config_file_name, e);
|
||||
} else {
|
||||
match toml::from_str(&contents) {
|
||||
Ok(configuration) => {
|
||||
info!("Loaded configuration from: {}", config_file_name);
|
||||
configuration
|
||||
},
|
||||
Err(error) => panic!("Failed to parse config file {}: {:?}. Config file must be a valid TOML file.", config_file_name, error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Failed to open config file! Please specify path to a config file.");
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
use modules::EventLoop;
|
||||
use toml::value::Table;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use transformable_channels::mpsc::ExtSender;
|
||||
|
||||
use Message;
|
||||
use event::{Event, Envelope};
|
||||
|
||||
use stc::Link;
|
||||
use stc::searchers::{Searcher, AggregateSearcher};
|
||||
use stc::searchers::yugioh::{YugiohCard, YugiohSearcher};
|
||||
use stc::searchers::mtg::{MtgCard, MtgSearcher};
|
||||
use stc::searchers::mediawiki::MediawikiSearcher;
|
||||
|
||||
use regex::Regex;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub struct AutolinkModule {}
|
||||
|
||||
impl AutolinkModule {
|
||||
pub fn new (_: &Table, _: &Table) -> Box<EventLoop> {
|
||||
Box::new(AutolinkModule {})
|
||||
}
|
||||
}
|
||||
|
||||
fn print_mtg_card (card: &MtgCard, message: &Message) {
|
||||
message.reply(&format!("{}", card.image_url));
|
||||
|
||||
if let Some(ref cost) = card.cost {
|
||||
message.reply(&format!("**{}** (**{}**)", card.name, cost));
|
||||
} else {
|
||||
message.reply(&format!("**{}**", card.name));
|
||||
}
|
||||
|
||||
message.reply(&format!("**{}**", card.typeline));
|
||||
|
||||
if let Some(ref rules) = card.rules {
|
||||
message.reply(&format!("{}", rules));
|
||||
}
|
||||
|
||||
if let Some(ref flavor) = card.flavor {
|
||||
message.reply(&format!("*{}*", flavor));
|
||||
}
|
||||
|
||||
if let (&Some(ref power), &Some(ref toughness)) = (&card.power, &card.toughness) {
|
||||
message.reply(&format!("{}/{}", power, toughness));
|
||||
}
|
||||
}
|
||||
|
||||
fn print_ygo_card (card: &YugiohCard, message: &Message) {
|
||||
if let Some(ref family) = card.family {
|
||||
message.reply(&format!("**{}** ({})", card.name, family));
|
||||
} else {
|
||||
message.reply(&format!("**{}**", card.name));
|
||||
}
|
||||
|
||||
if let Some(ref level) = card.level {
|
||||
message.reply(&format!("**Level**: {}", level));
|
||||
}
|
||||
|
||||
if let Some(ref subtype) = card.subtype {
|
||||
message.reply(&format!("**{} - {}**", card.card_type, subtype));
|
||||
} else {
|
||||
message.reply(&format!("**{}**", card.card_type));
|
||||
}
|
||||
|
||||
message.reply(&format!("{}", card.text));
|
||||
|
||||
if let (&Some(ref atk), &Some(ref def)) = (&card.atk, &card.def) {
|
||||
message.reply(&format!("{}/{}", atk, def));
|
||||
}
|
||||
}
|
||||
|
||||
fn print_any_link (link: &Link, message: &Message) {
|
||||
message.reply(&format!("**Autolink:** {} -> {}", link.label(), link.url()));
|
||||
}
|
||||
|
||||
impl EventLoop for AutolinkModule {
|
||||
fn run (&self, _: Box<ExtSender<Event>>, receiver: Receiver<Arc<Envelope>>) {
|
||||
let link_regex = Regex::new(r"\[\[([^\[\]]*)\]\]").expect("Invalid regex...");
|
||||
let mut searchers = AggregateSearcher::new();
|
||||
searchers.add_searcher("mtg", Box::new(MtgSearcher::new()));
|
||||
searchers.add_searcher("ygo", Box::new(YugiohSearcher::new()));
|
||||
searchers.add_searcher("ygp", Box::new(MediawikiSearcher::new(String::from("https://yugipedia.com/wiki/"))));
|
||||
searchers.add_searcher("pk", Box::new(MediawikiSearcher::new(String::from("https://bulbapedia.bulbagarden.net/wiki/"))));
|
||||
searchers.add_searcher("gp", Box::new(MediawikiSearcher::new(String::from("https://gammapedia.monarch-pass.net/wiki/"))));
|
||||
searchers.add_searcher("ip", Box::new(MediawikiSearcher::new(String::from("http://infinitypedia.org/wiki/"))));
|
||||
searchers.add_searcher("gcl", Box::new(MediawikiSearcher::new(String::from("https://glitchcity.info/wiki/"))));
|
||||
searchers.add_searcher("wp", Box::new(MediawikiSearcher::new(String::from("https://en.wikipedia.org/wiki/"))));
|
||||
|
||||
let mut searcher_cache = BTreeMap::new();
|
||||
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(envelope) => {
|
||||
if let Event::Message { ref message } = envelope.event {
|
||||
debug!("Received message from module {:?}... {:?}", envelope.from, message.content);
|
||||
for cap in link_regex.captures_iter(&message.content) {
|
||||
let term = cap[1].to_owned();
|
||||
if let &mut Some(ref mut item) = searcher_cache.entry(term.to_owned()).or_insert_with(|| searchers.exact_search(&term)) {
|
||||
print_any_link(item, message);
|
||||
if let Some(card) = item.downcast_ref::<MtgCard>() {
|
||||
print_mtg_card(card, message);
|
||||
} else if let Some(card) = item.downcast_ref::<YugiohCard>() {
|
||||
print_ygo_card(card, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => { error!("Error {:?}", error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
use discord;
|
||||
use discord::Discord;
|
||||
use discord::model::{Event, PossibleServer};
|
||||
|
||||
use modules::EventLoop;
|
||||
use toml::value::Table;
|
||||
|
||||
use event;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use transformable_channels::mpsc::ExtSender;
|
||||
|
||||
use std::fmt;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
use crossbeam;
|
||||
|
||||
use {MessageSender, Message, User, Channel};
|
||||
|
||||
pub struct DiscordModule {
|
||||
token: String,
|
||||
playing: String
|
||||
}
|
||||
|
||||
const DEFAULT_PLAYING: &'static str = "tenquestionmarks 0.0.3";
|
||||
|
||||
impl DiscordModule {
|
||||
pub fn new (_: &Table, configuration: &Table) -> Box<EventLoop> {
|
||||
let token = configuration.get("token")
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or("");
|
||||
|
||||
let playing = configuration.get("playing")
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or(DEFAULT_PLAYING);
|
||||
|
||||
Box::new(DiscordModule {
|
||||
token: String::from(token),
|
||||
playing: String::from(playing)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DiscordMessageSender {
|
||||
discord: Arc<Discord>,
|
||||
channel_id: discord::model::ChannelId
|
||||
}
|
||||
|
||||
impl MessageSender for DiscordMessageSender {
|
||||
fn send_message (&self, message: &str) {
|
||||
debug!("Send message to channel id {:?}: {:?}", self.channel_id, message);
|
||||
match self.discord.send_message(self.channel_id, message, "", false) {
|
||||
Ok(message) => { debug!("Send message succeeded: {:?}", message.id); },
|
||||
Err(err) => { error!("Send message failed: {:?}", err) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for DiscordMessageSender {
|
||||
fn fmt (&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
write!(formatter, "DiscordMessageSender {{ channel_id: {:?} }}", self.channel_id)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_channel_object (discord: Arc<Discord>, channel: discord::model::Channel) -> Option<Channel> {
|
||||
match channel {
|
||||
discord::model::Channel::Group(group) => {
|
||||
Some(Channel {
|
||||
name: group.name.unwrap_or_else(|| String::from("Group channel")),
|
||||
description: String::from(""),
|
||||
topic: String::from(""),
|
||||
sender: Box::new(DiscordMessageSender {
|
||||
discord: discord,
|
||||
channel_id: group.channel_id
|
||||
})
|
||||
})
|
||||
},
|
||||
discord::model::Channel::Private(private_channel) => {
|
||||
None
|
||||
},
|
||||
discord::model::Channel::Public(public_channel) => {
|
||||
Some(channel_from_public_channel(discord, public_channel))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn channel_from_public_channel (discord: Arc<Discord>, channel: discord::model::PublicChannel) -> Channel {
|
||||
Channel {
|
||||
name: channel.name,
|
||||
description: String::from(""),
|
||||
topic: channel.topic.unwrap_or_else(|| String::new()),
|
||||
sender: Box::new(DiscordMessageSender {
|
||||
discord: discord,
|
||||
channel_id: channel.id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop for DiscordModule {
|
||||
fn run (&self, sender: Box<ExtSender<event::Event>>, receiver: Receiver<Arc<event::Envelope>>) {
|
||||
let discord = Arc::new(Discord::from_bot_token(&self.token[..]).expect("Discord module: Login failed"));
|
||||
let (mut connection, _) = discord.connect().expect("Discord module: Connection failed");
|
||||
|
||||
info!("Playing {}", self.playing);
|
||||
connection.set_game_name(self.playing.clone());
|
||||
|
||||
crossbeam::scope(|scope| {
|
||||
let discord_sender = discord.clone();
|
||||
scope.spawn(move || {
|
||||
loop {
|
||||
if let Ok(envelope) = receiver.recv() {
|
||||
if let event::Event::Message { ref message } = envelope.event {
|
||||
if let Some(ref channel) = message.channel {
|
||||
let channel_string = channel.name.trim_left_matches('#');
|
||||
if let Ok(channel_id) = channel_string.parse::<u64>() {
|
||||
discord_sender.send_message(discord::model::ChannelId(channel_id), &message.content, "", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
loop {
|
||||
let event = connection.recv_event();
|
||||
debug!("Received event: {:?}", event);
|
||||
|
||||
match event {
|
||||
Ok(Event::ServerCreate(server)) => {
|
||||
match server {
|
||||
PossibleServer::Online(server) => {
|
||||
info!("Joined server: {}", server.name);
|
||||
for channel in server.channels {
|
||||
info!(" - Joined channel: {}", channel.name);
|
||||
|
||||
match sender.send(event::Event::SelfJoin {
|
||||
channel: channel_from_public_channel(discord.clone(), channel)
|
||||
}) {
|
||||
Err(err) => error!("Error sending selfjoin event: {:?}", err),
|
||||
Ok(_) => {}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
Ok(Event::MessageCreate(message)) => {
|
||||
let author = User {
|
||||
name: message.author.name.clone(),
|
||||
sender: Box::new(DiscordMessageSender {
|
||||
discord: discord.clone(),
|
||||
channel_id: message.channel_id
|
||||
})
|
||||
};
|
||||
|
||||
let message = Message {
|
||||
author: author,
|
||||
content: message.content,
|
||||
channel: discord.get_channel(message.channel_id).ok().and_then(|channel| make_channel_object(discord.clone(), channel))
|
||||
};
|
||||
|
||||
if let Err(err) = sender.send(event::Event::Message { message: message }) {
|
||||
error!("Error sending message event: {:?}", err)
|
||||
}
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(discord::Error::Closed(code, body)) => {
|
||||
error!("Gateway closed on us with code {:?}: {}", code, body);
|
||||
break
|
||||
}
|
||||
Err(err) => error!("Received error: {:?}", err)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
use modules::EventLoop;
|
||||
use toml::value::Table;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use transformable_channels::mpsc::ExtSender;
|
||||
|
||||
use helpers::command::split_command;
|
||||
use event::{Event, Envelope};
|
||||
|
||||
pub struct EchoModule {
|
||||
prefix: String
|
||||
}
|
||||
|
||||
impl EchoModule {
|
||||
pub fn new (_: &Table, configuration: &Table) -> Box<EventLoop> {
|
||||
let prefix = configuration.get("prefix")
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or("!echo");
|
||||
|
||||
Box::new(EchoModule {
|
||||
prefix: String::from(prefix)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop for EchoModule {
|
||||
fn run(&self, _: Box<ExtSender<Event>>, receiver: Receiver<Arc<Envelope>>) {
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(envelope) => {
|
||||
match envelope.event {
|
||||
Event::Message { ref message } => {
|
||||
debug!("Received message from module {:?}... {:?}", envelope.from, message.content);
|
||||
match split_command(&message.content) {
|
||||
Some((command, argument)) => {
|
||||
if command == self.prefix {
|
||||
message.reply(argument);
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
Err(error) => { error!("Error {:?}", error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
use modules::EventLoop;
|
||||
use toml::value::Table;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use transformable_channels::mpsc::ExtSender;
|
||||
|
||||
use helpers::command::split_command;
|
||||
use event::{Event, Envelope};
|
||||
use echobox::Echobox;
|
||||
|
||||
pub struct EchoboxModule {
|
||||
prefix: String,
|
||||
file: String
|
||||
}
|
||||
|
||||
impl EchoboxModule {
|
||||
pub fn new (_: &Table, configuration: &Table) -> Box<EventLoop> {
|
||||
let prefix = configuration.get("prefix")
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or("?echobox");
|
||||
|
||||
let file = configuration.get("responses")
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or("echobox.db");
|
||||
|
||||
Box::new(EchoboxModule {
|
||||
prefix: String::from(prefix),
|
||||
file: String::from(file)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop for EchoboxModule {
|
||||
fn run(&self, _: Box<ExtSender<Event>>, receiver: Receiver<Arc<Envelope>>) {
|
||||
let echobox = Echobox::with_file(&self.file).unwrap();
|
||||
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(envelope) => {
|
||||
match envelope.event {
|
||||
Event::Message { ref message } => {
|
||||
debug!("Received message from module {:?}... {:?}", envelope.from, message.content);
|
||||
match split_command(&message.content) {
|
||||
Some((command, in_quote)) => {
|
||||
if command == self.prefix {
|
||||
if !in_quote.is_empty() {
|
||||
match echobox.echo(in_quote) {
|
||||
Ok(out_quote) => message.reply(&format!("**Echobox, quote #{}:** {}", out_quote.id, out_quote.content)),
|
||||
Err(error) => { error!("Error from echobox.echo(): {:?}", error) }
|
||||
}
|
||||
} else {
|
||||
message.reply("**Echobox:** Please enter a quote.");
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
Err(error) => { error!("Error {:?}", error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,154 +0,0 @@
|
||||
use irc::client::prelude::*;
|
||||
use irc::client::data::command::Command;
|
||||
|
||||
use modules::EventLoop;
|
||||
use toml::value::Table;
|
||||
|
||||
use event;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::mpsc::Receiver;
|
||||
use transformable_channels::mpsc::ExtSender;
|
||||
|
||||
use std::fmt;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
use crossbeam;
|
||||
|
||||
use thread_local::CachedThreadLocal;
|
||||
use {MessageSender, Message, User, Channel};
|
||||
|
||||
pub struct IrcHandler {
|
||||
config: Config
|
||||
}
|
||||
|
||||
const DEFAULT_NICK: &'static str = "tenquestionmarks";
|
||||
|
||||
impl IrcHandler {
|
||||
pub fn new (_: &Table, configuration: &Table) -> Box<EventLoop> {
|
||||
Box::new(IrcHandler {
|
||||
config: Config {
|
||||
nickname: Some(configuration.get("nickname")
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or(DEFAULT_NICK)
|
||||
.to_owned()),
|
||||
server: Some(configuration.get("server")
|
||||
.and_then(|value| value.as_str())
|
||||
.expect("Expected server in IRC config")
|
||||
.to_owned()),
|
||||
channels: Some(configuration.get("channels")
|
||||
.and_then(|value| value.as_array())
|
||||
.map(|value| value.to_vec())
|
||||
.unwrap_or(vec![])
|
||||
.into_iter()
|
||||
.map(|value| { String::from(value.as_str().unwrap()) })
|
||||
.collect()),
|
||||
.. Default::default()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IrcMessageSender {
|
||||
irc: IrcServerWrapper,
|
||||
channel: String
|
||||
}
|
||||
|
||||
impl MessageSender for IrcMessageSender {
|
||||
fn send_message (&self, message: &str) {
|
||||
debug!("Send message to channel {:?}: {:?}", self.channel, message);
|
||||
match self.irc.get().send_privmsg(&self.channel, message) {
|
||||
Ok(_) => { debug!("Send message succeeded"); },
|
||||
Err(err) => { error!("Send message failed: {:?}", err) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for IrcMessageSender {
|
||||
fn fmt (&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
write!(formatter, "IrcMessageSender {{ channel: {:?} }}", self.channel)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_user (prefix: &Option<String>, server: &IrcServer) -> Option<User> {
|
||||
prefix.as_ref().and_then(|prefix| prefix.split("!").next()).map(|name| User {
|
||||
name: name.to_owned(),
|
||||
sender: Box::new(IrcMessageSender {
|
||||
irc: IrcServerWrapper::new(&server),
|
||||
channel: name.to_owned()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
struct IrcServerWrapper {
|
||||
server: IrcServer,
|
||||
thread_local: CachedThreadLocal<IrcServer>
|
||||
}
|
||||
|
||||
impl IrcServerWrapper {
|
||||
pub fn new (server: &IrcServer) -> IrcServerWrapper {
|
||||
IrcServerWrapper {
|
||||
server: server.clone(),
|
||||
thread_local: CachedThreadLocal::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get (&self) -> &IrcServer {
|
||||
self.thread_local.get_or(|| Box::new(self.server.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for IrcServerWrapper {}
|
||||
|
||||
impl EventLoop for IrcHandler {
|
||||
fn run (&self, sender: Box<ExtSender<event::Event>>, receiver: Receiver<Arc<event::Envelope>>) {
|
||||
let server = IrcServer::from_config(self.config.clone()).unwrap();
|
||||
server.identify().unwrap();
|
||||
|
||||
crossbeam::scope(|scope| {
|
||||
let server_sender = server.clone();
|
||||
scope.spawn(move || {
|
||||
loop {
|
||||
if let Ok(envelope) = receiver.recv() {
|
||||
if let event::Event::Message { ref message } = envelope.event {
|
||||
if let Some(ref channel) = message.channel {
|
||||
server_sender.send_privmsg(&channel.name, &message.content);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for server_message in server.iter() {
|
||||
if let Ok(irc_message) = server_message {
|
||||
match irc_message.command {
|
||||
Command::PRIVMSG(channel, message) => {
|
||||
if let Some(author) = make_user(&irc_message.prefix, &server) {
|
||||
let message = Message {
|
||||
author: author,
|
||||
content: message,
|
||||
channel: Some(Channel {
|
||||
name: channel.clone(),
|
||||
description: "".to_owned(),
|
||||
topic: "".to_owned(),
|
||||
sender: Box::new(IrcMessageSender {
|
||||
irc: IrcServerWrapper::new(&server),
|
||||
channel: channel.clone()
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
if let Err(err) = sender.send(event::Event::Message { message: message }) {
|
||||
error!("Error sending message event: {:?}", err)
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
use toml::value::Table;
|
||||
|
||||
use modules::{Module, EventLoop};
|
||||
use modules::discord::DiscordModule;
|
||||
use modules::lua::LuaModule;
|
||||
use modules::stdin::StdinModule;
|
||||
use modules::echo::EchoModule;
|
||||
use modules::random::RandomModule;
|
||||
use modules::pvn::PvnModule;
|
||||
use modules::echobox::EchoboxModule;
|
||||
use modules::autolink::AutolinkModule;
|
||||
use modules::logger::LoggerModule;
|
||||
use modules::irc::IrcHandler;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub struct ModuleLoader {
|
||||
types: BTreeMap<&'static str, fn(&Table, &Table) -> Box<EventLoop>>
|
||||
}
|
||||
|
||||
impl ModuleLoader {
|
||||
pub fn new () -> ModuleLoader {
|
||||
let mut types = BTreeMap::new();
|
||||
types.insert("discord", DiscordModule::new as fn(&Table, &Table) -> Box<EventLoop>);
|
||||
types.insert("lua", LuaModule::new as fn(&Table, &Table) -> Box<EventLoop>);
|
||||
types.insert("stdin", StdinModule::new as fn(&Table, &Table) -> Box<EventLoop>);
|
||||
types.insert("echo", EchoModule::new as fn(&Table, &Table) -> Box<EventLoop>);
|
||||
types.insert("random", RandomModule::new as fn(&Table, &Table) -> Box<EventLoop>);
|
||||
types.insert("pvn", PvnModule::new as fn(&Table, &Table) -> Box<EventLoop>);
|
||||
types.insert("echobox", EchoboxModule::new as fn(&Table, &Table) -> Box<EventLoop>);
|
||||
types.insert("autolink", AutolinkModule::new as fn(&Table, &Table) -> Box<EventLoop>);
|
||||
types.insert("logger", LoggerModule::new as fn(&Table, &Table) -> Box<EventLoop>);
|
||||
types.insert("irc", IrcHandler::new as fn(&Table, &Table) -> Box<EventLoop>);
|
||||
ModuleLoader {
|
||||
types: types
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_from_configuration (&self, configuration: Table) -> Result<BTreeMap<String, Module>, ModuleLoaderError> {
|
||||
let general_config = configuration.get("general")
|
||||
.and_then(|value| value.as_table())
|
||||
.map(|value| value.clone())
|
||||
.unwrap_or_else(|| BTreeMap::new());
|
||||
|
||||
configuration.into_iter().filter(|&(ref key, _)| {
|
||||
key != "general"
|
||||
}).map(|(key, value)| {
|
||||
match value.as_table() {
|
||||
Some(table) => {
|
||||
let module = self.load_single_module(&key, &general_config, table)?;
|
||||
Result::Ok((key, module))
|
||||
},
|
||||
None => Result::Err(ModuleLoaderError { message: format!("Bad configuration parameters for module instance: {}. Configuration for a Module must be a table.", key) })
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
pub fn load_single_module (&self, name: &str, general_configuration: &Table, module_configuration: &Table) -> Result<Module, ModuleLoaderError> {
|
||||
/*
|
||||
* The Module type defaults to the instance name (in the tenquestionmarks configuration)
|
||||
* but can explicitly be set by using the special "type" parameter.
|
||||
*/
|
||||
let module_type: &str = module_configuration.get("type")
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or(name);
|
||||
|
||||
match self.types.get(module_type) {
|
||||
Some(constructor) => Result::Ok(Module {
|
||||
module_type: module_type.to_owned(),
|
||||
config: module_configuration.clone(),
|
||||
event_loop: constructor(general_configuration, module_configuration),
|
||||
sender: Mutex::new(None)
|
||||
}),
|
||||
None => Result::Err(ModuleLoaderError { message: format!("No such module type: {}", module_type) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ModuleLoaderError {
|
||||
message: String
|
||||
}
|
||||
|
||||
impl Error for ModuleLoaderError {
|
||||
fn description(&self) -> &str {
|
||||
&self.message[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ModuleLoaderError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "ModuleLoaderError: {}", self.message)
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
use modules::EventLoop;
|
||||
use toml::value::Table;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use transformable_channels::mpsc::ExtSender;
|
||||
|
||||
use event::{Event, Envelope};
|
||||
|
||||
pub struct LoggerModule {}
|
||||
|
||||
impl LoggerModule {
|
||||
pub fn new (_: &Table, configuration: &Table) -> Box<EventLoop> {
|
||||
Box::new(LoggerModule {})
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop for LoggerModule {
|
||||
fn run(&self, _: Box<ExtSender<Event>>, receiver: Receiver<Arc<Envelope>>) {
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(envelope) => {
|
||||
info!("Received event envelope: {:?}", envelope);
|
||||
}
|
||||
Err(error) => { error!("Error {:?}", error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,201 +0,0 @@
|
||||
use modules::EventLoop;
|
||||
|
||||
use toml::Value;
|
||||
use toml::value::Table;
|
||||
|
||||
use hlua;
|
||||
use hlua::{Lua, LuaFunction, AnyHashableLuaValue, AnyLuaValue, AsMutLua};
|
||||
|
||||
use {User, Message, Channel, NullMessageSender};
|
||||
use event::{Event, Envelope};
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use transformable_channels::mpsc::ExtSender;
|
||||
|
||||
use std::path::Path;
|
||||
use std::fs::File;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct LuaModule {
|
||||
code: Option<String>,
|
||||
file: Option<String>,
|
||||
variables: Table
|
||||
}
|
||||
|
||||
impl LuaModule {
|
||||
pub fn new (_: &Table, config: &Table) -> Box<EventLoop> {
|
||||
Box::new(LuaModule {
|
||||
code: config.get("code").and_then(|value| value.as_str()).map(String::from),
|
||||
file: config.get("file").and_then(|value| value.as_str()).map(String::from),
|
||||
variables: config.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct TomlValueWrapper(Value);
|
||||
implement_lua_read!(TomlValueWrapper);
|
||||
|
||||
impl TomlValueWrapper {
|
||||
pub fn lua_set (self, lua: &mut Lua, key: &str) {
|
||||
match self.0 {
|
||||
Value::String(string) => lua.set(key, string),
|
||||
Value::Integer(integer) => lua.set(key, (integer as i32)),
|
||||
Value::Float(float) => lua.set(key, float),
|
||||
Value::Boolean(boolean) => lua.set(key, boolean),
|
||||
Value::Table(table) => {},
|
||||
Value::Array(array) => {},
|
||||
Value::Datetime(datetime) => lua.set(key, datetime.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*impl<'lua, L: AsMutLua<'lua>> hlua::Push<L> for TomlValueWrapper {
|
||||
type Err = hlua::Void;
|
||||
|
||||
fn push_to_lua (self, lua: L) -> Result<hlua::PushGuard<L>, (hlua::Void, L)> {
|
||||
match self.0 {
|
||||
Value::String(string) => string.push_to_lua(lua),
|
||||
Value::Integer(integer) => (integer as i32).push_to_lua(lua),
|
||||
Value::Float(float) => float.push_to_lua(lua),
|
||||
Value::Boolean(boolean) => boolean.push_to_lua(lua),
|
||||
Value::Table(table) => {
|
||||
let hashmap: HashMap<_, _> = table.into_iter().map(|(key, value)| {
|
||||
(key, TomlValueWrapper(value))
|
||||
}).collect();
|
||||
hashmap.push_to_lua(lua)
|
||||
},
|
||||
Value::Array(array) => {
|
||||
let vec: Vec<_> = array.into_iter().map(TomlValueWrapper).collect();
|
||||
vec.push_to_lua(lua)
|
||||
},
|
||||
Value::Datetime(datetime) => datetime.to_string().push_to_lua(lua)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua, L: AsMutLua<'lua>> hlua::PushOne<L> for TomlValueWrapper {}*/
|
||||
|
||||
struct MessageWrapper {
|
||||
envelope: Arc<Envelope>
|
||||
}
|
||||
|
||||
implement_lua_read!(MessageWrapper);
|
||||
implement_lua_push!(MessageWrapper, |mut metatable| {
|
||||
let mut index = metatable.empty_array("__index");
|
||||
index.set("content", hlua::function1(|wrapper: &MessageWrapper| {
|
||||
if let Event::Message { ref message } = wrapper.envelope.event {
|
||||
Some(message.content.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}));
|
||||
index.set("reply", hlua::function2(|wrapper: &MessageWrapper, reply: String| {
|
||||
if let Event::Message { ref message } = wrapper.envelope.event {
|
||||
message.reply(&reply);
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
struct SenderWrapper {
|
||||
sender: Box<ExtSender<Event>>
|
||||
}
|
||||
|
||||
implement_lua_read!(SenderWrapper);
|
||||
implement_lua_push!(SenderWrapper, |mut metatable| {
|
||||
let mut index = metatable.empty_array("__index");
|
||||
index.set("send", hlua::function2(|wrapper: &SenderWrapper, data: HashMap<AnyHashableLuaValue, AnyLuaValue>| {
|
||||
if let Some(&AnyLuaValue::LuaString(ref event_type)) = data.get(&AnyHashableLuaValue::LuaString("type".to_owned())) {
|
||||
match event_type.as_ref() {
|
||||
"message" => {
|
||||
wrapper.sender.send(Event::Message {
|
||||
message: Message {
|
||||
author: User {
|
||||
name: data.get(&AnyHashableLuaValue::LuaString("username".to_owned()))
|
||||
.and_then(|value| match value {
|
||||
&AnyLuaValue::LuaString (ref string_value) => Some(string_value.to_owned()),
|
||||
_ => None
|
||||
})
|
||||
.unwrap_or_else(|| "".to_owned()),
|
||||
sender: Box::new(NullMessageSender {})
|
||||
},
|
||||
channel: data.get(&AnyHashableLuaValue::LuaString("channel".to_owned()))
|
||||
.and_then(|value| match value {
|
||||
&AnyLuaValue::LuaString (ref string_value) => Some(string_value.to_owned()),
|
||||
&AnyLuaValue::LuaNumber (ref number_value) => Some(format!("{}", number_value)),
|
||||
_ => None
|
||||
}).map(|channel| Channel {
|
||||
name: channel,
|
||||
description: "".to_owned(),
|
||||
sender: Box::new(NullMessageSender {}),
|
||||
topic: "".to_owned()
|
||||
}),
|
||||
content: data.get(&AnyHashableLuaValue::LuaString("message".to_owned()))
|
||||
.and_then(|value| match value {
|
||||
&AnyLuaValue::LuaString (ref string_value) => Some(string_value.to_owned()),
|
||||
_ => None
|
||||
})
|
||||
.expect("Invalid message event passed in")
|
||||
}
|
||||
});
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
impl EventLoop for LuaModule {
|
||||
fn run (&self, sender: Box<ExtSender<Event>>, receiver: Receiver<Arc<Envelope>>) {
|
||||
let mut lua = Lua::new();
|
||||
lua.openlibs();
|
||||
|
||||
for (key, value) in &self.variables {
|
||||
if key != "type" {
|
||||
TomlValueWrapper(value.clone()).lua_set(&mut lua, key);
|
||||
}
|
||||
}
|
||||
|
||||
lua.set("sender", SenderWrapper {
|
||||
sender: Box::new(sender.clone())
|
||||
});
|
||||
|
||||
if let Some(ref file) = self.file {
|
||||
lua.execute_from_reader::<(), _>(File::open(&Path::new(file)).unwrap()).unwrap();
|
||||
}
|
||||
|
||||
if let Some(ref code) = self.code {
|
||||
lua.execute(&code).unwrap()
|
||||
}
|
||||
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(envelope) => {
|
||||
match envelope.event {
|
||||
Event::Configure { ref configuration } => {
|
||||
for (key, value) in configuration {
|
||||
if key != "type" {
|
||||
TomlValueWrapper(value.clone()).lua_set(&mut lua, key);
|
||||
}
|
||||
}
|
||||
},
|
||||
Event::Message { ref message } => {
|
||||
let on_message: Option<LuaFunction<_>> = lua.get("on_message");
|
||||
match on_message {
|
||||
Some(mut on_message) => {
|
||||
on_message.call_with_args::<(), _, _>(MessageWrapper {
|
||||
envelope: envelope.clone()
|
||||
}).unwrap();
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
Err(error) => { error!("Error {:?}", error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
pub mod lua;
|
||||
pub mod discord;
|
||||
pub mod stdin;
|
||||
pub mod echo;
|
||||
pub mod random;
|
||||
pub mod pvn;
|
||||
pub mod echobox;
|
||||
pub mod autolink;
|
||||
pub mod logger;
|
||||
pub mod irc;
|
||||
|
||||
pub mod loader;
|
||||
|
||||
use event::{Event, Envelope};
|
||||
|
||||
use std::sync::{Mutex, Arc};
|
||||
use std::sync::mpsc::{Sender, Receiver};
|
||||
use transformable_channels::mpsc::ExtSender;
|
||||
|
||||
use toml::value::Table;
|
||||
|
||||
pub struct Module {
|
||||
event_loop: Box<EventLoop>,
|
||||
module_type: String,
|
||||
sender: Mutex<Option<Sender<Arc<Envelope>>>>,
|
||||
pub config: Table,
|
||||
}
|
||||
|
||||
impl Module {
|
||||
pub fn run (&self, sender: Box<ExtSender<Event>>, receiver: Receiver<Arc<Envelope>>) {
|
||||
self.event_loop.run(sender, receiver);
|
||||
}
|
||||
|
||||
pub fn reconfigure (&self, configuration: Table) {
|
||||
self.send(Envelope {
|
||||
from: "reconfigure".to_owned(),
|
||||
event: Event::Configure {
|
||||
configuration: configuration
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send (&self, event: Envelope) {
|
||||
if let Ok(sender_lock) = self.sender.lock() {
|
||||
if let Some(ref sender) = *sender_lock {
|
||||
sender.send(Arc::new(event));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_sender (&self, sender: &Sender<Arc<Envelope>>) {
|
||||
if let Ok(ref mut sender_lock) = self.sender.lock() {
|
||||
**sender_lock = Some(sender.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parents (&self) -> Vec<String> {
|
||||
self.config.get("parents")
|
||||
.and_then(|value| value.as_array())
|
||||
.map(|value| value.to_vec())
|
||||
.unwrap_or(vec![])
|
||||
.iter()
|
||||
.map(|value| value.as_str())
|
||||
.filter(|value| value.is_some())
|
||||
.map(|value| value.unwrap().to_owned())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn children (&self) -> Vec<String> {
|
||||
self.config.get("children")
|
||||
.and_then(|value| value.as_array())
|
||||
.map(|value| value.to_vec())
|
||||
.unwrap_or(vec![])
|
||||
.iter()
|
||||
.map(|value| value.as_str())
|
||||
.filter(|value| value.is_some())
|
||||
.map(|value| value.unwrap().to_owned())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventLoop : Sync + Send {
|
||||
fn run (&self, _: Box<ExtSender<Event>>, _: Receiver<Arc<Envelope>>) {}
|
||||
}
|
@ -1,169 +0,0 @@
|
||||
use modules::EventLoop;
|
||||
use toml::value::Table;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use transformable_channels::mpsc::ExtSender;
|
||||
|
||||
use event::{Event, Envelope};
|
||||
|
||||
use Message;
|
||||
use helpers::command::split_command;
|
||||
|
||||
use pvn::Fighter;
|
||||
use pvn::pirates::{Pirate, Pirates};
|
||||
use pvn::ninjas::{Ninja, Ninjas};
|
||||
|
||||
pub struct PvnModule {}
|
||||
|
||||
impl PvnModule {
|
||||
pub fn new (_: &Table, configuration: &Table) -> Box<EventLoop> {
|
||||
Box::new(PvnModule {})
|
||||
}
|
||||
}
|
||||
|
||||
fn split_combatants (input: &str) -> Option<(&str, &str)> {
|
||||
let fighters: Vec<&str> = input.split("|").map(|item| item.trim()).collect();
|
||||
if fighters.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Option::Some((
|
||||
*fighters.get(0).expect("should be exactly two fighters"),
|
||||
*fighters.get(1).expect("should be exactly two fighters")
|
||||
))
|
||||
}
|
||||
|
||||
trait PvnFighter: Fighter {
|
||||
fn name (&self) -> &str;
|
||||
fn print (&self, message: &Message);
|
||||
|
||||
fn fight (&self, other: &PvnFighter, message: &Message) {
|
||||
self.print(message);
|
||||
other.print(message);
|
||||
|
||||
if self.power() == other.power() {
|
||||
message.reply("**It's a tie!**");
|
||||
} else if self.power() > other.power() {
|
||||
message.reply(&format!("**Winner: {}!**", self.name()));
|
||||
} else {
|
||||
message.reply(&format!("**Winner: {}!**", other.name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PvnFighter for Pirate {
|
||||
fn print (&self, message: &Message) {
|
||||
message.reply(&format!("**{}**", self.name));
|
||||
message.reply(&format!("Swashbuckling: {}", self.swashbuckling));
|
||||
message.reply(&format!("Drunkenness: {}", self.drunkenness));
|
||||
message.reply(&format!("Booty: {}", self.booty));
|
||||
message.reply(&format!("Weapons: {}", self.weapons.join(", ")));
|
||||
message.reply(&format!("**TOTAL POWER: {}**", self.power()));
|
||||
}
|
||||
|
||||
fn name (&self) -> &str {
|
||||
&self.name[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl PvnFighter for Ninja {
|
||||
fn print (&self, message: &Message) {
|
||||
message.reply(&format!("**{}**", self.name));
|
||||
message.reply(&format!("Sneakiness: {}", self.sneakiness));
|
||||
message.reply(&format!("Pajamas: {}", self.pajamas));
|
||||
message.reply(&format!("Pointy Things: {}", self.pointy_things));
|
||||
message.reply(&format!("Weapons: {}", self.weapons.join(", ")));
|
||||
message.reply(&format!("**TOTAL POWER: {}**", self.power()));
|
||||
}
|
||||
|
||||
fn name (&self) -> &str {
|
||||
&self.name[..]
|
||||
}
|
||||
}
|
||||
|
||||
struct PirateVsNinja {
|
||||
pirates: Pirates,
|
||||
ninjas: Ninjas
|
||||
}
|
||||
|
||||
impl PirateVsNinja {
|
||||
fn pvn_command (&mut self, argument: &str, message: &Message) {
|
||||
match split_combatants(argument) {
|
||||
Some((pirate_name, ninja_name)) => {
|
||||
match self.pirates.get(pirate_name) {
|
||||
Ok(pirate) => {
|
||||
match self.ninjas.get(ninja_name) {
|
||||
Ok(ninja) => {
|
||||
pirate.fight(ninja, message);
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Error getting ninja: {:?}", error);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Error getting pirate: {:?}", error);
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
message.reply("Expected two arguments of the form: {pirate} | {ninja}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pirate_command (&mut self, name: &str, message: &Message) {
|
||||
match self.pirates.get(name) {
|
||||
Ok(pirate) => {
|
||||
pirate.print(message);
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Error getting pirate: {:?}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ninja_command (&mut self, name: &str, message: &Message) {
|
||||
match self.ninjas.get(name) {
|
||||
Ok(ninja) => {
|
||||
ninja.print(message);
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Error getting ninja: {:?}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop for PvnModule {
|
||||
fn run(&self, _: Box<ExtSender<Event>>, receiver: Receiver<Arc<Envelope>>) {
|
||||
let mut pvn = PirateVsNinja {
|
||||
pirates: Pirates::new(),
|
||||
ninjas: Ninjas::new()
|
||||
};
|
||||
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(envelope) => {
|
||||
match envelope.event {
|
||||
Event::Message { ref message } => {
|
||||
let command = split_command(&message.content);
|
||||
debug!("Received message from module {:?}... {:?}", envelope.from, message.content);
|
||||
match command {
|
||||
Some(("?pvn", argument)) => { pvn.pvn_command(argument, message) },
|
||||
Some(("?pirate", name)) => { pvn.pirate_command(name, message) },
|
||||
Some(("?ninja", name)) => { pvn.ninja_command(name, message) },
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Error {:?}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
use modules::EventLoop;
|
||||
use toml::value::Table;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use transformable_channels::mpsc::ExtSender;
|
||||
|
||||
use regex;
|
||||
use regex::Regex;
|
||||
|
||||
use event::{Event, Envelope};
|
||||
use rand;
|
||||
|
||||
pub struct RandomModule {
|
||||
initial_configuration: Table,
|
||||
}
|
||||
|
||||
impl RandomModule {
|
||||
pub fn new (_: &Table, configuration: &Table) -> Box<EventLoop> {
|
||||
Box::new(RandomModule {
|
||||
initial_configuration: configuration.clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn pattern_from_config (configuration: &Table) -> Regex {
|
||||
configuration.get("pattern")
|
||||
.and_then(|value| value.as_str())
|
||||
.map(String::from)
|
||||
.or_else(|| configuration.get("prefix")
|
||||
.and_then(|value| value.as_str())
|
||||
.map(|value| format!("^{}", regex::escape(value))))
|
||||
.and_then(|value| Regex::new(&value).ok())
|
||||
.expect("Invalid value for pattern")
|
||||
}
|
||||
|
||||
fn responses_from_config (configuration: &Table) -> Vec<String> {
|
||||
configuration.get("responses")
|
||||
.and_then(|value| value.as_array())
|
||||
.map(|value| value.to_vec())
|
||||
.unwrap_or(vec![])
|
||||
.into_iter()
|
||||
.map(|value| { String::from(value.as_str().unwrap()) })
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop for RandomModule {
|
||||
fn run (&self, _: Box<ExtSender<Event>>, receiver: Receiver<Arc<Envelope>>) {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut pattern = RandomModule::pattern_from_config(&self.initial_configuration);
|
||||
let mut responses = RandomModule::responses_from_config(&self.initial_configuration);
|
||||
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(envelope) => {
|
||||
match envelope.event {
|
||||
Event::Configure { ref configuration } => {
|
||||
pattern = RandomModule::pattern_from_config(configuration);
|
||||
responses = RandomModule::responses_from_config(configuration);
|
||||
},
|
||||
Event::Message { ref message } => {
|
||||
debug!("Received message from module {:?}... {:?}", envelope.from, message.content);
|
||||
if let Some(captures) = pattern.captures(&message.content) {
|
||||
let mut response = String::new();
|
||||
captures.expand(&rand::sample(&mut rng, &responses, 1)[0], &mut response);
|
||||
message.reply(&response);
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Err(error) => { error!("Error {:?}", error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
use std::io;
|
||||
|
||||
use modules::EventLoop;
|
||||
use toml::value::Table;
|
||||
|
||||
use {MessageSender, Message, User, Channel};
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use transformable_channels::mpsc::ExtSender;
|
||||
|
||||
use event::{Event, Envelope};
|
||||
|
||||
pub struct StdinModule {
|
||||
name: String,
|
||||
channel: Option<String>
|
||||
}
|
||||
|
||||
const DEFAULT_NICK: &'static str = "user";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StdinMessageSender {
|
||||
name: String
|
||||
}
|
||||
|
||||
impl MessageSender for StdinMessageSender {
|
||||
fn send_message (&self, message: &str) {
|
||||
debug!("Send message to stdout: {:?}", message);
|
||||
println!("@{}: {}", self.name, message);
|
||||
}
|
||||
}
|
||||
|
||||
impl StdinModule {
|
||||
pub fn new (_: &Table, configuration: &Table) -> Box<EventLoop> {
|
||||
Box::new(StdinModule {
|
||||
name: configuration.get("name")
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or(DEFAULT_NICK)
|
||||
.to_owned(),
|
||||
channel: configuration.get("channel")
|
||||
.and_then(|value| value.as_str())
|
||||
.map(String::from)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop for StdinModule {
|
||||
fn run(&self, sender: Box<ExtSender<Event>>, _: Receiver<Arc<Envelope>>) {
|
||||
let name = self.name.clone();
|
||||
let channel = self.channel.clone();
|
||||
|
||||
loop {
|
||||
let mut input = String::new();
|
||||
match io::stdin().read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
let message = Message {
|
||||
author: User {
|
||||
name: name.clone(),
|
||||
sender: Box::new(StdinMessageSender {
|
||||
name: name.clone()
|
||||
})
|
||||
},
|
||||
content: input,
|
||||
channel: channel.as_ref().map(|channel| Channel {
|
||||
name: channel.to_owned(),
|
||||
description: "".to_owned(),
|
||||
sender: Box::new(StdinMessageSender {
|
||||
name: channel.clone()
|
||||
}),
|
||||
topic: "".to_owned()
|
||||
})
|
||||
};
|
||||
|
||||
match sender.send(Event::Message { message: message }) {
|
||||
Err(err) => error!("Error sending message event: {:?}", err),
|
||||
Ok(_) => {}
|
||||
}
|
||||
}
|
||||
Err(error) => error!("error: {}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
267
tenquestionmarks.py
Executable file
267
tenquestionmarks.py
Executable file
@ -0,0 +1,267 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tenquestionmarks is an extensible, modular IRC bot.
|
||||
|
||||
This file is governed by the following license:
|
||||
Copyright (c) 2011 Adrian Malacoda, Monarch Pass
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License, version 3, as published by
|
||||
the Free Software Foundation.
|
||||
|
||||
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/>.
|
||||
|
||||
Based on the IRC bot at:
|
||||
http://kstars.wordpress.com/2009/09/05/a-python-irc-bot-for-keeping-up-with-arxiv-or-any-rss-feed/
|
||||
Copyright 2009 by Akarsh Simha
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import optparse
|
||||
import json
|
||||
import traceback
|
||||
import datetime
|
||||
import inspect
|
||||
import re
|
||||
|
||||
for libdir in os.listdir(os.path.join(os.path.dirname(__file__),"lib")):
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__),"lib",libdir))
|
||||
|
||||
import irclib
|
||||
from monarchpass import butterfree
|
||||
|
||||
class Tenquestionmarks(butterfree.Base,irclib.SimpleIRCClient):
|
||||
VERSION_NAME = "Tenquestionmarks"
|
||||
VERSION_NUMBER = "0.16.15"
|
||||
|
||||
def __init__(self):
|
||||
irclib.SimpleIRCClient.__init__(self)
|
||||
self.msgqueue = []
|
||||
butterfree.Base.__init__(self,"tenquestionmarks")
|
||||
|
||||
def config(self):
|
||||
conf = butterfree.Base.config(self)
|
||||
if "port" not in conf:
|
||||
conf["port"] = 6667
|
||||
return conf
|
||||
|
||||
def _dispatcher(self,connection,event):
|
||||
irclib.SimpleIRCClient._dispatcher(self,connection,event)
|
||||
self.log(event.eventtype(),"%s from %s to %s" % (event.arguments(), event.source(), event.target()))
|
||||
if not self.modules() == {}:
|
||||
method = "on_" + event.eventtype()
|
||||
self.dispatch(method,self,event)
|
||||
|
||||
def version(self):
|
||||
"""Creates the version string that is returned from a CTCP version
|
||||
request. This can be overridden."""
|
||||
return "%s %s" % (Tenquestionmarks.VERSION_NAME, Tenquestionmarks.VERSION_NUMBER)
|
||||
|
||||
def connect(self):
|
||||
"""Connects to the IRC network given in the constructor and joins a
|
||||
set of channels.
|
||||
|
||||
When this method completes, the on_connected event is fired for modules
|
||||
to handle."""
|
||||
|
||||
irclib.SimpleIRCClient.connect(self,self.config()["host"], self.config()["port"], self.config()["nick"], None, self.config()["nick"], ircname=self.config()["nick"])
|
||||
if "password" in self.config():
|
||||
self.server.privmsg("NickServ", "identify %s" % (self.config()["password"]))
|
||||
for channel in self.config()["channels"]:
|
||||
self.connection.join(channel)
|
||||
self.dispatch("on_connected",self)
|
||||
|
||||
def loop(self):
|
||||
"""Starts the IRC bot's loop."""
|
||||
|
||||
while 1:
|
||||
while len(self.msgqueue) > 0:
|
||||
msg = self.msgqueue.pop()
|
||||
for channel in self.config()["channels"]:
|
||||
self.log("post","Posting queued message %s" % (msg))
|
||||
try:
|
||||
self.connection.privmsg(channel, msg)
|
||||
except Exception, e:
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
self.log("Error","%s %s" % (e,msg))
|
||||
time.sleep(1) # TODO: Fix bad code
|
||||
self.ircobj.process_once()
|
||||
time.sleep(1) # So that we don't hog the CPU!
|
||||
|
||||
def queue(self, message):
|
||||
"""Adds a message to the end of the bot's message queue."""
|
||||
self.msgqueue.append(message.encode("UTF-8"))
|
||||
|
||||
def on_ctcp(self, connection, event):
|
||||
"""Event handler for CTCP messages. If the CTCP message is a version request,
|
||||
a version string is returned."""
|
||||
|
||||
self.log("ctcp","%s from %s" % (event.arguments(), event.source()))
|
||||
ctcptype = event.arguments()[0]
|
||||
if ctcptype == "VERSION":
|
||||
self.connection.ctcp_reply(event.source().split("!")[0],self.version())
|
||||
|
||||
def on_privmsg(self, connection, event):
|
||||
"""Event handler for messages sent directly to the bot. These
|
||||
are always treated as commands."""
|
||||
|
||||
message = event.arguments()[0]
|
||||
nick = event.source().split("!")[0]
|
||||
try:
|
||||
(command, arg) = message.split(" ",1)
|
||||
except ValueError:
|
||||
command = message
|
||||
arg = ""
|
||||
self.log("cmd","%s called command %s with arg %s" % (nick,command,arg))
|
||||
self.call_command(nick, None, command, arg)
|
||||
|
||||
def on_pubmsg(self, connection, event):
|
||||
"""Event handler for messages sent to a channel in which the
|
||||
bot resides. If the message starts with a certain string, the
|
||||
message is treated as a command."""
|
||||
|
||||
message = event.arguments()[0]
|
||||
nick = event.source().split("!")[0]
|
||||
channel = event.target()
|
||||
if message.startswith(self.command_prefix):
|
||||
try:
|
||||
(command, arg) = message[1:].split(" ",1)
|
||||
except ValueError:
|
||||
command = message[1:]
|
||||
arg = ""
|
||||
self.log("cmd","%s called command %s in %s with arg %s" % (nick,command,channel,arg))
|
||||
self.call_command(nick, channel, command, arg)
|
||||
|
||||
def call_command(self, nick, channel, command, arg):
|
||||
"""Calls a command defined in one or more modules. This method is
|
||||
called indirectly through IRC messages sent from a user, who may
|
||||
optionally be sending that message publicly in a channel.
|
||||
|
||||
The argument is a single string. Depending on how many arguments
|
||||
the command takes, this string may be broken up into a number
|
||||
of strings separated by spaces.
|
||||
|
||||
The return value of the command is output either back to the
|
||||
user or to the channel in which the command was invoked."""
|
||||
|
||||
command_functions = butterfree.find_functions(self.modules(),command)
|
||||
if len(command_functions) > 0:
|
||||
for func in command_functions:
|
||||
argspec = inspect.getargspec(func)
|
||||
numargs = len(argspec.args) - 3
|
||||
varargs = not argspec.varargs == None
|
||||
args = []
|
||||
if varargs:
|
||||
args = arg.split(" ")
|
||||
else:
|
||||
args = arg.split(" ",numargs - 1)
|
||||
args.insert(0,self)
|
||||
args.insert(0,channel)
|
||||
args.insert(0,nick)
|
||||
try:
|
||||
returnvalue = func(*args)
|
||||
if not returnvalue == None:
|
||||
if not channel == None:
|
||||
self.multiline_privmsg(channel, returnvalue)
|
||||
else:
|
||||
self.multiline_privmsg(nick, returnvalue)
|
||||
except Exception, e:
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
self.log("Error","Command %s caused an %s" % (command, e))
|
||||
|
||||
def degrade_to_ascii(self,string):
|
||||
"""In order to allow as wide a range of inputs as possible, if
|
||||
a Unicode string cannot be decoded, it can instead be run through
|
||||
this function, which will change "fancy quotes" to regular quotes
|
||||
and remove diacritics and accent marks, among other things.
|
||||
|
||||
To doubly sure that the string is safe for ASCII, any non-ASCII
|
||||
characters are then removed after any conversion is done."""
|
||||
|
||||
chars = {
|
||||
u"’": "'",
|
||||
u"‘": "'",
|
||||
u"“": '"',
|
||||
u"”": '"',
|
||||
u"Æ": "AE",
|
||||
u"À": "A",
|
||||
u"Á": "A",
|
||||
u"Â": "A",
|
||||
u"Ã": "A",
|
||||
u"Ä": "A",
|
||||
u"Å": "A",
|
||||
u"Ç": "C",
|
||||
u"È": "E",
|
||||
u"É": "E",
|
||||
u"Ê": "E",
|
||||
u"Ë": "E",
|
||||
u"Ì": "I",
|
||||
u"Í": "I",
|
||||
u"Î": "I",
|
||||
u"Ï": "I",
|
||||
u"Ð": "D",
|
||||
u"Ñ": "N",
|
||||
u"Ò": "O",
|
||||
u"Ó": "O",
|
||||
u"Ô": "O",
|
||||
u"Õ": "O",
|
||||
u"Ö": "O",
|
||||
u"Ø": "O",
|
||||
u"Ù": "U",
|
||||
u"Ú": "U",
|
||||
u"Û": "U",
|
||||
u"Ü": "U",
|
||||
u"Ý": "Y",
|
||||
u"Þ": "Th",
|
||||
u"ß": "S",
|
||||
u"–": "-"
|
||||
}
|
||||
for char in chars:
|
||||
string = string.replace(char,chars[char]).replace(char.lower(),chars[char].lower())
|
||||
|
||||
# Strip away anything non-ascii that remains
|
||||
string = "".join([char for char in string if ord(char) < 128])
|
||||
return string
|
||||
def html(self,string):
|
||||
"""Basic parser that converts between a very limited subset of
|
||||
HTML and IRC control codes.
|
||||
|
||||
Note that this parser is very strict about the font tag. It expects
|
||||
either color or color and bgcolor, but not bgcolor alone. It also wants
|
||||
both color and bgcolor to be quoted and separated by no more or less than one space."""
|
||||
|
||||
tags = [
|
||||
[re.compile("<b>(.+?)</b>"),"\x02%(0)s\x02"],
|
||||
[re.compile("<u>(.+?)</u>"),"\x1f%(0)s\x1f"],
|
||||
[re.compile("<font color=\"(.+?)\">(.+?)</font>"),"\x03%(0)s%(1)s\x03"],
|
||||
[re.compile("<font color=\"(.+?)\" bgcolor=\"(.+?)\">(.+?)</font>"),"\x03%(0)s,%(1)s%(2)s\x03"],
|
||||
[re.compile("<font bgcolor=\"(.+?)\" color=\"(.+?)\">(.+?)</font>"),"\x03%(1)s,%(0)s%2(2)s\x03"]
|
||||
]
|
||||
for (regex,replacement) in tags:
|
||||
regex_match = regex.search(string)
|
||||
while regex_match is not None:
|
||||
groups_dict = {}
|
||||
for i in xrange(len(regex_match.groups())):
|
||||
groups_dict[str(i)] = regex_match.groups()[i]
|
||||
|
||||
string = string.replace(regex_match.group(0), replacement % groups_dict)
|
||||
regex_match = regex.search(string)
|
||||
return string
|
||||
def multiline_privmsg(self, target, message):
|
||||
for line in message.split("\n"):
|
||||
self.connection.privmsg(target, line)
|
||||
|
||||
if __name__ == "__main__":
|
||||
tqm = Tenquestionmarks()
|
||||
tqm.connect()
|
||||
tqm.loop()
|
383
tenquestionmarks.py.old
Executable file
383
tenquestionmarks.py.old
Executable file
@ -0,0 +1,383 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tenquestionmarks is an extensible, modular IRC bot.
|
||||
|
||||
This file is governed by the following license:
|
||||
Copyright (c) 2011 Adrian Malacoda, Monarch Pass
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License, version 3, as published by
|
||||
the Free Software Foundation.
|
||||
|
||||
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/>.
|
||||
|
||||
Based on the IRC bot at:
|
||||
http://kstars.wordpress.com/2009/09/05/a-python-irc-bot-for-keeping-up-with-arxiv-or-any-rss-feed/
|
||||
Copyright 2009 by Akarsh Simha
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import optparse
|
||||
import json
|
||||
import traceback
|
||||
import datetime
|
||||
import inspect
|
||||
import re
|
||||
|
||||
for libdir in os.listdir(os.path.join(os.path.dirname(__file__),"lib")):
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__),"lib",libdir))
|
||||
|
||||
import irclib
|
||||
|
||||
class Tenquestionmarks(irclib.SimpleIRCClient):
|
||||
VERSION_NAME = "Tenquestionmarks"
|
||||
VERSION_NUMBER = "0.13.23"
|
||||
|
||||
def __init__(self,hostname, channels, nick, command_prefix="!", password=None, port=6667, directory=None):
|
||||
irclib.SimpleIRCClient.__init__(self)
|
||||
self.hostname = hostname
|
||||
self.port = port
|
||||
self.channel_list = channels
|
||||
self.nick = nick
|
||||
self.password = password
|
||||
if directory == None:
|
||||
directory = os.path.join(os.environ.get("HOME"),".tenquestionmarks")
|
||||
self.directory = directory
|
||||
try:
|
||||
os.makedirs(directory)
|
||||
except OSError: pass
|
||||
self.msgqueue = []
|
||||
self.logfile = open(os.path.join(self.directory,"log.log"),"w")
|
||||
self.command_prefix = command_prefix
|
||||
self.config = self.get_json("options.json")
|
||||
self.modules = {}
|
||||
|
||||
def load_modules(self,modules):
|
||||
"""Loads a set of modules given by name. The modules reside in the
|
||||
modules subdirectory and so are prefixed with the string "module." """
|
||||
self.modules = {}
|
||||
for module in modules:
|
||||
self.log("module","Loading module %s" % (module))
|
||||
try:
|
||||
self.modules[module] = getattr(__import__("modules.%s" % (module)),module)
|
||||
self.log("module","%s loaded" % (module))
|
||||
except ImportError:
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
self.log("module","No module named %s" % (module))
|
||||
|
||||
def _dispatcher(self,connection,event):
|
||||
irclib.SimpleIRCClient._dispatcher(self,connection,event)
|
||||
self.log(event.eventtype(),"%s from %s to %s" % (event.arguments(), event.source(), event.target()))
|
||||
if self.modules:
|
||||
method = "on_" + event.eventtype()
|
||||
self._fire_method_call(method,self,event)
|
||||
|
||||
def _fire_method_call(self,method,*args):
|
||||
if self.modules:
|
||||
for module in self.modules:
|
||||
module = self.modules[module]
|
||||
if hasattr(module, method):
|
||||
getattr(module, method)(*args)
|
||||
|
||||
def version(self):
|
||||
"""Creates the version string that is returned from a CTCP version
|
||||
request. This can be overridden."""
|
||||
return "%s %s" % (Tenquestionmarks.VERSION_NAME, Tenquestionmarks.VERSION_NUMBER)
|
||||
|
||||
def connect(self):
|
||||
"""Connects to the IRC network given in the constructor and joins a
|
||||
set of channels.
|
||||
|
||||
When this method completes, the on_connected event is fired for modules
|
||||
to handle."""
|
||||
|
||||
irclib.SimpleIRCClient.connect(self,self.hostname, self.port, self.nick, None, self.nick, ircname=self.nick)
|
||||
if not self.password == None:
|
||||
self.server.privmsg("NickServ", "identify %s" % (self.nick))
|
||||
for channel in self.channel_list:
|
||||
self.connection.join(channel)
|
||||
self._fire_method_call("on_connected",self)
|
||||
|
||||
def loop(self):
|
||||
"""Starts the IRC bot's loop."""
|
||||
|
||||
while 1:
|
||||
while len(self.msgqueue) > 0:
|
||||
msg = self.msgqueue.pop()
|
||||
for channel in self.channel_list:
|
||||
self.log("post","Posting queued message %s" % (msg))
|
||||
try:
|
||||
self.connection.privmsg(channel, msg)
|
||||
except Exception, e:
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
self.log("Error","%s %s" % (e,msg))
|
||||
time.sleep(1) # TODO: Fix bad code
|
||||
self.ircobj.process_once()
|
||||
time.sleep(1) # So that we don't hog the CPU!
|
||||
|
||||
def queue(self, message):
|
||||
"""Adds a message to the end of the bot's message queue."""
|
||||
self.msgqueue.append(message.encode("UTF-8"))
|
||||
|
||||
def on_ctcp(self, connection, event):
|
||||
"""Event handler for CTCP messages. If the CTCP message is a version request,
|
||||
a version string is returned."""
|
||||
|
||||
self.log("ctcp","%s from %s" % (event.arguments(), event.source()))
|
||||
ctcptype = event.arguments()[0]
|
||||
if ctcptype == "VERSION":
|
||||
self.connection.ctcp_reply(event.source().split("!")[0],self.version())
|
||||
|
||||
def on_privmsg(self, connection, event):
|
||||
"""Event handler for messages sent directly to the bot. These
|
||||
are always treated as commands."""
|
||||
|
||||
message = event.arguments()[0]
|
||||
nick = event.source().split("!")[0]
|
||||
try:
|
||||
(command, arg) = message.split(" ",1)
|
||||
except ValueError:
|
||||
command = message
|
||||
arg = ""
|
||||
self.log("cmd","%s called command %s with arg %s" % (nick,command,arg))
|
||||
self.call_command(nick, None, command, arg)
|
||||
|
||||
def on_pubmsg(self, connection, event):
|
||||
"""Event handler for messages sent to a channel in which the
|
||||
bot resides. If the message starts with a certain string, the
|
||||
message is treated as a command."""
|
||||
|
||||
message = event.arguments()[0]
|
||||
nick = event.source().split("!")[0]
|
||||
channel = event.target()
|
||||
if message.startswith(self.command_prefix):
|
||||
try:
|
||||
(command, arg) = message[1:].split(" ",1)
|
||||
except ValueError:
|
||||
command = message[1:]
|
||||
arg = ""
|
||||
self.log("cmd","%s called command %s in %s with arg %s" % (nick,command,channel,arg))
|
||||
self.call_command(nick, channel, command, arg)
|
||||
|
||||
def call_command(self, nick, channel, command, arg):
|
||||
"""Calls a command defined in one or more modules. This method is
|
||||
called indirectly through IRC messages sent from a user, who may
|
||||
optionally be sending that message publicly in a channel.
|
||||
|
||||
The argument is a single string. Depending on how many arguments
|
||||
the command takes, this string may be broken up into a number
|
||||
of strings separated by spaces.
|
||||
|
||||
The return value of the command is output either back to the
|
||||
user or to the channel in which the command was invoked."""
|
||||
|
||||
command_functions = self.resolve_command(command)
|
||||
if len(command_functions) > 0:
|
||||
for func in command_functions:
|
||||
argspec = inspect.getargspec(func)
|
||||
numargs = len(argspec.args) - 3
|
||||
varargs = not argspec.varargs == None
|
||||
args = []
|
||||
if varargs:
|
||||
args = arg.split(" ")
|
||||
else:
|
||||
args = arg.split(" ",numargs - 1)
|
||||
args.insert(0,self)
|
||||
args.insert(0,channel)
|
||||
args.insert(0,nick)
|
||||
try:
|
||||
returnvalue = func(*args)
|
||||
if not returnvalue == None:
|
||||
if not channel == None:
|
||||
self.multiline_privmsg(channel, returnvalue)
|
||||
else:
|
||||
self.multiline_privmsg(nick, returnvalue)
|
||||
except Exception, e:
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
self.log("Error","Command %s caused an %s" % (command, e))
|
||||
|
||||
def resolve_command(self, cmdname):
|
||||
"""Given a command name, traverses through the modules loaded into
|
||||
the bot and finds functions that match the command name.
|
||||
|
||||
If given a command name by itself, this method will look through
|
||||
all modules for the command. If the command name is given in
|
||||
the form [module].[command], only the given module is considered."""
|
||||
|
||||
funcs = []
|
||||
module = ""
|
||||
if "." in cmdname:
|
||||
(module, cmdname) = cmdname.split(".")
|
||||
self.log("cmd","%s is located in module %s" % (cmdname, module))
|
||||
modobj = self.modules[module]
|
||||
if hasattr(modobj,cmdname):
|
||||
func = getattr(modobj,cmdname)
|
||||
funcs.append(func)
|
||||
else:
|
||||
self.log("cmd","%s is not looking for a specific module" % (cmdname))
|
||||
for modname in self.modules:
|
||||
modobj = self.modules[modname]
|
||||
if hasattr(modobj,cmdname):
|
||||
func = getattr(modobj,cmdname)
|
||||
funcs.append(func)
|
||||
return funcs
|
||||
|
||||
def log(self, operation, message):
|
||||
"""Logs a message onto standard out and into a file in the bot's directory."""
|
||||
|
||||
try:
|
||||
logmessage = u"[%s] %s: %s" % (datetime.datetime.now(), operation.upper(), message)
|
||||
print logmessage
|
||||
self.logfile.write(logmessage)
|
||||
except Exception, e:
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
|
||||
def get_json(self,filename):
|
||||
"""Retrieves a JSON object (Python dictionary) from a file in this bot's directory.
|
||||
|
||||
If the file does not exist, one is created and an empty dictionary
|
||||
is returned."""
|
||||
|
||||
try:
|
||||
target = open(os.path.join(options.directory,filename))
|
||||
obj = json.loads(target.read())
|
||||
target.close()
|
||||
return obj
|
||||
except IOError:
|
||||
self.put_json(filename, {})
|
||||
return {}
|
||||
|
||||
def put_json(self,filename,obj):
|
||||
"""Saves a JSON object (Python dictionary) as a file in this bot's directory."""
|
||||
|
||||
target = open(os.path.join(options.directory,filename),"w")
|
||||
target.write(json.dumps(obj))
|
||||
target.close()
|
||||
def degrade_to_ascii(self,string):
|
||||
"""In order to allow as wide a range of inputs as possible, if
|
||||
a Unicode string cannot be decoded, it can instead be run through
|
||||
this function, which will change "fancy quotes" to regular quotes
|
||||
and remove diacritics and accent marks, among other things.
|
||||
|
||||
To doubly sure that the string is safe for ASCII, any non-ASCII
|
||||
characters are then removed after any conversion is done."""
|
||||
|
||||
chars = {
|
||||
u"’": "'",
|
||||
u"‘": "'",
|
||||
u"“": '"',
|
||||
u"”": '"',
|
||||
u"Æ": "AE",
|
||||
u"À": "A",
|
||||
u"Á": "A",
|
||||
u"Â": "A",
|
||||
u"Ã": "A",
|
||||
u"Ä": "A",
|
||||
u"Å": "A",
|
||||
u"Ç": "C",
|
||||
u"È": "E",
|
||||
u"É": "E",
|
||||
u"Ê": "E",
|
||||
u"Ë": "E",
|
||||
u"Ì": "I",
|
||||
u"Í": "I",
|
||||
u"Î": "I",
|
||||
u"Ï": "I",
|
||||
u"Ð": "D",
|
||||
u"Ñ": "N",
|
||||
u"Ò": "O",
|
||||
u"Ó": "O",
|
||||
u"Ô": "O",
|
||||
u"Õ": "O",
|
||||
u"Ö": "O",
|
||||
u"Ø": "O",
|
||||
u"Ù": "U",
|
||||
u"Ú": "U",
|
||||
u"Û": "U",
|
||||
u"Ü": "U",
|
||||
u"Ý": "Y",
|
||||
u"Þ": "Th",
|
||||
u"ß": "S",
|
||||
u"–": "-"
|
||||
}
|
||||
for char in chars:
|
||||
string = string.replace(char,chars[char]).replace(char.lower(),chars[char].lower())
|
||||
|
||||
# Strip away anything non-ascii that remains
|
||||
string = "".join([char for char in string if ord(char) < 128])
|
||||
return string
|
||||
def html(self,string):
|
||||
"""Basic parser that converts between a very limited subset of
|
||||
HTML and IRC control codes.
|
||||
|
||||
Note that this parser is very strict about the font tag. It expects
|
||||
either color or color and bgcolor, but not bgcolor alone. It also wants
|
||||
both color and bgcolor to be quoted and separated by no more or less than one space."""
|
||||
|
||||
tags = [
|
||||
[re.compile("<b>(.+?)</b>"),"\x02%(0)s\x02"],
|
||||
[re.compile("<u>(.+?)</u>"),"\x1f%(0)s\x1f"],
|
||||
[re.compile("<font color=\"(.+?)\">(.+?)</font>"),"\x03%(0)s%(1)s\x03"],
|
||||
[re.compile("<font color=\"(.+?)\" bgcolor=\"(.+?)\">(.+?)</font>"),"\x03%(0)s,%(1)s%(2)s\x03"],
|
||||
[re.compile("<font bgcolor=\"(.+?)\" color=\"(.+?)\">(.+?)</font>"),"\x03%(1)s,%(0)s%2(2)s\x03"]
|
||||
]
|
||||
for (regex,replacement) in tags:
|
||||
regex_match = regex.search(string)
|
||||
while regex_match is not None:
|
||||
groups_dict = {}
|
||||
for i in xrange(len(regex_match.groups())):
|
||||
groups_dict[str(i)] = regex_match.groups()[i]
|
||||
|
||||
string = string.replace(regex_match.group(0), replacement % groups_dict)
|
||||
regex_match = regex.search(string)
|
||||
return string
|
||||
def multiline_privmsg(self, target, message):
|
||||
for line in message.split("\n"):
|
||||
self.connection.privmsg(target, line)
|
||||
|
||||
if __name__ == "__main__":
|
||||
optparser = optparse.OptionParser()
|
||||
optparser.add_option("-n", "--nick", action="store", type="string", dest="nick", help="IRC bot nick")
|
||||
optparser.add_option("-s", "--host", action="store", type="string", dest="host", help="Hostname of the IRC server")
|
||||
optparser.add_option("-d", "--directory", action="store", type="string", dest="directory", default=os.path.join(os.environ.get("HOME"),".tenquestionmarks"),
|
||||
help="Directory where the bot stores things (default: ~/.tenquestionmarks)")
|
||||
optparser.add_option("-p", "--password", action="store", type="string", dest="password", help="Nickserv password")
|
||||
optparser.add_option("-c", "--channels", action="store", type="string", dest="channels", help="Comma-separated list of channels to join")
|
||||
optparser.add_option("-m", "--modules", action="store", type="string", dest="modules", help="Comma-separated names of modules to load")
|
||||
optparser.add_option("-r", "--command-prefix", action="store", type="string", dest="commandprefix", help="Prefix put in front of user commands (default: !)")
|
||||
(options, args) = optparser.parse_args()
|
||||
conf_file = open(os.path.join(options.directory,"options.json"))
|
||||
conf = json.loads(conf_file.read())
|
||||
conf_file.close()
|
||||
|
||||
if options.nick == None and "nick" in conf:
|
||||
options.nick = conf["nick"]
|
||||
if options.password == None and "password" in conf:
|
||||
options.password = conf["password"]
|
||||
if options.channels == None and "channels" in conf:
|
||||
options.channels = conf["channels"]
|
||||
else:
|
||||
options.channels = options.channels.split(",")
|
||||
if options.modules == None and "modules" in conf:
|
||||
options.modules = conf["modules"]
|
||||
elif not options.modules == None:
|
||||
options.modules = options.modules.split(",")
|
||||
if options.host == None and "host" in conf:
|
||||
options.host = conf["host"]
|
||||
if options.commandprefix == None and "commandprefix" in conf:
|
||||
options.commandprefix = conf["commandprefix"]
|
||||
tqm = Tenquestionmarks(options.host, options.channels, options.nick, options.commandprefix, options.password)
|
||||
if not options.modules == None:
|
||||
tqm.load_modules(options.modules)
|
||||
tqm.connect()
|
||||
tqm.loop()
|
@ -1,193 +0,0 @@
|
||||
[general]
|
||||
foo = "bar"
|
||||
|
||||
[dailies]
|
||||
type = "lua"
|
||||
children = ["irc"]
|
||||
file = "lua/dailies.lua"
|
||||
hour = 8
|
||||
minute = 0
|
||||
second = 0
|
||||
channel = "#eightbar"
|
||||
code = """
|
||||
function chrimbus (time)
|
||||
if time.day == 25 and time.month == 12 then
|
||||
return "https://i.imgur.com/cDiJxrV.gif"
|
||||
end
|
||||
end
|
||||
|
||||
function februaryween (time)
|
||||
if time.day == 14 and time.month == 2 then
|
||||
return "http://s3images.coroflot.com/user_files/individual_files/302239_CauyLQoHZTkSJkkGnr3kVbFtw.jpg"
|
||||
end
|
||||
end
|
||||
|
||||
function monday (time)
|
||||
if time.weekday == "monday" then
|
||||
return "https://memegenerator.net/img/instances/66733493/you-dont-hate-mondays-you-hate-capitalism.jpg"
|
||||
end
|
||||
end
|
||||
|
||||
function tuesday (time)
|
||||
if time.weekday == "tuesday" then
|
||||
return "https://78.media.tumblr.com/996c6866874691590558dce00b394416/tumblr_nxyfp54u121rp1193o1_1280.png"
|
||||
end
|
||||
end
|
||||
|
||||
function wednesday (time)
|
||||
if time.weekday == "wednesday" then
|
||||
return "http://i0.kym-cdn.com/photos/images/original/001/091/264/665.jpg"
|
||||
end
|
||||
end
|
||||
|
||||
function thursday (time)
|
||||
if time.weekday == "thursday" then
|
||||
if time.day == 20 then
|
||||
return "http://i1.kym-cdn.com/photos/images/newsfeed/001/245/590/bd8.jpg"
|
||||
else
|
||||
return "https://78.media.tumblr.com/b05de5acb40dfb4eca044526eed5bbfa/tumblr_inline_p59be6mrQp1scg9wt_540.png"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function friday (time)
|
||||
if time.weekday == "friday" then
|
||||
if time.day == 13 then
|
||||
return "https://www.youtube.com/watch?v=9cPDdQs7iHs"
|
||||
else
|
||||
return "https://www.youtube.com/watch?v=kfVsfOSbJY0"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
run_dailies({chrimbus, februaryween, monday, tuesday, wednesday, thursday, friday})
|
||||
"""
|
||||
|
||||
[irc]
|
||||
nickname = "tenquestionmarks2"
|
||||
server = "irc.rizon.net"
|
||||
channels = ["#eightbar"]
|
||||
|
||||
#[discord]
|
||||
#token = "your token here"
|
||||
|
||||
[stdin]
|
||||
|
||||
[echo]
|
||||
parents = ["stdin", "discord", "irc"]
|
||||
prefix = "?echo"
|
||||
|
||||
[no]
|
||||
type = "random"
|
||||
parents = ["stdin", "discord", "irc"]
|
||||
prefix = "?no"
|
||||
responses = [
|
||||
"https://www.youtube.com/watch?v=WWaLxFIVX1s", # Darth Vader
|
||||
"https://www.youtube.com/watch?v=Hwz7YN1AQmQ", # Mario
|
||||
"https://www.youtube.com/watch?v=O-ycQlfOqyY", # Dr. Robotnik
|
||||
"https://www.youtube.com/watch?v=FSWiMoO8zNE", # Luke Skywalker
|
||||
"https://www.youtube.com/watch?v=31g0YE61PLQ", # Michael Scott
|
||||
"https://www.youtube.com/watch?v=xFGfWrJR5Ck", # Captain Picard
|
||||
"https://www.youtube.com/watch?v=gvdf5n-zI14", # Nope.avi
|
||||
"https://www.youtube.com/watch?v=2HJxya0CWco", # Dr. Evil
|
||||
"https://www.youtube.com/watch?v=HIAql1AfSSU", # Finn the Human
|
||||
"https://www.youtube.com/watch?v=4LSJJeR6MEU", # Trunks
|
||||
"https://www.youtube.com/watch?v=6h7clHdeg6g", # Vegeta
|
||||
"https://www.youtube.com/watch?v=cYTzynLuEuk", # Freakazoid
|
||||
"https://www.youtube.com/watch?v=6BoVUpSsA1A", # Ganon
|
||||
"https://www.youtube.com/watch?v=Oz7b7uYG0pk", # Robbie Rotten's Clone/Brother (We Are #1)
|
||||
"https://www.youtube.com/watch?v=iabC7-9YUG4", # Cleveland Brown
|
||||
"https://www.youtube.com/watch?v=iGLh9hRmRcM", # Homer Simpson
|
||||
"https://www.youtube.com/watch?v=zfbK_dbsCu0", # Nathan Explosion
|
||||
"https://www.youtube.com/watch?v=WfpyGyb1J4I", # Eric Cartman
|
||||
"https://www.youtube.com/watch?v=wOxt9PoJNkg", # Nostalgia Critic
|
||||
]
|
||||
|
||||
[yes]
|
||||
type = "random"
|
||||
parents = ["stdin", "discord", "irc"]
|
||||
prefix = "?yes"
|
||||
responses = [
|
||||
"https://www.youtube.com/watch?v=JPVaDaynNKM", # Captain Falcon
|
||||
"https://www.youtube.com/watch?v=P3ALwKeSEYs", # M. Bison
|
||||
"https://www.youtube.com/watch?v=FJbmB9k2Y88", # Daniel Bryan
|
||||
"https://www.youtube.com/watch?v=kfk5NIG7AhY", # Plankton
|
||||
"https://www.youtube.com/watch?v=5v15U2uaV6k", # Jerry's boss (Rick and Morty)
|
||||
"https://www.youtube.com/watch?v=DrKmo0YAZEo", # Simpsons Yes Guy
|
||||
"https://www.youtube.com/watch?v=6VU1Kb7k0cs", # Majin Vegeta
|
||||
"https://www.youtube.com/watch?v=jyJyI3z_tcc", # Scouter Vegeta
|
||||
"https://www.youtube.com/watch?v=jcreG-bhRRA", # Piccolo
|
||||
"https://www.youtube.com/watch?v=XFDGnmQyDf4", # Algernop Krieger
|
||||
"https://www.youtube.com/watch?v=gSnfdncZCYo", # Twilight Sparkle
|
||||
"https://www.youtube.com/watch?v=IPjvDE-rKo0", # William Forester (YTMND)
|
||||
"https://www.youtube.com/watch?v=CBuIqmpeAm0", # Finn Hudson
|
||||
"https://www.youtube.com/watch?v=q6EoRBvdVPQ", # Oro (Yee Dinosaur)
|
||||
"https://www.youtube.com/watch?v=j44nP2J23Jk", # Austin Powers
|
||||
"https://www.youtube.com/watch?v=b4mJtqqfMrQ", # Data
|
||||
]
|
||||
|
||||
[chk]
|
||||
type = "random"
|
||||
parents = ["stdin", "discord", "irc"]
|
||||
prefix = "?chk"
|
||||
responses = ["ack"]
|
||||
|
||||
[pvn]
|
||||
parents = ["stdin", "discord", "irc"]
|
||||
|
||||
[echobox]
|
||||
parents = ["stdin", "discord", "irc"]
|
||||
|
||||
[lua]
|
||||
parents = ["stdin", "discord"]
|
||||
code = """
|
||||
function on_message (message)
|
||||
message:reply("Lua says: " .. message:content())
|
||||
end
|
||||
"""
|
||||
foo = "bar"
|
||||
|
||||
[lua2]
|
||||
type = "lua"
|
||||
parents = ["stdin", "discord"]
|
||||
filters = [{ username = "David" }]
|
||||
code = """
|
||||
function on_message (message)
|
||||
message:reply("Lua2 says: " .. message:content())
|
||||
end
|
||||
"""
|
||||
|
||||
[lua3]
|
||||
type = "lua"
|
||||
#children = ["lua", "irc"]
|
||||
code = """
|
||||
local clock = os.clock
|
||||
function sleep(n) -- seconds
|
||||
local t0 = clock()
|
||||
while clock() - t0 <= n do end
|
||||
end
|
||||
|
||||
while true do
|
||||
sender:send({type = "message", channel = "#eightbar", message = "Hello world!"})
|
||||
sleep(10)
|
||||
end
|
||||
"""
|
||||
|
||||
[autolink]
|
||||
parents = ["stdin", "discord", "irc"]
|
||||
|
||||
[logger]
|
||||
parents = ["stdin", "discord", "irc"]
|
||||
#filters = [{ username = "Dave" }, { username = "Kevin" }]
|
||||
|
||||
[icced]
|
||||
type = "random"
|
||||
parents = ["stdin", "discord", "irc"]
|
||||
pattern = "(?i)\\bicc?ed?\\b"
|
||||
responses = ["Did some carbon-based lifeform just say **I C E**?"]
|
||||
|
||||
[trout]
|
||||
type = "random"
|
||||
parents = ["stdin", "discord", "irc"]
|
||||
pattern = "^\\?slap (.*)"
|
||||
responses = ["/me slaps $1 around a bit with a large trout", "/me slaps $1 around a bit with a large brick"]
|
Loading…
x
Reference in New Issue
Block a user