Skip to content
Browse files

1.0 release

  • Loading branch information...
2 parents f5dbe71 + 57ff586 commit a4866ea7456139c1159ff8fd41cbdb3766722469 Lee Jarvis committed Aug 19, 2010
Showing with 3,852 additions and 2,741 deletions.
  1. +4 −3 .gitignore
  2. +1 −0 .yardopts
  3. +1 −1 LICENSE
  4. +192 −0 README.md
  5. +0 −200 README.rdoc
  6. +54 −38 Rakefile
  7. +8 −15 cinch.gemspec
  8. +0 −32 examples/autovoice.rb
  9. +32 −0 examples/basic/autovoice.rb
  10. +35 −0 examples/basic/google.rb
  11. +15 −0 examples/basic/hello.rb
  12. +38 −0 examples/basic/join_part.rb
  13. +39 −0 examples/basic/memo.rb
  14. +16 −0 examples/basic/msg.rb
  15. +36 −0 examples/basic/seen.rb
  16. +35 −0 examples/basic/urban_dict.rb
  17. +35 −0 examples/basic/url_shorten.rb
  18. +0 −19 examples/custom_patterns.rb
  19. +0 −25 examples/custom_prefix.rb
  20. +0 −31 examples/google.rb
  21. +0 −13 examples/hello.rb
  22. +0 −26 examples/join_part.rb
  23. +0 −40 examples/memo.rb
  24. +0 −14 examples/msg.rb
  25. +0 −19 examples/named-param-types.rb
  26. +40 −0 examples/plugins/autovoice.rb
  27. +23 −0 examples/plugins/custom_prefix.rb
  28. +37 −0 examples/plugins/google.rb
  29. +22 −0 examples/plugins/hello.rb
  30. +42 −0 examples/plugins/join_part.rb
  31. +50 −0 examples/plugins/memo.rb
  32. +22 −0 examples/plugins/msg.rb
  33. +41 −0 examples/plugins/multiple_matches.rb
  34. +45 −0 examples/plugins/seen.rb
  35. +30 −0 examples/plugins/urban_dict.rb
  36. +32 −0 examples/plugins/url_shorten.rb
  37. +0 −41 examples/seen.rb
  38. +0 −31 examples/urban_dict.rb
  39. +0 −34 examples/url_shorten.rb
  40. +7 −20 lib/cinch.rb
  41. +41 −0 lib/cinch/ban.rb
  42. +0 −368 lib/cinch/base.rb
  43. +479 −0 lib/cinch/bot.rb
  44. +11 −0 lib/cinch/callback.rb
  45. +419 −0 lib/cinch/channel.rb
  46. +369 −0 lib/cinch/constants.rb
  47. +25 −0 lib/cinch/exceptions.rb
  48. +21 −0 lib/cinch/helpers.rb
  49. +344 −38 lib/cinch/irc.rb
  50. +0 −135 lib/cinch/irc/message.rb
  51. +0 −141 lib/cinch/irc/parser.rb
  52. +0 −329 lib/cinch/irc/socket.rb
  53. +96 −0 lib/cinch/isupport.rb
  54. +80 −0 lib/cinch/logger/formatted_logger.rb
  55. +44 −0 lib/cinch/logger/logger.rb
  56. +18 −0 lib/cinch/logger/null_logger.rb
  57. +46 −0 lib/cinch/mask.rb
  58. +183 −0 lib/cinch/message.rb
  59. +62 −0 lib/cinch/message_queue.rb
  60. +0 −54 lib/cinch/names.rb
  61. +205 −0 lib/cinch/plugin.rb
  62. +1 −0 lib/cinch/rubyext/infinity.rb
  63. +18 −0 lib/cinch/rubyext/module.rb
  64. +19 −0 lib/cinch/rubyext/queue.rb
  65. +24 −0 lib/cinch/rubyext/string.rb
  66. +0 −171 lib/cinch/rules.rb
  67. +55 −0 lib/cinch/syncable.rb
  68. +325 −0 lib/cinch/user.rb
  69. +0 −94 spec/base_spec.rb
  70. +5 −0 spec/bot_spec.rb
  71. +5 −0 spec/channel_spec.rb
  72. +5 −0 spec/cinch_spec.rb
  73. +0 −8 spec/irc/helper.rb
  74. +0 −61 spec/irc/message_spec.rb
  75. +0 −103 spec/irc/parser_spec.rb
  76. +0 −90 spec/irc/socket_spec.rb
  77. +5 −0 spec/irc_spec.rb
  78. +5 −0 spec/message_spec.rb
  79. +0 −393 spec/names_spec.rb
  80. +0 −45 spec/options_spec.rb
  81. +5 −0 spec/plugin_spec.rb
  82. +0 −109 spec/rules_spec.rb
  83. 0 spec/{helper.rb → spec_helper.rb}
  84. +5 −0 spec/user_spec.rb
