-
Notifications
You must be signed in to change notification settings - Fork 4
/
README.autumn
1118 lines (857 loc) · 47 KB
/
README.autumn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
= Autumn: A Ruby IRC Bot Framework
<b>Version 3.0 (Jul 4, 2008)</b>
Author:: Tim Morgan (mailto:[email protected])
Copyright:: Copyright (c)2007-2008 Tim Morgan
License:: Distributed under the same terms as Ruby. Portions of this code are
copyright (c)2004 David Heinemeier Hansson; please see
libs/inheritable_attributes.rb for more information.
Autumn is a full-featured framework on top of which IRC bots (called "leaves")
can be quickly and easily built. It features a very Ruby-like approach to
bot-writing, a complete framework for loading and daemonizing your bots,
multiple environment contexts, a database-backed model, and painless logging
support.
== Requirements
Autumn requires RubyGems (http://www.rubygems.org) and the Daemons and Facets*
gems. Install RubyGems then run <tt>sudo gem install daemons facets</tt> in a
command line in order to run Autumn.
If you wish to use a database backend for your bot, you will need the DataMapper
gem. To install, see the DataMapper website (http://www.datamapper.org).
The included example bot Scorekeeper requires the DataMapper gem. It can
optionally use the Chronic gem to enhance its textual date parsing. The other
example bot, Insulter, is much simpler and can run under any Autumn
configuration.
== Directory Structure
An Autumn installation is like a Ruby on Rails installation: There is a
certain directory structure where your files go. A lot of files and folders will
seem confusing to people who have never used Autumn before, but bear with me. In
a bit I will explain in detail what all of this stuff is. For now, here is an
overview you can consult for future reference:
* <b>config/</b> - Configuration files and season definitions
* global.yml - Universal settings that apply to every season
* <b>seasons/</b> - Contains directories for each season (see "Seasons")
* <b>testing/</b> - Example season
* database.yml - Example database configuration file
* leaves.yml - Example bot configuration file
* season.yml - Season configuration
* stems.yml - Example IRC configuration file
* <b>doc/</b> - HTML documentation generated by RDoc
* <b>api/</b> - Autumn API documentation
* <b>leaves/</b> - Autumn leaves documentation
* <b>leaves/</b> - Autumn leaves. Each subdirectory contains all the code and
data for a leaf.
* <b>insulter/</b> - Very simple example leaf
* <i>See the *scorekeeper* directory</i>
* <b>scorekeeper/</b> - Database-backed, full-featured example leaf
* config.yml - Optional leaf-global configuration options
* controller.rb - The leaf's controller object
* <b>data/</b> - Optional directory for data storage (not used by Autumn)
* <b>helpers/</b> - Modules that extend the controller and views
* <b>models/</b> - Active record-type database objects
* <b>tasks</b> - Additional rake tasks for this leaf
* <b>views/</b> - ERb views for each of the leaf's commands
* <b>libs/</b> - Autumn core code
* channel_leaf.rb - A leaf subclass that can ignore messages from certain
channels its in
* coder.rb - Used by script/generate to write out Ruby code
* ctcp.rb - CTCP support library
* daemon.rb - Provides support for different kinds of IRC servers
* datamapper_hacks.rb - Some hacks to help DataMapper work with Autumn
* foliater.rb - Instantiates and manages stems and leaves
* formatting.rb - Provides support for different kinds of IRC client text
formatting and colorization
* generator.rb - Library used by script/generate
* genesis.rb - Boots the Autumn environment
* inheritable_attributes.rb - Adds support for class-level inheritable
attributes
* leaf.rb - The core bot superclass
* log_facade.rb - Simplifies logging for stems and leaves
* misc.rb - RubyCore class additions and other knick-knacks
* script.rb - Library used by script/generate and script/destroy
* speciator.rb - Manages global, season, stem, and leaf configurations
* stem.rb - IRC client library
* stem_facade.rb - Additional methods to simplify the Stem class
* <b>log/</b> - Directory where (most) Autumn logs are written (see the "Logs"
section)
* Rakefile - Contains the rake tasks used to control Autumn (see the "Tasks"
section)
* README - This file
* README.textile - Textile-formatted readme
* <b>resources/</b> - Data files used by Autumn
* <b>daemons/</b> - Data files describing different IRC server types
* <b>script/</b> - Helper scripts for controlling Autumn
* daemon - Runs Autumn as a daemon
* destroy - Destroys Autumn objects
* generate - Creates Autumn objects
* server - Starts Autumn
* <b>shared/</b> - Shared code libraries available to all leaves
* <b>tmp/</b> - Temporary files, such as PID files
== Configuring Autumn for Your First Launch
Before you can run Autumn and try out the example leaves, you'll need to set up
a few things. Here are the steps:
=== Configure Your Testing Season
In Autumn, your leaves run in an environment, called a "season." Each season has
different leaves and different settings for those leaves. By default, Autumn
comes with a season called "testing" already set up for you. You can edit that
season or create a new one with <tt>script/generate season [season name]</tt>.
The files for your season are stored in the config/seasons directory.
First, edit the stems.yml file. This file stores information about your IRC
connection. Edit it to connect to an IRC server of your choosing. For more
information, see "Stems" below.
Next, edit the database.yml file. As mentioned previously, Scorekeeper requires
the DataMapper gem because it uses a persistent store. By default it's set up to
use a MySQL database, but you can use PostgreSQL or SQLite 3 if you'd like. If
you'd prefer not to install any of these database solutions, delete the
database.yml file and remove the Scorekeeper leaf from the leaves.yml and
stems.yml files.
If you do choose to set up a database, you will have to run <tt>rake
db:migrate</tt> after your database.yml file is configured and your database is
created.
Lastly, view the leaves.yml file. You shouldn't have to make any changes to this
file, but it's a good idea to look at it to see how leaves are configured. You
can do the same with the season.yml file. See "Seasons" and "Leaves" below for
more.
=== Starting the Server
Run the shell command <tt>script/server</tt> to start the server. After a short
while, your leaf should appear in the channel you specified. You can type
"!points Coolguy +5" and then "!points" to get started using Scorekeeper, or
"!insult" to play with Insulter. Have some fun, and when you're satisfied, stop
the server by typing "!quit".
If you'd like to daemonize your server, you can use the shell commands <tt>rake
app:start</tt> and <tt>rake app:stop</tt>. For more information, see "Tasks"
below.
== Making Your Own Leaf
Making your own leaf using Autumn is easy. In this tutorial, I'll show you how
to make a simple Fortune bot that responds to a few basic commands.
=== Step 1: Subclass Leaf
Create a new leaf by typing <tt>script/generate leaf fortune</tt>. This will
create a fortune directory in the leaves directory, along with the bare bones of
files needed within that directory. Edit the controller.rb file. First we'll
create an array to hold our fortunes:
FORTUNES = [
"You will make someone happy today.",
"Someone you don't expect will be important to you today.",
"Today will bring unexpected hardships."
]
As you can see, our 3 meager fortunes are stored in the +FORTUNES+ class
constant. Now, we'll want it to respond to the "!fortune" command, and all you
have to do is create a method called +fortune_command+ to make it work:
def fortune_command(stem, sender, reply_to, msg)
FORTUNES.at_rand
end
The +at_rand+ method is provided by Facets, so you'll need to add a <tt>require
'facets/random'</tt> line at the top of your file. Our method returns a fortune
at random, which is automatically transmitted to the channel or nick where the
command was received.
Of course, any self-respecting fortune bot announces its presence when it starts
up, so, in your +Controller+ class, override the Autumn::Leaf#did_start_up
method to display a cheerful greeting:
def did_start_up
stems.message 'FortuneBot at your service! Type "!fortune" to get your fortune!'
end
...and that's it! You now have a fully functional fortune bot featuring -- not
two -- but <i>three</i> unique and exciting fortunes!
(For more on that <tt>stems.message</tt> bit, see "Stems.")
=== Step 2: Add the Leaf to Your Season
If you want, you can add the fortune bot to your leaves.yml and stems.yml files
to try it out. Adding a leaf is easy; simply duplicate the structure used for
another leaf's entry and change the values as appropriate. A typical two-leaf
configuration will look like:
Scorekeeper:
class: Scorekeeper
respond_to_private_messages: false
Fortune:
class: Fortune
respond_to_private_messages: true
As you notice, each leaf instance is given a name. In this example the name
happens to be the same as the leaf's _type_ name, but you could run two copies
of a leaf like so:
Fortune1:
class: Fortune
Fortune2:
class: Fortune
This doesn't make a whole lot of sense for our fortune bot, but for more
complicated bots it can be useful.
We've created the leaf, but we have to add it to the stem for it to work.
(Remember, a stem is an IRC connection and a leaf is a bot.) So, in your
stems.yml file, add an entry for this leaf. Your new config will appear
something like:
Example:
nick: Scorekeeper
leaves:
- Scorekeeper
- Fortune
rejoin: true
channel: somechannel
server: irc.someserver.com
When you restart the server, the bot will come back online and will now also
respond to the "!fortune" command. This is a helpful tutorial on how stems and
leaves are separate. One leaf can have many stems, and one stem can have many
leaves. You can combine these two entities however you need.
=== Step 3: Upgrade to ERb Views
You've already learned that for your <tt>[word]_command</tt>-type methods, the
bot responds with whatever string your method returns. For more complicated
commands, however, you may want to upgrade to full view abstraction, a la Ruby
on Rails. This is what the views directory is for.
If you place a .txt.erb file in the views directory named after your command, it
will be parsed by ERb and rendered as the result. You can pass variables to the
ERb parser by using the Autumn::Leaf#var method. Let's upgrade our
+fortune_command+ method for that:
def fortune_command(stem, sender, reply_to, msg)
var :fortune => FORTUNES.at_rand
end
We can then write a view, fortune.txt.erb, which will render the fortune:
<%= var :fortune %>
OK, so admittedly, this doesn't really get us anywhere, but for more complicated
bots, this well help separate view and controller concerns.
For more information on view rendering, see the Autumn::Leaf#render method.
== Seasons
Each time you start Autumn, the process launches in a certain season (a.k.a.
environment context). This season is defined in the config/global.yml file. You
can temporarily override it by setting the +SEASON+ environment variable (e.g.,
<tt>SEASON=production script/server</tt>).
It's important to realize that an season is just a name, nothing more. You can
have as many seasons as you like, and name them anything that you like. Autumn
will load the config files for the season you've indicated as active. Autumn
doesn't really care if it's named "production" or "live" or
"testing-on-jeffs-machine"; it's all the same to Autumn.
Your season's configuration is stored in the season.yml file within your season
directory. Currently it supports one directive, +logging+. This sets the minimum
log level (such as +debug+ or +warn+). If the log level is set to +debug+, it
also enables console output parroting. (See the "Logging" section.)
The power of seasons comes in custom configuration options. For instance,
consider that you have a testing and production season. In your testing season,
your season.yml file contains:
dont_http: true
and in production, it contains:
dont_http: false
Now, in your code, you might have a method like:
def scores_command(stem, sender, reply_to, msg)
if options[:dont_http] then
return "Some fake sports scores."
else
# go on the web and find real sports scores
end
end
=== Standard Configuration Options
==== Global
System-wide configuration is done in the config/global.yml file. It supports by
default the following directives:
+season+:: The season to launch in.
+log_history+:: The number of historical logfiles to keep (default 10).
In addition, the following options are available (but cannot be set in the yml
file):
+root+:: The root directory of the Autumn installation.
+system_logger+:: The Autumn::LogFacade instance that records system messages.
==== Season
Season-specific configuration is done in the config/seasons/[season]/season.yml
file. Currently it only supports one directive, +logging+, which takes log
levels such as +debug+ or +warn+.
==== Stem
Stem-specific configuration is done in the config/seasons/[season]/stems.yml
file. It's important to note that stem and leaf configurations are completely
independent of each other. (In other words, stem options do not override leaf
options, nor vice versa.) Therefore, you generally won't add custom directives
to the stems.yml file, because you generally won't be working with stems
directly. The standard options are:
+server+:: The address of the IRC server.
+port+:: The IRC server port (default 6667).
+local_ip+:: The IP address to connect on (for virtual hosting).
+nick+:: The nick to request.
+password+:: The nick's password, if it is registered.
+channel+:: A channel to join.
+channels+:: A list of channels to join.
+leaf+:: The name of a leaf to run.
+leaves+:: A list of leaves to run. (These are the names of leaf configurations
in leaves.yml, not leaf subclasses.)
+rejoin+:: If true, the stem will rejoin any channels it is kicked from.
+server_password+:: The password for the IRC server, if necessary.
+ssl+:: If true, the connection to the IRC server will be made over SSL.
+server_type+:: The IRC server type. See resources/daemons for a list of valid
server types. If you do not manually set this value, it will be
guessed automatically.
+case_sensitive_channel_names+:: If true, channel names will be compared with
case sensitivity.
+dont_ghost+:: If true, the stem will not try to GHOST a registered nick if it's
taken.
+ghost_without_password+:: If true, the stem will use the GHOST command without
a password. Set this for servers that use some other
form of nick authentication, such as hostname-based.
+user+:: The username to send (optional).
+name+:: The user's real name (optional).
The +channel+ and +channels+ directives can also be used to specify a password
for a password protected channel, like so:
channel:
channelname: channelpassword
or
channels:
- channel1: password1
- channel2: password2
The +port+, +server_type+, and <tt>channel</tt>/<tt>channels</tt> options are
set in the config file but not available in the +options+ hash. They are
accessed directly from attributes in the Stem instance, such as the +channels+
attribute.
==== Leaf
Leaf-specific configuration is done in the config/seasons/[season]/leaves.yml
file and the leaves/[leaf]/config.yml file, with the former taking precedence
over the latter. As mentioned above, leaf and stem configurations are completely
separate, so one does not override the other. The standard options are:
+class+:: The type of the leaf. It must be a subdirectory in the leaves
directory.
+command_prefix+:: The text that must precede each command. Defaults to "!".
+respond_to_private_messages+:: If true, the leaf will parse commands in
whispers, and respond over whispers to those
commands.
+database+:: A database connection to use (as defined in database.yml). By
default Autumn will choose a connection named after your leaf.
+formatter+:: The name of a module in Autumn::Formatting that will handle output
formatting and colorization. This defaults to mIRC-style
formatting.
In addition, the following options are available (but cannot be set in the yml
file):
+root+:: The root directory of the leaf installation.
The leaves.yml file is optional. When not included, each leaf in the leaves
directory will be automatically instantiated once.
=== Custom Configuration Options
All configuration files support user-generated directives. You can set options
at any level. Options at a more narrow level override those at a broader level.
Options are maintained and cataloged by the Autumn::Speciator singleton. You
could access the singleton directly, but most objects have an +options+
attribute providing simpler access to the Speciator.
For example, to access options in a leaf, all you do is call, for example,
<tt>options[:my_custom_option]</tt>. +my_custom_option+ can be set at the
global, season, or leaf level.
== Leaves
The Autumn::Leaf class has many tools to help you write your leaves. These
include things like filters, helpers, loggers, and an easy to use IRC library.
The Autumn::Leaf and Autumn::Stem class docs are the most thorough way of
learning about each of these features, but I'll walk you through the basics
here.
=== The Many Methods of Leaf
By subclassing Autumn::Leaf, you gain access to a number of neat utilities.
These generally come in three classes: IRC commands that have already been
written for you, utility methods you can call, and invoked methods you can
override. Utility methods do things like add filters. Invoked methods are called
when certain events happen, like when your leaf starts up or when a private
message is received. You override them in your leaf to customize how it responds
to these events.
<b>Invoked methods</b>:: +will_start_up+, +did_start_up+,
+did_receive_channel_message+, etc.
<b>Utility methods</b>:: +before_filter+, +database+, etc.
<b>IRC commands</b>:: +quit_command+, +reload_command+, +autumn_command+, etc.
See the class docs for more information on these methods.
In addition, your leaf is designated as a listener for its Autumn::Stem
instances. In short, this means if you want even finer control over the IRC
connection, you can implement listener methods. See the
Autumn::Stem#add_listener method for examples of such methods.
Finally, your leaf can implement methods that are broadcast by listener plugins.
An example of such a plugin is the Autumn::CTCP class, which is included in all
stems by default. Visit its class docs to learn more about how to send and
receive CTCP requests.
=== Filters
Filters are methods that are run either before or after a command is executed.
In the former case, they can also prevent the command from being run. This is
useful for authentication, for instance: A filter could determine if someone is
authorized to run a command, and prevent the command from being run if not.
Use filters to save yourself the effort of rewriting code that will run before
or after a command is executed. Filter methods are named <tt>[word]_filter</tt>
and they are added to the filter chain using the +before_filter+ and
+after_filter+ methods (like in Ruby on Rails). As an example, imagine you
wanted your bot to say something after each command:
class Controller > Autumn::Leaf
after_filter :outro
private
def outro_filter(stem, channel, sender, command, msg, opts)
stem.message "This has been a production of OutroBot!", channel
end
end
The result of this is that after each command, the leaf will make a dramatic
exit. (Why did I use +after_filter+ and not +before_filter+? Because as I said
earlier, a +before_filter+ can stop the command from being executed; the only
way we know for sure that the command was executed -- and therefore should be
outroed -- is to use an +after_filter+.)
I made the +outro_filter+ method private because I felt it shouldn't be exposed
to other classes; this is not a requirement of the filter framework, though.
Now let's say you wanted to prevent the command from being run in some cases.
The most obvious application of this feature is authentication. Autumn already
includes a robust authentication module, but for the sake of example, let's
pretend you wanted to do your own authentication in your leaf. So, you write a
+before_filter+ to determine if the user is authenticated.
<tt>before_filter</tt>s have return values; if they return false, the filter
chain is halted and the command is suppressed. If you want to have your leaf
display some sort of message (like "Nice try!"), you need to include that in
your filter.
As an example, here's a simple form of authentication that just checks a
person's nick:
class Controller < Autumn::Leaf
before_filter :authenticate, :only => :quit, :admin => 'Yournick'
def authenticate_filter(stem, channel, sender, command, msg, opts)
sender == opts[:admin]
end
end
I'm introducing you to three new features with this sample:
* You can use the <tt>:only</tt> option to limit your filter to certain
commands. Note that you specify the _command_ name as a symbol, _not_ the
method name (which would be +quit_command+ in this case).
* You can pass your own options to +before_filter+ and +after_filter+; they are
passed through to your method via the last parameter, +opts+.
* The return value of a +before_filter+ is used to determine if the command
should be run. So be careful that your method does not return nil or false
unless you really mean for the command to be suppressed.
Both of these examples use the parameters sent to your filter method. They are,
in order:
1. the Autumn::Stem instance that received the command,
2. the name of the channel to which the command was sent (or nil if it was a
private message),
3. the sender hash,
4. the name of the command that was typed, as a symbol,
5. any additional parameters after the command (same as the +msg+ parameter in
the <tt>[word]_command</tt> methods),
6. the custom options that were given to +before_filter+ or +after_filter+.
There are two built-in options that you can specify for +before_filter+ and
+after_filter+, and those are +only+ and +except+. They work just like in Rails:
The +only+ option limits the filter to running only on the given command or list
of commands, and the +except+ option prevents the filter from being run on the
given command or list. All other options are passed to the filter for you to
use.
Filters are run in the order they are added to the filter chain. Therefore, a
superclass's filters will run before a subclass's filters, and filters added
later in a class definition will be run after those added earlier.
If you subclass one of your leaves, it inherits your superclass's filters. The
Autumn::Leaf superclass does not have any filters by default, though by default
new leaves come with a simple authentication filter that checks the user's
privilege level.
=== Authentication
You don't need to write a +before_filter+ as shown above, because Autumn already
includes a robust authentication module. The Autumn::Authentication module
includes the +Base+ class and four different subclasses of it. Each of these
subclasses handles a different type of authentication. You can choose the
authentication strategy you want on a leaf-by-leaf basis or for a whole season.
To specify the kind of authentication you want, you must add an +authentication+
directive to your config. If you want to set it for an individual leaf, add it
to the leaves.yml file. If you want all leaves to have the same authentication
strategy, add it to the season.yml or global.yml file.
The +authentication+ directive should be a hash that, at a minimum, includes a
key called +type+. This is the snake_cased name of subclass in
Autumn::Authentication that you wish to use. As an example, here is an entry for
an Administrator bot in a leaves.yml file, with ops-based authentication.
Administrator:
class: Administrator
authentication:
type: op
This will instantiate the Autumn::Authentication::Op class for use with the
Administrator bot.
Other authentication strategies may require additional information. For
instance, if you want to used nick-based authentication, your leaves.yml file
might look like:
Administrator:
class: Administrator
authentication:
type: nick
nick: MyNick
See the class docs for each subclass in Autumn::Authentication for more info on
how you should set up your configs.
=== Persistent Stores
If you would like to use a persistent store for your leaf, you should install
the DataMapper gem and a DataObjects gem for your database of choice (MySQL,
PostgreSQL, or SQLite). DataMapper works almost identically to ActiveRecord, so
if you have any Rails programming experience, you should be able to dive right
in.
Once you've got DataMapper installed, you should create one or more database
connections in your config/seasons/[season]/database.yml file. A sample database
connection looks like:
connection_name:
adapter: mysql
host: localhost
username: root
password: pass
database: database_name
or, in a smaller syntax:
connection_name: mysql://root@pass:localhost/database_name
If you are using the "sqlite3" adapter, the +database+ option is the path to the
file where the data should be written (example:
leaves/fortune/data/my_database.db). You can name your connection however you
want, but you _should_ name it after either your leaf or your leaf subclass.
(More on this below.)
You should also create DataMapper model classes for each of your model objects.
You can place them within your leaf's models directory. This works almost
exactly the same as the app/models directory in Rails.
Once your database, data models, and leaves have been configured, you can use
the <tt>rake db:migrate</tt> task to automatically populate your database.
Now, unlike Rails, Autumn supports multiple database connections. Two leaves can
use two different database connections, or share the same database connection.
Because of this, it's important to understand how to manage your connections.
Autumn tries to do this for you by guessing which connection belongs to which
leaf, based on their names.
For example, imagine you have a leaf named "Fortune" and an instance of that
leaf in leaves.yml named "MyFortune". If you name your database connection
either "Fortune" or "MyFortune" (or "fortune" or "my_fortune"), it will
automatically be associated with that leaf. What this means is that for the
leaf's command methods (such as +about_command+) and invoked methods (such as
+did_receive_private_message+), the database connection will already be set for
you, and you can start using your DataMapper objects just like ActiveRecord
objects.
If, on the other hand, you either <b>named your database connection differently
from your leaf or subclass name</b> or you <b>are writing a method outside of
the normal flow of leaf methods</b> (for instance, one that is directly called
by a Stem, or a different listener), you will need to call the +database+ method
and pass it a block containing your code.
This is terribly confusing, so let me give you an example. Let's assume you've
got a fortune bot running a leaf named "FortuneLeaf", so your leaves.yml
configuration is:
FortuneBot:
class: FortuneLeaf
And you have a database connection for that leaf, named after the leaf's class:
fortune_leaf:
adapter: sqlite3
database: leaves/fortune_leaf/data/development.db
Let's further assume you have a simple DataMapper object:
class Fortune
include DataMapper::Resource
property :id, Integer, :serial => true
property :text, String
end
Now, if we wanted to write a "!fortune" command, it would appear something like
this:
def fortune_command(stem, sender, reply_to, msg)
fortunes = Fortune.all
fortunes[rand(fortunes.size)].text
end
Autumn automatically knows to execute this DataMapper code in the correct
database context. It knows this because your leaf's name is +FortuneLeaf+, and
your database context is named the same.
But what if you wanted to use that connection for other leaves too, so you named
it something like "local_database"? Now, Autumn won't be able to guess that you
want to use that DB context, so you have to specify it manually:
def fortune_command(stem, sender, reply_to, msg)
database(:local_database) do
fortunes = Fortune.all
return fortunes[rand(fortunes.size)].text
end
end
If that is too tedious, you can specify the database connection manually in the
leaves.yml file:
FortuneBot:
class: FortuneLeaf
database: local_database
OK, now onto the second special case. Imagine you want your fortune bot to also
send a fortune in response to a CTCP VERSION request. So, you'd implement a
method like so:
def ctcp_version_request(handler, stem, sender, arguments)
fortune = random_fortune # Loads a random fortune
send_ctcp_reply stem, sender[:nick], 'VERSION', fortune.text
end
This will break -- why? Because the +ctcp_version_request+ method is in the
realm of the Autumn::CTCP class, _not_ the Autumn::Leaf class. (You can see this
by investigating the CTCP class docs; it shows you what methods you can
implement for CTCP support.) Basically, the +CTCP+ class calls your method
directly, giving the Autumn::Leaf class no chance to set up the database first.
So to fix it, make a call to +database+ first:
def ctcp_version_request(handler, stem, sender, arguments)
fortune = database { random_fortune }
send_ctcp_reply stem, sender[:nick], 'VERSION', fortune.text
end
This will execute those methods in the scope of the database connection guessed
by Autumn::Leaf. Of course, you can manually pass in a connection name if
necessary.
<b>Another important note:</b> You will need to make a call to @database@ in any
child threads your leaf creates. The database context is not automatically
carried over to such threads.
=== Your Leaf's Module; or, "What Do I Do About Namespace Conflicts?"
So, if you have two database-backed leaves, it's entirely likely that both of
them will use some sort of DataMapper resource named +Channel+, or something
similar. You can't define the class +Channel+ twice in two different ways, so
how do you deal with this?
The answer is: It's already dealt with for you. Go ahead and define the class
twice. Or three times.
The longer explanation is: Secretly, behind the scenes, <b>all your leaf code is
being cleverly loaded into a module named after your leaf</b>. So, when, in your
controller.rb code, it says <tt>class Controller < Autumn::Leaf</tt>, you should
read it as <tt>class MyLeafName::Controller < Autumn::Leaf</tt>. When you define
your model with <tt>class Channel</tt>, it's really read as <tt>class
MyLeafName::Channel</tt>.
Don't worry about table names or associations or anything, either. Just go ahead
and use it as if it weren't in a module. The libs/datamapper_hacks.rb file has
all the necessary code changes to make this bit of trickery work.
=== Using Support Modules
Helper modules placed in your leaf's helpers directory will automatically be
loaded and included in your leaf controller and views. To create a helper
module, place Ruby files to be loaded into the helpers directory. Make sure your
helper modules' names end with the word "Helper".
For instance, if your leaf's name is "Fortune", and you needed two helpers, a
database helper and a network helper, you could create two modules named
+DatabaseHelper+ and +NetworkHelper+. Any modules named in this fashion and
placed in the helpers subdirectory will be loaded and appended to the
controller and its views automatically.
=== Debugging Your Leaf
If you make a simple code change to your leaf, you can reload it without having
to restart the whole process. See the Autumn::Leaf#reload_command documentation
for more information on when and how you can reload your leaf's code.
If an error occurs on a live production instance, it will be logged to the log
file for your season. You can inspect the log file to determine what went wrong.
If the error happens before the logger is available, oftentimes it will appear
in the autumn.output or autumn.log files. These files are generated by the
daemon library and note any uncaught exceptions or standard outs. They are in
the tmp directory.
The most tricky of errors can happen before the process is daemonized. If your
process is quitting prematurely, and you don't see anything in either log file,
consider running <tt>script/server</tt>, allowing you to see any exceptions for
yourself.
Unfortunately, it's still possible that the bug might not appear when you do
this, but only appear when the process is daemonized. In this situation, I'd
recommend installing rdebug (<tt>sudo gem install rdebug</tt>) and stepping
through the code to figure out what's going wrong. In particular, make sure you
step into the +Foliater+'s +start_stems+ method, when it creates the new
threads. It's possible your exception will rear its head once you step into that
line of code.
== Stems
Autumn::Stem is a full-featured IRC client library, written from the ground up
for Autumn. It makes extensive use of implicit protocols, meaning that most
features are accessed by implementing the methods you feel are necessary.
Most of the time, you will only work with stems indirectly via leaves. For
instance, if you want an "!opped" command that returns true if the sender is an
operator, it would look like this:
def opped_command(stem, sender, reply_to, msg)
stem.channel_members[reply_to][sender[:nick]] == :operator ? "You are opped." : "You are not opped."
end
Let's break this down. In order to figure out if someone is opped or not, we
need three pieces of information: their nick, the channel they are in, and the
IRC server they are connected to.
The +stem+ parameter contains the Autumn::Stem instance that received this
message. It is our link to that server. Through it we can perform IRC actions
and make requests.
Autumn::Stem includes an attribute +channel_members+, a hash of channels mapped
to their members. The channel that received the message is passed via the
+reply_to+ parameter. So we call <tt>channel_members[reply_to]</tt> and we
receive a hash of member names to their privilege levels. The +sender+ parameter
contains information about the person who sent the command, including their
nick. So we use their nick to resolve their privilege level.
Complicated? Sure it is. That's the price we pay for separating stems from
leaves. But what if you, like probably 90% of the people out there who use
Autumn, only have one stem? Why should you have to call the same damn stem each
and every time?
Fortunately, your pleas are not in vain. For leaves that run off only one stem,
the stem's methods are rolled right into the leaf. So, that "!opped" command
method becomes:
def opped_command(stem, sender, reply_to, msg)
channel_members[reply_to][sender[:nick]] == :operator ? "You are opped." : "You are not opped."
end
OK, so it's not like a world-class improvement, but it helps.
The primary thing your leaf will probably do with a Stem instance is use it to
send messages, like so:
def about_command(stem, sender, reply_to, msg)
stem.message "I am a pretty awesome bot!", reply_to
end
Fortunately, if you just return a string, Autumn::Leaf will automatically send
it for you, simplifying our method:
def about_command(stem, sender, reply_to, msg)
"I am a pretty awesome bot!"
end
You would still interact with the stem directly if you wanted to do something
like announce your leaf's presence to everyone. To do this, you'd have to send
a message to every channel of every stem the leaf is a listener for:
stems.each { |stem| stem.channels.each { |channel| stem.message "Hello!", channel } }
But! Autumn::Stem#message will automatically send a message to every channel if
you don't specify any channels, simplifying our code to:
stems.each { |stem| stem.message "Hello!" }
It gets even better. <b>You can call methods on the +stems+ array as if it were
a stem itself!</b> This simplifies the line significantly:
stems.message "Hello!"
Pretty nifty, huh? This also works for functions as well as methods; for
instance, the Autumn::Stem#ready? function, which returns true if a stem is
ready:
stems.ready? #=> [ true, true, false, true ] (for example)
=== The nitty-gritty of stems
The section above dealt with stems as they relate to leaves. But when would you
need to deal with a stem directly? Generally, never. However, if you find
that Autumn::Leaf doesn't have what you need, you may have to turn to
Autumn::Stem to get the functionality you are looking for. So let's take a look
at how Stem works.
A stem interacts with interested parties via the listener protocol. Your leaf
signals its interest to a stem by calling Autumn::Stem#add_listener. When a leaf
or any other object becomes a stem's listener, that stem then invokes methods on
the listener whenever an IRC event occurs.
Let's take a simple example. Assume you wanted to build a basic textual IRC
client using Stem. You'd first want to indicate that your client is a listener:
class MyClient
def initialize(stem)
@stem = stem
@stem.add_listener self
end
end
Now the stem will send method calls to your +MyClient+ instance every time an
IRC event occurs. None of these methods are required -- you can implement as few
or as many as you want. The different methods that Stem will send are documented
in the Autumn::Stem#add_listener method docs. One very important method is the
+irc_privmsg_event+ method. Let's implement it:
def irc_privmsg_event(stem, sender, arguments)
puts "#{arguments[:channel]} <#{sender[:nick]}> #{arguments[:message]}"
end
Now we've got the most important part of our IRC client done -- receiving
messages.
You can also send IRC events using stem. It's simple: Every IRC command (such as
JOIN and PRIVMSG and MODE) has a corresponding method in Stem (such as +join+
and +privmsg+ and +mode+). These methods aren't in the API docs because they're
implemented using +method_missing+. Their arguments are exactly the same as the
arguments the IRC command expects, and in the same order.
So how do we send a message? Well according to RFC-1459, the basic IRC spec, the
PRIVMSG command takes two arguments: a list of receivers, and the text to be
sent. So, we know our method call should look something like this:
@stem.privmsg recipient, message
Astute readers will note that the spec shows a _list_ of recipients, and indeed,
you can call the method like so:
@stem.privmsg [ recipient1, recipient2 ], message
That's the basics of how Autumn::Stem works, but there's one other thing worth
mentioning, and that's listener plugins. The details are in the
Autumn::Stem#add_listener method docs, but the short of it is that these are
special listeners that bestow their powers onto other listeners.
The best example of this is the Autumn::CTCP class. This class is indeed a Stem
listener: It listens to PRIVMSG events from the stem, and checks them to see if
they are CTCP requests. However, it _also_ gives you, the author of another
listener (such as your leaf) the ability to implement methods according to _its_
protocol.
For example, say you wanted to respond to CTCP VERSION requests with your own
version information. You do it like so:
def ctcp_version_request(handler, stem, sender, arguments)
send_ctcp_reply stem, sender[:nick], 'VERSION', "AwesomeBot 2.0 by Sancho Sample"
end
What's going on here? Because the Autumn::CTCP class is a listener plugin, it is
sending its own method calls as well as implementing Stem's method calls. One
such call is the +ctcp_version_request+ method, which you can see in the CTCP
class docs. Somewhere deep in the annals of Autumn::Foliater, there is some code
similar to the following:
ctcp = Autumn::CTCP.new
stem.add_listener ctcp
Thus, every stem comes pre-fab with a CTCP listener plugin. That plugin is
intercepting PRIVMSG events and checking if they're CTCP requests. If they are,
it is invoking methods, such as +ctcp_version_request+, in all of the stem's
other listeners, among which is your leaf. Hopefully you understand how this all
fits together.
The lesson to take home here is two-fold: Firstly, if you'd like CTCP support in
your leaf, know that it's the Autumn::CTCP class that is providing the method
calls to your leaf, not the Autumn::Stem class. Secondly, this should hopefully
give you some ideas should you want to write your own listener plugin to enhance
Stem's functionality.
== Autumn's Logging
Autumn uses Ruby's Logger class to log; however, it uses Autumn::LogFacade to
prepend additional information to each log entry. The LogFacade class has the
exact same external API as Logger, so you can use it like a typical Ruby or
Ruby on Rails logger. Many objects (such as Leaf and Stem) include a +logger+
attribute:
logger.debug "Debug statement"
logger.fatal $!
See the LogFacade class docs for details.
== Tasks
The included Rakefile contains a number of useful tasks to help you develop and
deploy your leaves. You can always get a list of tasks by typing
<tt>rake --tasks</tt>. The various commands you can run are:
Application tasks:
* <b>rake app:start</b> - Starts the Autumn daemon in the background.
* <b>rake app:stop</b> - Stops the Autumn daemon.
* <b>rake app:restart</b> - Reloads the Autumn daemons.
* <b>rake app:run</b> - Starts the Autumn daemon in the foreground.
* <b>rake app:zap</b> - Forces the daemon to a stopped state. Use this command
if your daemon is not running but script/daemon thinks it still is.
Database tasks:
* <b>LEAF=[leaf name] rake db:migrate</b> - Creates all the tables for a leaf,
as specified by the leaf's model objects.
Documentation tasks:
* <b>rake doc:api</b> - Generates HTML documentation for Autumn, found in the
doc/api directory.
* <b>rake doc:leaves</b> - Generates HTML documentation for your leaves, found
in the doc/leaves directory.
* <b>rake doc:clear</b> - Removes all HTML documentation.
Logging tasks:
* <b>rake log:clear</b> - Clears the log files for all seasons.
* <b>rake log:errors</b> - Prints a list of error-level log messages for the
current season, and uncaught exceptions in all seasons.
=== Custom leaf tasks
You can define your own leaf-specific tasks in the tasks subdirectory within
your leaf's directory. Any .rake files there will be loaded by rake. The tasks
will be added within a task-group named after your leaf. Use Scorekeeper as an
example: If you type <tt>rake --tasks</tt>, you'll see one other task,
<tt>rake scorekeeper:scores</tt>. The "scores" task is defined in the
leaves/scorekeeper/tasks/stats.rake file, and placed in the "scorekeeper" task