View
7 .gitignore
@@ -1,4 +1,5 @@
-*.swp
*~
-rdoc
-*.gem
+coverage
+doc
+pkg
+.yardoc/
View
1 .yardopts
@@ -0,0 +1 @@
+--hide-void-return -m markdown --verbose
View
2 LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2010 Lee Jarvis
+Copyright (c) 2010 Lee Jarvis, Dominik Honnef
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
View
192 README.md
@@ -0,0 +1,192 @@
+Cinch - An IRC Bot Building Framework
+=====================================
+
+Description
+-----------
+
+Cinch is an IRC Bot Building Framework for quickly creating IRC bots in
+Ruby with minimal effort. It provides a simple interface based on plugins and
+rules. It's as easy as creating a plugin, defining a rule, and watching your
+profits flourish.
+
+Cinch will do all of the hard work for you, so you can spend time creating cool
+plugins and extensions to wow your internet peers.
+
+If you'd like to test your own Cinch experiments you can do so in the
+\#cinch-bots IRC channel on
+[irc.freenode.org](irc://irc.freenode.org/cinch-bots). For general
+support, join [#cinch](irc://irc.freenode.org/cinch).
+
+This original document can be found [here](http://doc.injekt.net/cinch).
+
+Installation
+------------
+
+### RubyGems
+
+You can install the latest Cinch gem using RubyGems
+
+ gem install cinch
+
+### GitHub
+
+Alternatively you can check out the latest code directly from Github
+
+ git clone http://github.com/injekt/cinch.git
+
+Example
+-------
+
+Your typical Hello, World application in Cinch would go something like this:
+
+ require 'cinch'
+
+ bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.org"
+ c.channels = ["#cinch-bots"]
+ end
+
+ on :message, "hello" do |m|
+ m.reply "Hello, #{m.user.nick}"
+ end
+ end
+
+ bot.start
+
+More examples can be found in the `examples` directory.
+
+Features
+--------
+
+### Documentation
+
+Cinch provides a documented API, which is online for your viewing pleasure [here](http://doc.injekt.net/cinch).
+
+### Object Oriented
+
+Many IRC bots (and there are, so **many**) are great, but we see so little of them take
+advantage of the awesome Object Oriented Interface which most Ruby programmers will have
+become accustomed to and grown to love.
+
+Well, Cinch uses this functionality to it's advantage. Rather than having to pass around
+a reference to a channel or a user, to another method, which then passes it to
+another method (by which time you're confused about what's going on) -- Cinch provides
+an OOP interface for even the simpliest of tasks, making your code simple and easy
+to comprehend.
+
+### Threaded
+
+Unlike a lot of popular IRC frameworks, Cinch is threaded. But wait, don't let that
+scare you. It's totally easy to grasp.
+
+Each of Cinch's plugins and handlers are executed in their own personal thread. This
+means the main thread can stay focused on what it does best, providing non-blocking
+reading and writing to an IRC server. This will prevent your bot from locking up
+when one of your plugins starts doing some intense operations. Damn that's handy.
+
+### Key/Value Store
+
+We have listened to your requests and implemented a bot-wide key/value store. You can
+now store data and use it across your handlers. Here's an example:
+
+ configure do |c|
+ store[:friends] = []
+ end
+
+ on :message, /^add friend (.+)$/ do |m, friend|
+ store[:friends] << friend
+ end
+
+ on :message /^get friends$/ do |m|
+ m.reply "Your friends are: #{store[:friends].join(', ')}"
+ end
+
+Neat, right?
+
+### Plugins
+
+That's right folks, Cinch provides a modular based plugin system. This is a feature
+many people have bugged us about for a long time. It's finally here, and it's
+as awesome as you had hoped!
+
+This system allows you to create feature packed plugins without interfering with
+any of the Cinch internals. Everything in your plugin is self contained, meaning
+you can share your favorite plugins among your friends and release a ton of
+your own plugins for others to use
+
+Want to see the same Hello, World application in plugin form? Sure you do!
+
+ require 'cinch'
+
+ class Hello
+ include Cinch::Plugin
+
+ match "hello"
+
+ def execute(m)
+ m.reply "Hello, #{m.user.nick}"
+ end
+ end
+
+ bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.org"
+ c.channels = ["#cinch-bots"]
+ c.plugins.plugins = [Hello]
+ end
+ end
+
+ bot.start
+
+More information can be found in the {Cinch::Plugin} documentation.
+
+### Numeric Replies
+
+Do you know what IRC code 401 represents? How about 376? or perhaps 502?
+Sure you don't (and if you do, you're as geeky as us!). Cinch doesn't expect you
+to store the entire IRC RFC code set in your head, and rightfully so!
+
+That's exactly why Cinch has a ton of constants representing these numbers
+so you don't have to remember them. We're so nice.
+
+### Pretty Output
+
+Ever get fed up of watching those boring, frankly unreadable lines
+flicker down your terminal screen whilst your bot is online? Help is
+at hand! By default, Cinch will colorize all text it sends to a
+terminal, meaning you get some pretty damn awesome readable coloured
+text. Cinch also provides a way for your plugins to log custom
+messages:
+
+ on :message, /hello/ do |m|
+ bot.logger.debug "Someone said hello"
+ end
+
+Authors
+-------
+
+* [Lee Jarvis](http://injekt.net)
+* [Dominik Honnef](http://fork-bomb.org)
+
+Contribute
+----------
+
+Love Cinch? Love Ruby? Love helping? Of course you do! If you feel like Cinch
+is missing that awesome jaw-dropping feature and you want to be the one to
+make this magic happen, you can!
+
+Please note that although we very much appreciate all of your efforts, Cinch
+will not accept patches in aid of Ruby 1.8 compatibility. We have no intention
+of supporting Ruby versions below 1.9.1.
+
+Fork the project, implement your awesome feature in it's own branch, and send
+a pull request to one of the Cinch collaborators. We'll be more than happy
+to check it out.
+
+Just remember, no specs, no cookies!
+
+### Contributors
+- darix &lt;darix [at] nordisch.org&gt; (wrote the message splitting algorithm)
+- robgleeson (thanks for testing, contributing a lot of ideas,
+ discussing design decisions etc)
View
200 README.rdoc
@@ -1,200 +0,0 @@
-= Cinch: The IRC Bot Building Framework
-
-== ALERT
-Cinch will soon be undergoing a huge update. We're officially merging with the Newton IRC library. This means there are likely to be some incompatible changes coming soon. Please stay updated. Here's a {blog post}[http://blog.injekt.net/19/cinch-and-newton-join-forces] on this.
-
-Stay tuned!
-
-== Description
-Cinch is an IRC Bot Building Framework for quickly creating IRC bots
-in Ruby with minimal effort.
-It provides a minimal interface based on plugins and rules. It's as simple as creating a
-plugin, defining a rule, and watching your profits flourish.
-
-Cinch will do all of the hard work for you, so you can spend time creating cool plugins
-and extensions to wow your internet peers.
-
-If you'd like to test your own Cinch experiments you can do so in the cinch IRC channel
-on {irc.freenode.org}[irc://irc.freenode.org/cinch]. Support is also welcome here.
-
-== Installation
-
-=== RubyGems
-You can install the latest version of Cinch using RubyGems
- gem install cinch
-
-=== GitHub
-Alternatively you can check out the latest code directly from Github
- git clone http://github.com/injekt/cinch.git
-
-== Example
-Your typical <em>Hello, World</em> application would go something like this:
-
- require 'cinch'
-
- bot = Cinch.setup do
- server "irc.freenode.org"
- nick "Cinch"
- end
-
- bot.plugin "hello" do |m|
- m.reply "Hello, #{m.nick}!"
- end
-
- bot.run
-
-It doesn't take much to work out what's happening here, but I'll explain it anyway.
-
-First we run the <em>Cinch::setup</em> block which is required in every application. Cinch is boxed
-with a load of default values, so the <b>only</b> option required in this block is the server.
-
-We then define a plugin using the <em>plugin</em> method and pass it a rule (a String in this
-case). Every plugin must be mapped to a rule. When the rule matches an IRC message its block is
-invoked, the contents of which contains your plugin interface. The variable passed to the block is
-an instance of Cinch::IRC::Message.
-
-Cinch::IRC::Message also supplies us with some helper methods which aid in replying to users and
-channels.
-
-=== See Also
-* Cinch::IRC::Message#reply
-* Cinch::IRC::Message#answer
-
-This example would provide the following response on IRC:
-
- * Cinch has joined #cinch
- injekt> !hello
- Cinch> Hello, injekt!
-
-Since Cinch doesn't provide a binary executable, running your application is as simple as you would any
-other Ruby script.
-
- ruby hello.rb
-
-Cinch also parses the command line for options, to save you having to configure
-options within your script.
-
- ruby hello.rb -s irc.freenode.org -n Coolbot
- ruby hello.rb -C foo,bar
-
-Doing a <b>ruby hello.rb -h</b> provides all possible command line options.
-
-When using the <em>-C</em> or <em>--channels</em> option, the channel prefix is
-optional, and if none is given the channel will be prefixed with a hash (#) character.
-
-== Plugins
-Plugins are invoked using the command prefix character (which by default is set to <b>!</b>). You can
-also tell Cinch to ignore any command prefix and instead use the bots username. This would provide
-a result similar to this:
-
- injekt> Cinch: hello
- Cinch> Hello, injekt!
-
-Cinch also provides named parameters. This method of expression was inspired by the {Sinatra
-Web Framework}[http://www.sinatrarb.com/] and although it doesn't quite follow the same pattern,
-it's useful for naming parameters passed to plugins. These paramaters are available through the
-Cinch::IRC::Message#args method which is passed to each plugin.
-
- bot.plugin("say :text") do |m|
- m.reply m.args[:text]
- end
-
-This plugin would provide the following output:
-
- injekt> !say foo bar baz
- Cinch> foo bar baz
-
-Each plugin takes an optional hash of message specific options. These options provide an extension to
-the rules given, for example if we want to reply only if the nick sending the message is injekt, we
-could pass the 'nick' option to the hash.
-
- bot.plugin("join :channel", :nick => 'injekt') do |m|
- bot.join #{m.args[:channel]}
- end
-
-This method also works for arrays, to only reply to a message sent in the foo and bar channels
-
- bot.plugin :hello, :channel => ['#foo', '#bar'] do |m|
- m.reply "Hello"
- end
-
-You can also set a custom prefix for each individual plugin, this is a great method if you have
-two commands which do slightly different things. You can seperate the commands depending on which
-prefix the rule contains.
-
- bot.plugin "foo", :prefix => '@' do |m|
- m.reply "Doing foo.."
- end
-
-You can also prefix the rule with the bots nickname. Either pass the <b>:bot</b>, <b>:botnick</b> or
-<b>bot.nick</b> values to the prefix option.
-
- bot.plugin "foo", :prefix => :bot do |m|
- m.reply "Doing foo.."
- end
-
-Assuming the username is cinch, this will respond to the following:
-* cinch: foo
-* cinch, foo
-
-More examples of this can be found in the /examples directory
-
-== Named Parameter Patterns
-Since version 0.2, Cinch supports named parameter patterns. It means stuff like the this works:
-
- bot.plugin("say :n-digit :text") do |m|
- m.args[:n].to_i.times do
- m.reply m.args[:text]
- end
- end
-
-This would provide the following output on IRC
-
- injekt> !say foo bar
- injekt> !say 2 foo bar
- Cinch> foo bar
- Cinch> foo bar
-
-* See Cinch::Base#compile for more information and the available patterns
-
-Cinch also supports custom named parameter patterns. That's right, you can define you own
-pattern. Just like this:
-
- bot.add_custom_pattern(:friends, /injekt|lee|john|bob/)
-
- bot.plugin("I like :friend-friends", :prefix => false) do |m|
- m.reply "I like #{m.args[:friend]} too!"
- end
-
-Which would provide the following output on IRC:
-
- * Cinch has joined #cinch
- injekt> I like spongebob
- injekt> I like bob
- Cinch> I like bob too!
-
-Note though that although Cinch adds the capturing parenthesis for you, you must escape it yourself
-
-== Examples
-Check out the /examples directory for basic, yet fully functional out-of-the-box bots.
-If you have any examples you'd like to add, please either fork the repo and push your example
-before sending me a pull request. Alternatively paste the example and inform me in the IRC
-channel or by email
-
-== Authors
-* {Lee Jarvis}[http://injekt.net]
-
-== Notes
-* RDoc API documentation is available {here}[http://doc.injekt.net/cinch]
-* Issue and feature tracking is available {here}[https://github.com/injekt/cinch/issues]
-* Contribution in the form of bugfixes or feature requests is welcome and encouraged
-
-== Contribute
-If you'd like to contribute, fork the GitHub repository, make any changes, and send
-{injekt}[http://github.com/injekt] a pull request. Collaborator access is available on
-request once one patch has been submitted. Any contribution is welcome and appreciated
-
-== TODO
-* More specs
-* More documentation
-
View
92 Rakefile
@@ -1,50 +1,66 @@
-require "rake"
-require "rake/clean"
-require "rake/gempackagetask"
-require "spec/rake/spectask"
-
-require 'lib/cinch'
-
-NAME = 'cinch'
-VERSION = Cinch::VERSION
-TITLE = "Cinch: The IRC Bot Building Framework"
-CLEAN.include ["*.gem", "rdoc"]
-
-require 'hanna'
-require 'rdoc/task'
-RDoc::Task.new do |rdoc|
- rdoc.options.push '-f', 'hanna'
- rdoc.main = 'README.rdoc'
- rdoc.rdoc_dir = 'rdoc'
- rdoc.title = TITLE
- rdoc.rdoc_files.include('README.rdoc')
- rdoc.rdoc_files.include('lib/**/*.rb')
-end
+require 'rubygems'
+require 'rake'
+require 'rake/clean'
+
+$LOAD_PATH.unshift('lib') unless $LOAD_PATH.include?('lib')
+require 'cinch'
-desc "Package"
-task :package => [:clean] do |p|
- sh "gem build #{NAME}.gemspec"
+CLEAN.include ["doc"]
+
+require 'spec/rake/spectask'
+Spec::Rake::SpecTask.new(:spec) do |spec|
+ spec.libs << 'lib' << 'spec'
+ spec.spec_files = FileList['spec/**/*_spec.rb']
end
-desc "Install gem"
-task :install => [:package] do
- sh "sudo gem install ./#{NAME}-#{VERSION} --local"
+require 'yard'
+YARD::Rake::YardocTask.new do |t|
+ t.files = ['lib/**/*.rb', 'README.md']
+ t.options = [
+ '-m', 'markdown',
+ '--hide-void-return',
+ '--quiet',
+ '--title', "Cinch #{Cinch::VERSION} Documentation",
+ '--main', 'README.md',
+ ]
end
-desc "Uninstall gem"
-task :uninstall => [:clean] do
- sh "sudo gem uninstall #{NAME}"
+namespace :gem do
+ desc "Build gem"
+ task :build => ["rake:clean"] do
+ sh("gem build cinch.gemspec")
+ end
+
+ desc "Uninstall gem (not root)"
+ task :uninstall do
+ sh("gem uninstall cinch -v #{Cinch::VERSION}")
+ end
+
+ desc "Release to rubygems"
+ task :release => [:build] do
+ sh("gem push ./cinch-#{Cinch::VERSION}.gem")
+ end
+
+ desc "Install gem (not root)"
+ task :install => :build do
+ sh("gem install ./cinch-#{Cinch::VERSION} --local")
+ end
end
-desc "Upload gem to gemcutter"
-task :release => [:package] do
- sh "gem push ./#{NAME}-#{VERSION}.gem"
+namespace :doc do
+ desc "Upload documentation"
+ task :push => [:yard] do
+ # XXX rename once merge is complete
+ sh("scp -r doc injekt:injekt.net/doc/cinch-merge")
+ end
end
-desc "Run all specs"
-Spec::Rake::SpecTask.new(:spec) do |t|
- t.spec_files = Dir['spec/**/*_spec.rb']
+task :version do
+ puts "Cinch version #{Cinch::VERSION}"
end
-task :default => [:clean, :spec]
+desc "Install gem (not root)"
+task :install => "gem:install"
+
+task :default => :spec
View
23 cinch.gemspec
@@ -1,20 +1,13 @@
-require File.expand_path("../lib/cinch", __FILE__)
-
spec = Gem::Specification.new do |s|
s.name = 'cinch'
- s.version = Cinch::VERSION
- s.platform = Gem::Platform::RUBY
- s.has_rdoc = true
- s.extra_rdoc_files = ["README.rdoc"]
- s.rdoc_options += ["--quiet", '--title', 'Cinch: The IRC Bot Building Framework', '--main', 'README.rdoc']
- s.summary = "An IRC Bot Building Framework"
- s.description = s.summary
- s.author = "Lee 'injekt' Jarvis"
- s.email = "ljjarvis@gmail.com"
- s.homepage = "http://doc.injekt.net/cinch"
- s.required_ruby_version = ">= 1.8.7"
- s.files = %w(README.rdoc Rakefile) + Dir["{rdoc,spec,lib,examples}/**/*"]
- s.require_path = "lib"
+ s.version = "1.0.0"
+ s.summary = 'An IRC Bot Building Framework'
+ s.description = 'A simple, friendly DSL for creating IRC bots'
+ s.authors = ['Lee Jarvis', 'Dominik Honnef']
+ s.email = ['lee@jarvis.co', 'dominikh@fork-bomb.org']
+ s.homepage = 'http://doc.injekt.net/cinch'
+ s.required_ruby_version = '>= 1.9.1'
+ s.files = Dir['LICENSE', 'Rakefile', 'README.md', '{spec,lib,examples}/**/*']
s.add_development_dependency('rspec', '= 1.3.0')
end
View
32 examples/autovoice.rb
@@ -1,32 +0,0 @@
-require 'cinch'
-
-# Give this bot ops in a channel and it'll auto voice
-# visitors
-#
-# Enable with !autovoice on
-# Disable with !autovoice off
-
-bot = Cinch.setup do
- server "irc.freenode.org"
- channels %w( #cinch )
-end
-
-autovoice = true
-
-bot.on :join do |m|
- unless m.nick == bot.options.nick # We shouldn't attempt to voice ourselves
- bot.mode(m.channel, '+v', m.nick) if autovoice
- end
-end
-
-bot.add_custom_pattern(:onoff, '(on|off)')
-
-bot.plugin "autovoice :option-onoff" do |m|
- case m.args[:option]
- when 'on'; autovoice = true
- when 'off'; autovoice = false
- end
- m.answer "Autovoice is now #{autovoice ? 'enabled' : 'disabled'}"
-end
-
-bot.run
View
32 examples/basic/autovoice.rb
@@ -0,0 +1,32 @@
+require 'cinch'
+
+# Give this bot ops in a channel and it'll auto voice
+# visitors
+#
+# Enable with !autovoice on
+# Disable with !autovoice off
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.nick = "cinch_autovoice"
+ c.server = "irc.freenode.org"
+ c.verbose = true
+ c.channels = ["#cinch-bots"]
+
+ @autovoice = true
+ end
+
+ on :join do |m|
+ unless m.user.nick == bot.nick # We shouldn't attempt to voice ourselves
+ m.channel.voice(m.user) if @autovoice
+ end
+ end
+
+ on :channel, /^!autovoice (on|off)/ do |m, option|
+ @autovoice = option == "on"
+
+ m.reply "Autovoice is now #{@autovoice ? 'enabled' : 'disabled'}"
+ end
+end
+
+bot.start
View
35 examples/basic/google.rb
@@ -0,0 +1,35 @@
+require 'cinch'
+require 'open-uri'
+require 'nokogiri'
+require 'cgi'
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.net"
+ c.nick = "MrCinch"
+ c.channels = ["#cinch-bots"]
+ end
+
+ helpers do
+ # Extremely basic method, grabs the first result returned by Google
+ # or "No results found" otherwise
+ def google(query)
+ url = "http://www.google.com/search?q=#{CGI.escape(query)}"
+ res = Nokogiri::HTML(open(url)).at("h3.r")
+
+ title = res.text
+ link = res.at('a')[:href]
+ desc = res.at("./following::div").children.first.text
+ rescue
+ "No results found"
+ else
+ CGI.unescape_html "#{title} - #{desc} (#{link})"
+ end
+ end
+
+ on :message, /^!google (.+)/ do |m, query|
+ m.reply google(query)
+ end
+end
+
+bot.start
View
15 examples/basic/hello.rb
@@ -0,0 +1,15 @@
+require 'cinch'
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.org"
+ c.channels = ["#cinch-bots"]
+ end
+
+ on :message, "hello" do |m|
+ m.reply "Hello, #{m.user.nick}"
+ end
+end
+
+bot.start
+
View
38 examples/basic/join_part.rb
@@ -0,0 +1,38 @@
+require 'cinch'
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.org"
+ c.nick = "CinchBot"
+ c.channels = ["#cinch-bots"]
+
+ # Who should be able to access these plugins
+ @admin = "injekt"
+ end
+
+ on :connect do
+ bot.join "#cinch"
+ end
+
+ helpers do
+ def is_admin?(user)
+ true if user.nick == @admin
+ end
+ end
+
+ on :message, /^!join (.+)/ do |m, channel|
+ bot.join(channel) if is_admin?(m.user)
+ end
+
+ on :message, /^!part(?: (.+))?/ do |m, channel|
+ # Part current channel if none is given
+ channel = channel || m.channel
+
+ if channel
+ bot.part(channel) if is_admin?(m.user)
+ end
+ end
+end
+
+bot.start
+
View
39 examples/basic/memo.rb
@@ -0,0 +1,39 @@
+#!/usr/bin/env ruby
+
+require 'cinch'
+
+class Memo < Struct.new(:nick, :channel, :text, :time)
+ def to_s
+ "[#{time.asctime}] <#{channel}/#{nick}> #{text}"
+ end
+end
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.org"
+ c.channels = ["#cinch-bots"]
+
+ @memos = {}
+ end
+
+ on :message do |m|
+ if @memos.has_key?(m.user.nick)
+ m.user.send @memos.delete(m.user.nick).to_s
+ end
+ end
+
+ on :message, /^!memo (.+?) (.+)/ do |m, nick, message|
+ if @memos.key?(nick)
+ m.reply "There's already a memo for #{nick}. You can only store one right now"
+ elsif nick == m.user.nick
+ m.reply "You can't leave memos for yourself.."
+ elsif nick == bot.nick
+ m.reply "You can't leave memos for me.."
+ else
+ @memos[nick] = Memo.new(m.user.nick, m.channel, message, Time.now)
+ m.reply "Added memo for #{nick}"
+ end
+ end
+end
+
+bot.start
View
16 examples/basic/msg.rb
@@ -0,0 +1,16 @@
+require 'cinch'
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.org"
+ c.nick = "CinchBot"
+ c.channels = ["#cinch-bots"]
+ end
+
+ on :message, /^!msg (.+?) (.+)/ do |m, who, text|
+ User(who).send text
+ end
+end
+
+bot.start
+
View
36 examples/basic/seen.rb
@@ -0,0 +1,36 @@
+require 'cinch'
+
+class Seen < Struct.new(:who, :where, :what, :time)
+ def to_s
+ "[#{time.asctime}] #{who} was seen in #{where} saying #{what}"
+ end
+end
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = 'irc.freenode.org'
+ c.channels = ["#cinch-bots"]
+
+ @users = {}
+ end
+
+ # Only log channel messages
+ on :channel do |m|
+ @users[m.user.nick] = Seen.new(m.user.nick, m.channel, m.message, Time.new)
+ end
+
+ on :channel, /^!seen (.+)/ do |m, nick|
+ if nick == bot.nick
+ m.reply "That's me!"
+ elsif nick == m.user.nick
+ m.reply "That's you!"
+ elsif @users.key?(nick)
+ m.reply @users[nick].to_s
+ else
+ m.reply "I haven't seen #{nick}"
+ end
+ end
+end
+
+bot.start
+
View
35 examples/basic/urban_dict.rb
@@ -0,0 +1,35 @@
+require 'cinch'
+require 'open-uri'
+require 'nokogiri'
+require 'cgi'
+
+# This bot connects to urban dictionary and returns the first result
+# for a given query, replying with the result directly to the sender
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.net"
+ c.nick = "MrCinch"
+ c.channels = ["#cinch-bots"]
+ end
+
+ helpers do
+ # This method assumes everything will go ok, it's not the best method
+ # of doing this *by far* and is simply a helper method to show how it
+ # can be done.. it works!
+ def urban_dict(query)
+ url = "http://www.urbandictionary.com/define.php?term=#{CGI.escape(query)}"
+ CGI.unescape_html Nokogiri::HTML(open(url)).at("div.definition").text.gsub(/\s+/, ' ') rescue nil
+ end
+ end
+
+ on :message, /^!urban (.+)/ do |m, term|
+ m.reply(urban_dict(term) || "No results found", true)
+ end
+end
+
+bot.start
+
+# injekt> !urban cinch
+# MrCinch> injekt: describing an action that's extremely easy.
+
View
35 examples/basic/url_shorten.rb
@@ -0,0 +1,35 @@
+require 'open-uri'
+require 'cinch'
+
+# Automatically shorten URL's found in messages
+# Using the tinyURL API
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.org"
+ c.channels = ["#cinch-bots"]
+ end
+
+ helpers do
+ def shorten(url)
+ url = open("http://tinyurl.com/api-create.php?url=#{URI.escape(url)}").read
+ url == "Error" ? nil : url
+ rescue OpenURI::HTTPError
+ nil
+ end
+ end
+
+ on :channel do |m|
+ urls = URI.extract(m.message, "http")
+
+ unless urls.empty?
+ short_urls = urls.map {|url| shorten(url) }.compact
+
+ unless short_urls.empty?
+ m.reply short_urls.join(", ")
+ end
+ end
+ end
+end
+
+bot.start
View
19 examples/custom_patterns.rb
@@ -1,19 +0,0 @@
-require 'cinch'
-
-bot = Cinch.setup do
- server "irc.freenode.org"
- channels %w{ #cinch }
-end
-
-bot.add_custom_pattern(:friends, /injekt|lee|john|bob/)
-bot.add_custom_pattern(:hex, /[\dA-Fa-f]+/)
-
-bot.plugin("I like :person-friends", :prefix => false) do |m|
- m.reply "I like #{m.args[:person]} too!"
-end
-
-bot.plugin("checkhex :n-hex") do |m|
- m.answer "Yes, #{m.args[:n]} is indeed hex."
-end
-
-bot.run
View
25 examples/custom_prefix.rb
@@ -1,25 +0,0 @@
-require 'cinch'
-
-bot = Cinch.setup do
- server "irc.freenode.org"
- channels %w( #cinch )
-end
-
-bot.plugin "default" do |m|
- m.reply "default prefix"
-end
-
-bot.plugin "custom", :prefix => '@' do |m|
- m.reply "custom prefix"
-end
-
-bot.plugin "botnick", :prefix => :botnick do |m|
- m.reply "botnick prefix"
-end
-
-bot.plugin "botnick2", :prefix => bot.nick do |m|
- m.reply "another botnick prefix"
-end
-
-bot.run
-
View
31 examples/google.rb
@@ -1,31 +0,0 @@
-require 'cinch'
-require 'open-uri'
-require 'nokogiri'
-require 'cgi'
-
-bot = Cinch.setup do
- server "irc.freenode.net"
- nick "MrCinch"
- channels %w/ #cinch /
-end
-
-# Extremely basic method, grabs the first result returned by Google
-# or "No results found" otherwise
-def google(query)
- url = "http://www.google.com/search?q=#{CGI.escape(query)}"
- res = Nokogiri::HTML(open(url)).at("h3.r")
-
- title = res.text
- link = res.at('a')[:href]
- desc = res.at("./following::div").children.first.text
-rescue
- "No results found"
-else
- CGI.unescape_html "#{title} - #{desc} (#{link})"
-end
-
-bot.plugin("google :query") do |m|
- m.reply google(m.args[:query])
-end
-
-bot.run
View
13 examples/hello.rb
@@ -1,13 +0,0 @@
-require 'cinch'
-
-bot = Cinch.setup do
- server "irc.freenode.org"
- channels %w( #cinch )
-end
-
-bot.plugin "hello" do |m|
- m.reply "Hello, #{m.nick}!"
-end
-
-bot.run
-
View
26 examples/join_part.rb
@@ -1,26 +0,0 @@
-require 'cinch'
-
-bot = Cinch.setup do
- server "irc.freenode.org"
- nick "CinchBot"
- channels %w( #cinch )
-end
-
-# Who should be able to access these plugins
-admin = 'injekt'
-
-bot.plugin "join :channel", :nick => admin do |m|
- bot.join m.args[:channel]
-end
-
-bot.plugin "part :channel", :nick => admin do |m|
- bot.part m.args[:channel]
-end
-
-# Part current channel if none is given
-bot.plugin "part", :nick => admin do |m|
- bot.part m.channel
-end
-
-bot.run
-
View
40 examples/memo.rb
@@ -1,40 +0,0 @@
-#!/usr/bin/env ruby
-
-require 'cinch'
-
-bot = Cinch.setup do
- server "irc.freenode.org"
- channels %w( #cinch )
-end
-
-class Memo < Struct.new(:nick, :channel, :text, :time)
- def to_s
- "[#{time.asctime}] <#{channel}/#{nick}> #{text}"
- end
-end
-
-@memos = {}
-
-bot.on :privmsg do |m|
- if @memos.has_key?(m.nick)
- bot.privmsg m.nick, @memos[m.nick].to_s
- @memos.delete(m.nick)
- end
-end
-
-bot.plugin("memo :nick-string :memo") do |m|
- nick = m.args[:nick]
-
- if @memos.key?(nick)
- m.reply "There's already a memo #{nick}. You can only store one right now"
- elsif nick == m.nick
- m.reply "You can't leave memos for yourself.."
- elsif nick == bot.options.nick
- m.reply "You can't leave memos for me.."
- else
- @memos[nick] = Memo.new(m.nick, m.channel, m.args[:memo], Time.new)
- m.reply "Added memo for #{nick}"
- end
-end
-
-bot.run
View
14 examples/msg.rb
@@ -1,14 +0,0 @@
-require 'cinch'
-
-bot = Cinch.setup do
- server "irc.freenode.org"
- nick "CinchBot"
- channels %w/ #cinch /
-end
-
-bot.plugin("msg :who :text") do |m|
- bot.privmsg m.args[:who], m.args[:text]
-end
-
-bot.run
-
View
19 examples/named-param-types.rb
@@ -1,19 +0,0 @@
-require 'cinch'
-
-bot = Cinch.setup do
- server "irc.freenode.org"
- channels %w( #cinch )
-end
-
-bot.plugin("say :n-digit :text") do |m|
- m.args[:n].to_i.times {
- m.reply m.args[:text]
- }
-end
-
-bot.plugin("say :text-word :rest") do |m|
- stuff = [m.args[:text], m.args[:rest]].join(' ')
- m.reply stuff
-end
-
-bot.run
View
40 examples/plugins/autovoice.rb
@@ -0,0 +1,40 @@
+require 'cinch'
+
+# Give this bot ops in a channel and it'll auto voice
+# visitors
+#
+# Enable with !autovoice on
+# Disable with !autovoice off
+
+class Autovoice
+ include Cinch::Plugin
+ listen_to :join
+ match /autovoice (on|off)/
+
+ def listen(m)
+ unless m.user.nick == bot.nick
+ m.channel.voice(m.user) if @autovoice
+ end
+ end
+
+ def execute(m, option)
+ @autovoice = option == "on"
+
+ m.reply "Autovoice is now #{@autovoice ? 'enabled' : 'disabled'}"
+ end
+end
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.nick = "cinch_autovoice"
+ c.server = "irc.freenode.org"
+ c.verbose = true
+ c.plugins.plugins = [Autovoice]
+ end
+
+ on :connect do
+ bot.join "#cinch"
+ end
+end
+
+bot.start
View
23 examples/plugins/custom_prefix.rb
@@ -0,0 +1,23 @@
+require 'cinch'
+
+class SomeCommand
+ include Cinch::Plugin
+
+ prefix "~"
+ match "somecommand"
+
+ def execute(m)
+ m.reply "Successful"
+ end
+end
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.org"
+ c.channels = ["#cinch-bots"]
+ c.plugins.plugins = [SomeCommand]
+ end
+end
+
+bot.start
+
View
37 examples/plugins/google.rb
@@ -0,0 +1,37 @@
+require 'cinch'
+require 'open-uri'
+require 'nokogiri'
+require 'cgi'
+
+class Google
+ include Cinch::Plugin
+ match /google (.+)/
+
+ def search(query)
+ url = "http://www.google.com/search?q=#{CGI.escape(query)}"
+ res = Nokogiri::HTML(open(url)).at("h3.r")
+
+ title = res.text
+ link = res.at('a')[:href]
+ desc = res.at("./following::div").children.first.text
+ rescue
+ "No results found"
+ else
+ CGI.unescape_html "#{title} - #{desc} (#{link})"
+ end
+
+ def execute(m, query)
+ m.reply(search(query))
+ end
+end
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.net"
+ c.nick = "MrCinch"
+ c.channels = ["#cinch-bots"]
+ c.plugins.plugins = [Google]
+ end
+end
+
+bot.start
View
22 examples/plugins/hello.rb
@@ -0,0 +1,22 @@
+require 'cinch'
+
+class Hello
+ include Cinch::Plugin
+
+ match "hello"
+
+ def execute(m)
+ m.reply "Hello, #{m.user.nick}"
+ end
+end
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.org"
+ c.channels = ["#cinch-bots"]
+ c.plugins.plugins = [Hello]
+ end
+end
+
+bot.start
+
View
42 examples/plugins/join_part.rb
@@ -0,0 +1,42 @@
+require 'cinch'
+
+class JoinPart
+ include Cinch::Plugin
+
+ match /join (.+)/, method: :join
+ match /part(?: (.+))?/, method: :part
+
+ def initialize(*args)
+ super
+
+ @admins = ["injekt", "DominikH"]
+ end
+
+ def check_user(user)
+ user.refresh # be sure to refresh the data, or someone could steal
+ # the nick
+ @admins.include?(user.authname)
+ end
+
+ def join(m, channel)
+ return unless check_user(m.user)
+ Channel(channel).join
+ end
+
+ def part(m, channel)
+ return unless check_user(m.user)
+ channel ||= m.channel
+ Channel(channel).part if channel
+ end
+end
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.org"
+ c.nick = "CinchBot"
+ c.channels = ["#cinch-bots"]
+ c.plugins.plugins = [JoinPart]
+ end
+end
+
+bot.start
View
50 examples/plugins/memo.rb
@@ -0,0 +1,50 @@
+#!/usr/bin/env ruby
+
+require 'cinch'
+
+class Memo
+ class MemoStruct < Struct.new(:nick, :channel, :text, :time)
+ def to_s
+ "[#{time.asctime}] <#{channel}/#{nick}> #{text}"
+ end
+ end
+
+ include Cinch::Plugin
+
+ listen_to :message
+ match /memo (.+?) (.+)/
+
+ def initialize(*args)
+ super
+ @memos = {}
+ end
+
+ def listen(m)
+ if @memos.has_key?(m.user.nick)
+ m.user.send @memos.delete(m.user.nick).to_s
+ end
+ end
+
+ def execute(m, nick, message)
+ if @memos.key?(nick)
+ m.reply "There's already a memo for #{nick}. You can only store one right now"
+ elsif nick == m.user.nick
+ m.reply "You can't leave memos for yourself.."
+ elsif nick == bot.nick
+ m.reply "You can't leave memos for me.."
+ else
+ @memos[nick] = MemoStruct.new(m.user.nick, m.channel, message, Time.now)
+ m.reply "Added memo for #{nick}"
+ end
+ end
+end
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.org"
+ c.channels = ["#cinch-bots"]
+ c.plugins.plugins = [Memo]
+ end
+end
+
+bot.start
View
22 examples/plugins/msg.rb
@@ -0,0 +1,22 @@
+require 'cinch'
+
+class Messenger
+ include Cinch::Plugin
+
+ match /msg (.+?) (.+)/
+ def execute(m, receiver, message)
+ User(receiver).send(message)
+ end
+end
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.org"
+ c.nick = "CinchBot"
+ c.channels = ["#cinch-bots"]
+ c.plugins.plugins = [Messenger]
+ end
+end
+
+bot.start
+
View
41 examples/plugins/multiple_matches.rb
@@ -0,0 +1,41 @@
+require 'cinch'
+
+# Give this bot ops in a channel and it'll auto voice
+# visitors
+#
+# Enable with !autovoice on
+# Disable with !autovoice off
+
+class MultiCommands
+ include Cinch::Plugin
+ match /command1 (.+)/, method: :command1
+ match /command2 (.+)/, method: :command2
+ match /^command3 (.+)/, use_prefix: false
+
+ def command1(m, arg)
+ m.reply "command1, arg: #{arg}"
+ end
+
+ def command2(m, arg)
+ m.reply "command2, arg: #{arg}"
+ end
+
+ def execute(m, arg)
+ m.reply "command3, arg: #{arg}"
+ end
+end
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.nick = "cinch_multi"
+ c.server = "irc.freenode.org"
+ c.verbose = true
+ c.plugins.plugins = [MultiCommands]
+ end
+
+ on :connect do
+ bot.join "#dominikh"
+ end
+end
+
+bot.start
View
45 examples/plugins/seen.rb
@@ -0,0 +1,45 @@
+require 'cinch'
+
+class Seen
+ class SeenStruct < Struct.new(:who, :where, :what, :time)
+ def to_s
+ "[#{time.asctime}] #{who} was seen in #{where} saying #{what}"
+ end
+ end
+
+ include Cinch::Plugin
+ listen_to :channel
+ match /seen (.+)/
+
+ def initialize(*args)
+ super
+ @users = {}
+ end
+
+ def listen(m)
+ @users[m.user.nick] = SeenStruct.new(m.user, m.channel, m.message, Time.now)
+ end
+
+ def execute(m, nick)
+ if nick == @bot.nick
+ m.reply "That's me!"
+ elsif nick == m.user.nick
+ m.reply "That's you!"
+ elsif @users.key?(nick)
+ m.reply @users[nick].to_s
+ else
+ m.reply "I haven't seen #{nick}"
+ end
+ end
+end
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = 'irc.freenode.org'
+ c.channels = ["#cinch-bots"]
+ c.plugins.plugins = [Seen]
+ end
+end
+
+bot.start
+
View
30 examples/plugins/urban_dict.rb
@@ -0,0 +1,30 @@
+require 'cinch'
+require 'open-uri'
+require 'nokogiri'
+require 'cgi'
+
+class UrbanDictionary
+ include Cinch::Plugin
+
+ match /urban (.+)/
+ def lookup(word)
+ url = "http://www.urbandictionary.com/define.php?term=#{CGI.escape(word)}"
+ CGI.unescape_html Nokogiri::HTML(open(url)).at("div.definition").text.gsub(/\s+/, ' ') rescue nil
+ end
+
+ def execute(m, word)
+ m.reply(lookup(word) || "No results found", true)
+ end
+end
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.net"
+ c.nick = "MrCinch"
+ c.channels = ["#cinch-bots"]
+ c.plugins.plugins = [UrbanDictionary]
+ end
+end
+
+bot.start
+
View
32 examples/plugins/url_shorten.rb
@@ -0,0 +1,32 @@
+require 'open-uri'
+require 'cinch'
+
+class TinyURL
+ include Cinch::Plugin
+
+ listen_to :channel
+
+ def shorten(url)
+ url = open("http://tinyurl.com/api-create.php?url=#{URI.escape(url)}").read
+ url == "Error" ? nil : url
+ rescue OpenURI::HTTPError
+ nil
+ end
+
+ def listen(m)
+ urls = URI.extract(m.message, "http")
+ short_urls = urls.map { |url| shorten(url) }.compact
+ unless short_urls.empty?
+ m.reply short_urls.join(", ")
+ end
+ end
+end
+
+bot = Cinch::Bot.new do
+ configure do |c|
+ c.server = "irc.freenode.org"
+ c.channels = ["#cinch"]
+ end
+end
+
+bot.start
View
41 examples/seen.rb
@@ -1,41 +0,0 @@
-require 'cinch'
-
-users = {}
-
-class Seen < Struct.new(:who, :where, :what, :time)
- def to_s
- "[#{time.asctime}] #{who} was seen in #{where} saying #{what}"
- end
-end
-
-bot = Cinch.setup(
- :server => 'irc.freenode.org',
- :channels => ['#cinch'],
- :prefix => '!',
- :verbose => true,
-)
-
-# Only log a PRIVMSG
-bot.on :privmsg do |m|
- # Dont record a private message
- unless m.private?
- users[m.nick] = Seen.new(m.nick, m.channel, m.text, Time.new)
- end
-end
-
-bot.plugin("seen :nick") do |m|
- nick = m.args[:nick]
-
- if nick == bot.nick
- m.reply "That's me!"
- elsif nick == m.nick
- m.reply "That's you!"
- elsif users.key?(nick)
- m.reply users[nick].to_s
- else
- m.reply "I haven't seen #{nick}"
- end
-end
-
-bot.run
-
View
31 examples/urban_dict.rb
@@ -1,31 +0,0 @@
-require 'cinch'
-require 'open-uri'
-require 'nokogiri'
-require 'cgi'
-
-# This bot connects to urban dictionary and returns the first result
-# for a given query, replying with the result directly to the sender
-
-bot = Cinch.setup do
- server "irc.freenode.net"
- nick "MrCinch"
- channels %w/ #cinch /
-end
-
-# This method assumes everything will go ok, it's not the best method
-# of doing this *by far* and is simply a helper method to show how it
-# can be done.. it works!
-def urban_dict(query)
- url = "http://www.urbandictionary.com/define.php?term=#{CGI.escape(query)}"
- CGI.unescape_html Nokogiri::HTML(open(url)).at("div.definition").text.gsub(/\s+/, ' ') rescue nil
-end
-
-bot.plugin("urban :query") do |m|
- m.answer urban_dict(m.args[:query]) || "No results found"
-end
-
-bot.run
-
-# injekt> !urban cinch
-# MrCinch> injekt: describing an action that's extremely easy.
-
View
34 examples/url_shorten.rb
@@ -1,34 +0,0 @@
-require 'open-uri'
-require 'cinch'
-
-# Automatically shorten URL's found in messages
-# Using the tinyURL API
-
-bot = Cinch.configure do
- server "irc.freenode.org"
- verbose true
- channels %w/#cinch/
-end
-
-def shorten(url)
- url = open("http://tinyurl.com/api-create.php?url=#{URI.escape(url)}").read
- url == "Error" ? nil : url
-rescue OpenURI::HTTPError
- nil
-end
-
-bot.on :privmsg do |m|
- unless m.private?
- urls = URI.extract(m.text, "http")
-
- unless urls.empty?
- short_urls = urls.map {|url| shorten(url) }.compact
-
- unless short_urls.empty?
- m.reply short_urls.join(", ")
- end
- end
- end
-end
-
-bot.start
View
27 lib/cinch.rb
@@ -1,25 +1,12 @@
-dir = File.dirname(__FILE__)
-$LOAD_PATH.unshift(dir) unless $LOAD_PATH.include? dir
-
-require 'ostruct'
-require 'optparse'
-
-require 'cinch/irc'
-require 'cinch/rules'
-require 'cinch/base'
-require 'cinch/names'
+require 'cinch/bot'
module Cinch
- VERSION = '0.3.5'
-
- class << self
+ VERSION = '1.0.0'
- # Setup bot options and return a new Cinch::Base instance
- def setup(ops={}, &blk)
- Cinch::Base.new(ops, &blk)
- end
- alias_method :configure, :setup
+ # @return [String]
+ # @todo Handle mIRC color codes more gracefully.
+ # @api private
+ def self.filter_string(string)
+ string.gsub(/[\x00-\x1f]/, '')
end
-
end
-
View
41 lib/cinch/ban.rb
@@ -0,0 +1,41 @@
+require "cinch/mask"
+module Cinch
+ class Ban
+ # @return [Mask, String]
+ attr_reader :mask
+
+ # @return [String]
+ attr_reader :by
+
+ # @return [Time]
+ attr_reader :created_at
+
+ # @return [Boolean]
+ attr_reader :extended
+
+ def initialize(mask, by, at)
+ @by, @created_at = by, at
+ if mask =~ /^\$/
+ @extended = true
+ @mask = mask
+ else
+ @extended = false
+ @mask = Mask.new(mask)
+ end
+ end
+
+ # @return [Boolean] true if the ban matches `user`
+ # @raise [Exceptions::UnsupportedFeature] Cinch does not support
+ # Freenode's extended bans
+ def match(user)
+ raise UnsupportedFeature, "extended bans (freenode) are not supported yet" if @extended
+ @mask =~ user
+ end
+ alias_method :=~, :match
+
+ # @return [String]
+ def to_s
+ @mask.to_s
+ end
+ end
+end
View
368 lib/cinch/base.rb
@@ -1,368 +0,0 @@
-module Cinch
-
- # == Description
- # The base for an IRC connection
- # TODO: More documentation
- #
- # == Example
- # bot = Cinch::Base.new :server => 'irc.freenode.org'
- #
- # bot.on :join do |m|
- # m.reply "Welcome to #{m.channel}, #{m.nick}!" unless m.nick == bot.nick
- # end
- #
- # bot.plugin "say :text" do |m|
- # m.reply m.args[:text]
- # end
- #
- # == Author
- # * Lee Jarvis - ljjarvis@gmail.com
- class Base
-
- # A Hash holding rules and attributes
- attr_reader :rules
-
- # A Hash holding listeners and reply Procs
- attr_reader :listeners
-
- # An OpenStruct holding all configuration options
- attr_reader :options
-
- # Our IRC::Socket instance
- attr_reader :irc
-
- # Default options hash
- DEFAULTS = {
- :port => 6667,
- :nick => "Cinch",
- :nick_suffix => '_',
- :username => 'cinch',
- :realname => "Cinch IRC Bot Building Framework",
- :prefix => '!',
- :usermode => 0,
- :password => nil,
- :ssl => false,
- }
-
- # Options can be passed via a hash, a block, or on the instance
- # independantly. Or of course via the command line
- #
- # == Example
- # # With a Hash
- # bot = Cinch::Base.new(:server => 'irc.freenode.org')
- #
- # # With a block
- # bot = Cinch::Base.new do
- # server "irc.freenode.org"
- # end
- #
- # # After the instance is created
- # bot = Cinch::Base.new
- # bot.options.server = "irc.freenode.org"
- #
- # # Nothing, but invoked with "ruby foo.rb -s irc.freenode.org"
- # bot = Cinch::Base.new
- def initialize(ops={}, &blk)
- options = DEFAULTS.merge(ops).merge(Options.new(&blk))
- @options = OpenStruct.new(options.merge(cli_ops))
-
- @rules = Rules.new
- @listeners = {}
- @listeners[:ctcp] = {}
-
- @irc = IRC::Socket.new(options[:server], options[:port], options[:ssl])
- @parser = IRC::Parser.new
-
- # Default listeners
- on(:ping) {|m| @irc.pong(m.text) }
-
- on(:ctcp, :version) {|m| m.ctcp_reply "Cinch IRC Bot Building Framework v#{Cinch::VERSION}"}
- end
-
- # Add a new plugin
- #
- # == Example
- # plugin('hello') do |m|
- # m.reply "Hello, #{m.nick}!"
- # end
- def plugin(rule, options={}, &blk)
- rule, keys = compile(rule)
-
- if @rules.has_rule?(rule)
- @rules.add_callback(rule, blk)
- @rules.merge_options(rule, options)
- else
- @rules.add_rule(rule, keys, options, blk)
- end
- end
- alias :rule :plugin
-
- # Add new listeners
- #
- # == Example
- # on(376) do |m|
- # m.join "#mychan"
- # end
- #
- # This method also provides an alternative way to define a rule.
- # The following are equivalent
- # plugin("foo bar") do |m|
- # m.reply "baz!"
- # end
- #
- # on(:message, "foo bar") do |m|
- # m.reply "baz!"
- # end
- #
- # This also gives us the opportunity to reply to CTCP messages
- # on(:ctcp, :time) do |m|
- # m.ctcp_reply Time.now.asctime
- # end
- #
- # Note that when adding listeners for numberic IRC replies which
- # begin with a 0 (digit), make sure you define the command as a
- # String and not Integer. This is because 001.to_s == "1" so the
- # command will not work as expected.
- def on(*commands, &blk)
- if commands.first == :message
- rule, options = commands[1..2]
- options = {} unless options.is_a?(Hash)
- plugin(rule, options, &blk)
- elsif commands.first == :ctcp
- action = commands[1]
-
- if @listeners[:ctcp].key?(action)
- @listeners[:ctcp][action] << blk
- else
- @listeners[:ctcp][action] = [blk]
- end
- else
- commands.map {|x| x.to_s.downcase.to_sym }.each do |cmd|
- if @listeners.key?(cmd)
- @listeners[cmd] << blk
- else
- @listeners[cmd] = [blk]
- end
- end
- end
- end
-
- # This method builds a regular expression from your rule
- # and defines all named parameters, as well as dealing with
- # patterns.
- #
- # All patterns which exist in Cinch::IRC::Parser are supported
- #
- # == Examples
- # === Capturing digits
- # bot.plugin("say :text-digit")
- # * Does match !say 3
- # * Does not match !say 3 4
- # * Does not match !say foo
- #
- # === Both digit and word
- # bot.plugin("say :n-digit :text-word")
- # * Does match !say 3 foo
- # * Does not match !say 3 foo bar
- #
- # === Capturing until the end of the line
- # bot.plugin("say :text")
- # * Does match !say foo
- # * Does match !say foo bar
- #
- # === Or mix them all
- # bot.plugin("say :n-digit :who-word :text")
- #
- # Using "!say 3 injekt some text here" would provide
- # the following attributes
- # * m.args[:n] => 3
- # * m.args[:who] => injekt
- # * m.args[:text] => some text here
- def compile(rule)
- return [rule, []] if rule.is_a?(Regexp)
- keys = []
- special_chars = %w{. + ( )}
-
- pattern = rule.to_s.gsub(/((:[\w\-]+)|[\*#{special_chars.join}])/) do |match|
- case match
- when *special_chars
- Regexp.escape(match)
- else
- k = $2
- if k =~ /\-\w+$/
- key, type = k.split('-')
- keys << key[1..-1]
-
- if @parser.has_pattern?(type)
- "(#{@parser.pattern(type)})"
- else
- "([^\x00\r\n]+?)"
- end
- else
- keys << k[1..-1]
- "([^\x00\r\n]+?)"
- end
- end
- end
- ["^#{pattern}$", keys]
- end
-
- # Add a custom pattern for rule validation
- #
- # == Example
- # bot = Cinch.setup do
- # server 'irc.freenode.org'
- # port 6667
- # end
- #
- # bot.add_pattern(:number, /[0-9]/)
- #
- # bot.plugin("getnum :foo-number") do |m|
- # m.reply "Your number was: #{m.args[:foo]}"
- # end
- def add_pattern(name, pattern)
- @parser.add_pattern(name, pattern)
- end
- alias :add_custom_pattern :add_pattern
-
- # Run run run
- def run
- # Configure some runtime listeners
- on(433) do |m|
- @options.nick += @options.nick_suffix
- @irc.nick @options.nick
- end
-
- if @options.respond_to?(:channels)
- on("004") { @options.channels.each {|c| @irc.join(c) } }
- end
-
- @irc.connect options.server, options.port
- @irc.pass options.password if options.password
- @irc.nick options.nick
- @irc.user options.username, options.usermode, '*', options.realname
-
- begin
- process(@irc.read) while @irc.connected?
- rescue Interrupt
- @irc.quit("Interrupted")
- puts "\nInterrupted. Shutting down .."
- exit
- end
- end
- alias :start :run
-
- private
-
- # Process the next line read from the server
- def process(line)
- return unless line
- message = @parser.parse(line)
- message.irc = @irc unless message.irc
- puts message if options.verbose
-
- # runs on any symbol
- @listeners[:any].each { |l| l.call(message) } if @listeners.key?(:any)
-
- if @listeners.key?(message.symbol)
- if message.symbol == :ctcp
- action = message.ctcp_action.downcase.to_sym
-
- if @listeners[:ctcp].include?(action)
- @listeners[:ctcp][action].each {|l| l.call(message) }
- end
- else
- @listeners[message.symbol].each {|l| l.call(message) }
- end
- end
-
- if [:privmsg].include?(message.symbol)
- rules.each do |rule|
- pattern = rule.to_s
- rule.keys.map! {|key| key.to_sym }
- prefix = nil
-
- if options.prefix
- if rule.options.key?(:prefix)
- if [:bot, :botnick, options.nick].include? rule.options[:prefix]
- prefix = options.nick + "[:,] "
- else
- prefix = rule.options[:prefix]
- end
- else
- if [:bot, :botnick, options.nick].include? options.prefix
- prefix = options.nick + "[:,] "
- else
- prefix = options.prefix
- end
- end
- end
-
- # insert prefix unless it already exists
- pattern.insert(1, prefix) unless pattern[1..prefix.size] == prefix
-
- if rule.options[:ignore_case]
- regex = Regexp.new(pattern, Regexp::IGNORECASE)
- else
- regex = Regexp.new(pattern)
- end
-
- if message.text && mdata = message.text.rstrip.match(regex)
- unless rule.keys.empty? || mdata.captures.empty?
- args = Hash[rule.keys.zip(mdata.captures)]
- message.args = args
- end
- # execute rule
- rule.execute(message)
- end
- end
- end
- end
-
- # Parse command line options
- def cli_ops
- options = {}
- if ARGV.any?
- begin
- OptionParser.new do |op|
- op.on("-s server") {|v| options[:server] = v }
- op.on("-p port") {|v| options[:port] = v.to_i }
- op.on("-n nick") {|v| options[:nick] = v }
- op.on("-c command_prefix") {|v| options[:prefix] = v }
- op.on("-v", "--verbose", "Enable verbose mode") {|v| options[:verbose] = true }
- op.on("--ssl") {|v| options[:ssl] = true }
- op.on("-C", "--channels x,y,z", Array, "Autojoin channels") {|v|
- options[:channels] = v.map {|c| %w(# + &).include?(c[0].chr) ? c : c.insert(0, '#') }
- }
- end.parse(ARGV)
- rescue OptionParser::MissingArgument => err
- warn "Missing values for options: #{err.args.join(', ')}\nFalling back to default"
- rescue OptionParser::InvalidOption => err
- warn err.message
- end
- end
- options
- end
-
- # Catch methods
- def method_missing(meth, *args, &blk) # :nodoc:
- if options.respond_to?(meth)
- options.send(meth)
- elsif @irc.respond_to?(meth)
- @irc.send(meth, *args)
- else
- super
- end
- end
-
- # Option management
- class Options < Hash # :nodoc:
- def initialize(&blk)
- instance_eval(&blk) if block_given?
- end
- def method_missing(meth, *args, &blk)
- self[meth] = args.first unless args.empty?
- end
- end
- end
-end
View
479 lib/cinch/bot.rb
@@ -0,0 +1,479 @@
+# -*- coding: utf-8 -*-
+require 'socket'
+require "thread"
+require "ostruct"
+require "cinch/rubyext/module"
+require "cinch/rubyext/queue"
+require "cinch/rubyext/string"
+require "cinch/rubyext/infinity"
+
+require "cinch/exceptions"
+
+require "cinch/helpers"
+require "cinch/logger/logger"
+require "cinch/logger/null_logger"
+require "cinch/logger/formatted_logger"
+require "cinch/syncable"
+require "cinch/message"
+require "cinch/message_queue"
+require "cinch/irc"
+require "cinch/channel"
+require "cinch/user"
+require "cinch/constants"
+require "cinch/callback"
+require "cinch/ban"
+require "cinch/mask"
+require "cinch/isupport"
+require "cinch/plugin"
+
+module Cinch
+
+ class Bot
+ # @return [Config]
+ attr_accessor :config
+ # @return [IRC]
+ attr_accessor :irc
+ # @return [Logger]
+ attr_accessor :logger
+ # @return [Array<Channel>] All channels the bot currently is in
+ attr_reader :channels
+ # @return [String]
+ attr_reader :host
+ # @return [Mask]
+ attr_reader :mask
+ # @return [String]
+ attr_reader :user
+ # @return [String]
+ attr_reader :realname
+ # @return [Time]
+ attr_reader :signed_on_at
+
+ # Helper method for turning a String into a {Channel} object.
+ #
+ # @param [String] channel a channel name
+ # @return [Channel] a {Channel} object
+ # @example
+ # on :message, /^please join (#.+)$/ do |target|
+ # Channel(target).join
+ # end
+ def Channel(channel)
+ return channel if channel.is_a?(Channel)
+ Channel.find_ensured(channel, self)
+ end
+
+ # Helper method for turning a String into an {User} object.
+ #
+ # @param [String] user a user's nickname
+ # @return [User] an {User} object
+ # @example
+ # on :message, /^tell me everything about (.+)$/ do |target|
+ # user = User(target)
+ # reply "%s is named %s and connects from %s" % [user.nick, user.name, user.host]
+ # end
+ def User(user)
+ return user if user.is_a?(User)
+ User.find_ensured(user, self)
+ end
+
+ # @return [void]
+ # @see Logger#debug
+ def debug(msg)
+ @logger.debug(msg)
+ end
+
+ # @return [Boolean]
+ def strict?
+ @config.strictness == :strict
+ end
+
+ # @yield
+ def initialize(&b)
+ @logger = Logger::FormattedLogger.new($stderr)
+ @events = {}
+ @config = OpenStruct.new({
+ :server => "localhost",
+ :port => 6667,
+ :ssl => false,
+ :password => nil,
+ :nick => "cinch",
+ :realname => "cinch",
+ :verbose => true,
+ :messages_per_second => 0.5,
+ :server_queue_size => 10,
+ :strictness => :forgiving,
+ :message_split_start => '... ',
+ :message_split_end => ' ...',
+ :max_messages => nil,
+ :plugins => OpenStruct.new({
+ :plugins => [],
+ :prefix => "!",
+ :options => Hash.new {|h,k| h[k] = {}},
+ }),
+ :channels => [],
+ :encoding => nil,
+ })
+
+ @semaphores_mutex = Mutex.new
+ @semaphores = Hash.new { |h,k| h[k] = Mutex.new }
+ @plugins = []
+ @callback = Callback.new(self)
+ @channels = []
+
+ on :connect do
+ bot.config.channels.each do |channel|
+ bot.join channel
+ end
+ end
+
+ instance_eval(&b) if block_given?
+ end
+
+ # This method is used to set a bot's options. It indeed does
+ # nothing else but yielding {Bot#config}, but it makes for a nice DSL.
+ #
+ # @yieldparam [Struct] config the bot's config
+ # @return [void]
+ def configure(&block)
+ @callback.instance_exec(@config, &block)
+ end
+
+ # Since Cinch uses threads, all handlers can be run
+ # simultaneously, even the same handler multiple times. This also
+ # means, that your code has to be thread-safe. Most of the time,
+ # this is not a problem, but if you are accessing stored data, you
+ # will most likely have to synchronize access to it. Instead of
+ # managing all mutexes yourself, Cinch provides a synchronize
+ # method, which takes a name and block.
+ #
+ # Synchronize blocks with the same name share the same mutex,
+ # which means that only one of them will be executed at a time.
+ #
+ # @param [String, Symbol] name a name for the synchronize block.
+ # @return [void]
+ # @yield
+ #
+ # @example
+ # configure do |c|
+ # …
+ # @i = 0
+ # end
+ #
+ # on :channel, /^start counting!/ do
+ # synchronize(:my_counter) do
+ # 10.times do