pax_global_header00006660000000000000000000000064137102605320014511gustar00rootroot0000000000000052 comment=662c046d24bdfa9a7381a4036fa8e3daea235934 nadoka-0.10.0/000077500000000000000000000000001371026053200130245ustar00rootroot00000000000000nadoka-0.10.0/.gitignore000066400000000000000000000000711371026053200150120ustar00rootroot00000000000000*.gem .bundle Gemfile.lock log/ pkg/* nadoka_fatal_error nadoka-0.10.0/.travis.yml000066400000000000000000000005051371026053200151350ustar00rootroot00000000000000language: ruby rvm: - 2.7 - 2.6 - 2.5 - 2.4 - 2.3 - ruby-head - jruby-head matrix: allow_failures: - rvm: jruby-18mode - rvm: jruby-19mode - rvm: ruby-head - rvm: jruby-head before_install: - gem update bundler script: ruby check-syntax.rb notifications: irc: "irc.freenode.org#nadoka_jp" nadoka-0.10.0/Gemfile000066400000000000000000000001321371026053200143130ustar00rootroot00000000000000source "http://rubygems.org" # Specify your gem's dependencies in nadoka.gemspec gemspec nadoka-0.10.0/README.md000066400000000000000000000012701371026053200143030ustar00rootroot00000000000000# Nadoka: IRC Client server program. Written by SASADA Koichi [![Gem Version](https://badge.fury.io/rb/nadoka.svg)](http://badge.fury.io/rb/nadoka) [![Build Status](https://img.shields.io/travis/nadoka/nadoka.svg)](https://travis-ci.org/nadoka/nadoka) [![Code Climate](https://img.shields.io/codeclimate/github/nadoka/nadoka.svg)](https://codeclimate.com/github/nadoka/nadoka) ## What's this? Nadoka is IRC Client Server program. It's same concept as [madoka](http://www.madoka.org/). You can do with this software: - connect to IRC usually - easy to make a bot with ruby ## more about See Web pages: - https://github.com/nadoka/nadoka - http://www.atdot.net/nadoka/ nadoka-0.10.0/Rakefile000066400000000000000000000000341371026053200144660ustar00rootroot00000000000000require "bundler/gem_tasks" nadoka-0.10.0/bin/000077500000000000000000000000001371026053200135745ustar00rootroot00000000000000nadoka-0.10.0/bin/nadoka000077500000000000000000000005061371026053200147600ustar00rootroot00000000000000#!/usr/bin/ruby require 'rbconfig' ruby = nil begin ruby = RbConfig.ruby rescue ruby = File.join( Config::CONFIG["bindir"], Config::CONFIG["ruby_install_name"] + Config::CONFIG["EXEEXT"] ) end top_dir = File.expand_path('../..', __FILE__) exec(ruby, "-I#{top_dir}", File.join(top_dir, 'nadoka.rb'), *ARGV) nadoka-0.10.0/check-syntax.rb000077500000000000000000000003271371026053200157570ustar00rootroot00000000000000#!/usr/bin/env ruby STDERR.puts RUBY_DESCRIPTION result = true Dir.glob("{bin/nadoka,nadokarc,**/*.[nr]b}") do |filename| STDERR.print "#{filename}: " result &&= system("ruby", "-cw", filename) end exit(result) nadoka-0.10.0/doc/000077500000000000000000000000001371026053200135715ustar00rootroot00000000000000nadoka-0.10.0/doc/ChangeLog.old000066400000000000000000001106251371026053200161250ustar00rootroot000000000000002011-10-29 Kazuhiro NISHIYAMA * rice/irc.rb: add RPL_REOPLIST and RPL_ENDOFREOPLIST. * ChangeLog: remove svn keywords. 2011-09-29 Kazuhiro NISHIYAMA * plugins/opensearchbot.nb: add comma separators. 2011-09-29 Kazuhiro NISHIYAMA * ndk/version.rb: 0.7.7 2011-09-28 Kazuhiro NISHIYAMA * plugins/translatebot.nb: merge patch from ko1_ndk. support Bing Translator. 2011-09-15 Kazuhiro NISHIYAMA * plugins/opensearchbot.nb: split class and enabled to run on command line for tests. 2011-08-16 Kazuhiro NISHIYAMA * plugins/googlebot.nb: bot return uri only, because cannot fetch results from Google code search. 2011-08-15 Kazuhiro NISHIYAMA * plugins/opensearchbot.nb: add new bot. 2011-06-02 Kazuhiro NISHIYAMA * rice/irc.rb (RICE::Message::PATTERN::NICKNAME): nickname starts with DIGIT may occur in split mode. 2010-12-14 Kazuhiro NISHIYAMA * plugins/tenkibot.nb: use same_bot? and clean up trailing spaces. 2010-12-14 Kazuhiro NISHIYAMA * plugins/tenkibot.nb (TenkiBot#tenki): cut after "...". (TenkiBot#bot_initialize): use bot_init_utils. 2010-12-14 Kazuhiro NISHIYAMA * ndk/bot.rb (Nadoka::NDK_Bot#bot_init_utils): add new methods. * plugins/googlebot.nb (GoogleBot#bot_initialize): use bot_init_utils. 2010-10-21 Kazuhiro NISHIYAMA * plugins/googlebot.nb: add ch_kcode. merge patch from ko1_ndk. 2010-10-21 Kazuhiro NISHIYAMA * ndk/server.rb (Nadoka::NDK_Server#server_main_proc): do not use nick_succ when channel and retry join. 2010-10-21 Kazuhiro NISHIYAMA * plugins/translatebot.nb: added (contributed from SASADA Koichi) 2010-09-29 Kazuhiro NISHIYAMA * nadokarc, plugins/autoawaybot.nb, plugins/autodumpbot.nb, plugins/backlogbot.nb, plugins/dictbot.nb, plugins/modemanager.nb, plugins/pastebot.nb, plugins/roulettebot.nb, plugins/timestampbot.nb, plugins/xibot.nb, rice/irc.rb: CRLF -> LF. 2010-09-29 Kazuhiro NISHIYAMA * 0.7.5: released * ndk/version.rb: 0.7.6 2010-09-28 Kazuhiro NISHIYAMA * plugins/googlebot.nb: add gime and gimed. * plugins/googlebot.nb: use shortest match. 2010-09-13 Kazuhiro NISHIYAMA * plugins/dictbot.nb: use shortest match. 2010-09-06 NARUSE, Yui * plugins/twitterbot.nb: added. * plugins/xibot.nb: added. * plugins/backlogbot.nb: send message which matches pattern. 2010-08-18 Kazuhiro NISHIYAMA * rice/irc.rb: applied nurse's patch. Update regexp of IPv6 address based on RFC3986. 2010-07-21 Kazuhiro NISHIYAMA * nadokarc: update IPv6 Sample in Servers. see http://www.ircnet.ne.jp/ 2010-02-15 Kazuhiro NISHIYAMA * plugins/rss_checkbot.nb (RSS_CheckBot#send_notice): all #send_notice should support Array in @ch. * rice/irc.rb (RICE::Message#to_s): add recursive #to_s to avoid "can't convert Array into String (TypeError)" in `<<'. 2010-01-28 Kazuhiro NISHIYAMA * ndk/server.rb (Nadoka::NDK_Server#set_signal_trap): merge patch from n0kada. http://www.atdot.net/sp/view/dswxwk 2010-01-25 Kazuhiro NISHIYAMA * nadokarc: update Servers. see http://www.wide.ad.jp/news/press/20100125-IRC-server-close-j.html and http://www.ircnet.ne.jp/ . 2010-01-01 Koichi Sasada * plugins/googlebot.nb: support new searcher calc and code. * plugins/googlebot.nb: support abbreviated syntax (ex: g> ). 2009-11-30 Kazuhiro NISHIYAMA * nadokarc: update Servers. see http://twitter.com/ircnet_fujisawa/status/6185537849 for irc6.fujisawa.wide.ad.jp, and http://en.wikipedia.org/wiki/IRCd says most often used port for SSL is 6697. 2009-11-08 Kazuhiro NISHIYAMA * nadokarc: update Servers. see http://slashdot.jp/it/article.pl?sid=09/11/06/0747210 2009-10-23 Kazuhiro NISHIYAMA * plugins/googlebot.nb: fallback to json gem. 2009-09-22 Kazuhiro NISHIYAMA * ndk/server.rb (Nadoka::NDK_Server#server_main_proc): support RPL_ISUPPORT. 2009-09-22 Kazuhiro NISHIYAMA * ndk/error.rb: add Nadoka::NDK_InvalidMessage. * ndk/server.rb (Nadoka::NDK_Server#send_to_server): check [\r\n]. 2009-09-21 Kazuhiro NISHIYAMA * rice/irc.rb: remove magic comment. ruby 1.9.2 works without it. 2009-09-21 Kazuhiro NISHIYAMA * 0.7.2: released * ndk/version.rb: bumped version to 0.7.5. 2009-09-17 Kazuhiro NISHIYAMA * plugins/googlebot.nb: use hl because lr used by web searcher only. 2009-09-17 Kazuhiro NISHIYAMA * plugins/googlebot.nb: support more searcher of Google AJAX Search API. 2009-09-17 Kazuhiro NISHIYAMA * plugins/googlebot.nb: use Google AJAX Search API. see http://code.google.com/intl/ja/apis/ajaxsearch/ for detail. 2009-09-14 Kazuhiro NISHIYAMA * plugins/googlebot.nb: Google SOAP Search API (No Longer Available). see http://code.google.com/intl/ja/apis/soapsearch/ and http://googlecode.blogspot.com/2009/08/well-earned-retirement-for-soap-search.html 2009-08-21 Kazuhiro NISHIYAMA * nadoka.rb, ndk/server.rb: revert previous change, and use ?\x1 instead. 2009-08-21 Kazuhiro NISHIYAMA * nadoka.rb, ndk/server.rb: handle ctcp on ruby 1.9. 2009-08-18 Kazuhiro NISHIYAMA * nadoka.rb, ndk/config.rb, rice/irc.rb: ruby 1.9 support (not completed yet). apply patch from unak. 2009-08-17 Kazuhiro NISHIYAMA * nadokarc, ndk/config.rb, ndk/server.rb: no listen when Client_server_port is nil. 2009-08-17 Kazuhiro NISHIYAMA * plugins/tenkibot.nb: omit empty min celsius. 2009-07-29 Kazuhiro NISHIYAMA * lib/rss_check.rb: update lirs_uri. 2009-07-29 Kazuhiro NISHIYAMA * 0.7.1 : released * ndk/version.rb : 0.7.2 2009-07-27 Kazuhiro NISHIYAMA * nadoka.rb: add --daemon option. (apply patch from unak) 2009-07-04 Kazuhiro NISHIYAMA * plugins/dictbot.nb: avoid nkf bug. add waei as w, ruigo as r. new yahoo dict URI. (apply patch from ko1_ndk) * plugins/googlebot.nb: quote splited words. (apply patch from ko1_ndk) 2009-07-01 Kazuhiro NISHIYAMA * rice/irc.rb: @conn[0] may be nil. 2009-05-25 Kazuhiro NISHIYAMA * ndk/config.rb, ndk/server.rb: add Primitive_Filter to filter any commands. (apply patch from unak) 2009-05-20 Kazuhiro NISHIYAMA * plugins/identifynickserv.nb: added. 2009-05-20 Kazuhiro NISHIYAMA * ndk/bot.rb, ndk/server.rb: add on_server_connected. 2009-04-16 Kazuhiro NISHIYAMA * nadokarc, ndk/config.rb, ndk/server.rb, rice/irc.rb: support SSL. Wed Mar 19 19:46:52 2008 Kazuhiro NISHIYAMA * ndk/logger.rb : pass ch to msgobj from clog. * ndk/config.rb : unify safe channel log filename. Mon Jul 23 01:07:08 2007 Kazuhiro NISHIAYMA * lib/rss_check.rb : bug fix when dc_date is nil. * ndk/config.rb : fix bug when non-Hash in BotConfig array. Mon Jul 02 19:01:04 2007 Koichi Sasada * plugins/tenkibot.nb : fix output format detail. * plugins/googlebot.nb : fix to retry 5 times if error is occurred while google search. * plugins/dictbot.nb : fix to show short summary. 2007-07-02(Mon) 18:01:58 +0900 Kazuhiro NISHIAYMA * rice/irc.rb : allow trailing "." in HOSTNAME (for NickServ on Freenode) 2007-02-20(Tue) 23:35:41 +0900 Kazuhiro NISHIAYMA * **/*.rb, plugins/*.nb : fix typos 2006-10-28(Sat) 22:08:11 +0900 Kazuhiro NISHIAYMA * ndk/server_state.rb : fix bug on_mode after server split 2006-10-20(Fri) 22:22:07 +0900 Kazuhiro NISHIAYMA * ndk/bot.rb : new method ccn2rcn 2006-10-07(Sat) 03:52:11 +0900 Koichi Sasada * plugin/tenkibot.nb : added 2006-10-02(Mon) 21:58:40 +0900 Koichi Sasada * 0.7.0 : released * ndk/version.rb : 0.7.1 2006-10-02(Mon) 21:41:24 +0900 Koichi Sasada * plugins/weatherbot.nb : removed * plugins/sendpingbot.nb : added * ndk/client.rb : modified for pingbot * ndk/server.rb : ditto 2006-09-01(Fri) 02:04:15 +0900 Kazuhiro NISHIAYMA * ndk/bot.rb : support send to a safe channel * nadokarc : fix misalignment 2006-08-31(Thu) 01:40:39 +0900 Kazuhiro NISHIAYMA * ndk/bot.rb : fix typos 2006-08-11(Fri) 14:45:01 +0900 Koichi Sasada * plugins/gonzuibot.nb : add a comma * plugins/pastebot.nb : add nick to paste page name * plugins/googlebot.nb : fix show_char_code_and_erace_tag 2006-05-16(Tue) 16:16:30 +0900 Koichi Sasada * ndk/server.rb : handle TERM signal 2006-03-27(Mon) 19:50:27 +0900 Koichi Sasada * plugins/dictbot.nb : fix dict index * plugins/roulettebot.nb : added * rice/irc.rb : loosen nick regexp * ndk/server.rb : randomize server if serching next server 2005-09-30(Fri) 00:05:19 +0900 Koichi Sasada * plugins/googlebot.nb : fix bugs * plugins/dictbot.nb : support * plugins/gonzuibot.nb : added * ndk/config.rb : fix bugs 2005-06-25(Sat) 19:02:24 +0900 Koichi Sasada * nadoka.rb : output error information to file 'nadoka_fatal_error' when unhandled error is occure (bug) 2005-06-14(Tue) 21:26:07 +0900 Koichi Sasada * ndk/server.rb : change reloaded message output method to slog 2005-06-13(Mon) 18:25:33 +0900 Koichi Sasada * nadokarc, ndk/logger.rb, ndk/config.rb : support :channel_name_in_file_name option for logging 2005-06-13(Mon) 00:11:39 +0900 Koichi Sasada * ndk/server_state.rb : fix typo * ndk/config.rb : see ENV['LANG'] to decide filename encoding * ndk/version.rb : fix version format * ndk/bot.rb : fix comment 2005-06-10(Fri) 04:38:25 +0900 Koichi Sasada * ndk/server.rb : fix logic operation 2005-06-09(Thu) 17:22:25 +0900 Koichi Sasada * ndk/logger.rb : synchronize IOLogWriter output 2005-06-09(Thu) 17:18:21 +0900 Koichi Sasada * ndk/config.rb : set default file encoding to EUC 2005-06-09(Thu) 05:11:58 +0900 Koichi Sasada * ndk/config.rb : fix to get FilenameEncoding * ndk/server.rb : stop away message until server connection 2005-06-08(Wed) 07:56:17 +0900 Koichi Sasada * ndk/config.rb : clear if channel setting is not hash 2005-06-08(Wed) 07:42:17 +0900 Koichi Sasada * plugins/googlebot.nb : fix to use count default language 2005-06-08(Wed) 06:21:25 +0900 Koichi Sasada * ndk/server.rb : trap IOError and change error output * ndk/config.rb : support {prefix:(nick|user|host)} specifier * ndk/logger.rb : {user} -> {nick} * nadokarc : apply above changes * rice/irc.rb : output unless port is closed * plugins/backlogbot.nb : change spell (@store -> @stores) 2005-06-05(Sun) 22:47:15 +0900 Koichi Sasada * ndk/server.rb : PONG MISS message dlog -> slog 2005-05-31(Tue) 14:08:26 +0900 Koichi Sasada * nadokarc : change log setting of "#nadoka_check" 2005-05-29(Sun) 14:01:02 +0900 Koichi Sasada * ndk/server.rb, ndk/server_state.rb, ndk/logger.rb : NICK and QUIT logging from server_state * nadokarc, ndk/config.rb : change default PART, QUIT message format 2005-05-29(Sun) 12:53:48 +0900 Koichi Sasada * plugins/weba.nb : set default password on * ndk/logger.rb : fix nick logging 2005-05-29(Sun) 07:06:55 +0900 Koichi Sasada * plugins/weba.nb : use logger's message store * ndk/logger.rb, ndk/config.rb : some changes for weba * nadokarc : add vim pragma and FilenameEncoding setting * ndk/client.rb, ndk/server.rb : fix logger relaod process * ndk/server_state.rb : add channel_raw_names 2005-05-29(Sun) 03:35:28 +0900 Koichi Sasada * nadokarc : add BackLogBot as default bot 2005-05-29(Sun) 03:31:07 +0900 Koichi Sasada * ndk/server.rb : add @rc read accessor * nadokarc, ndk/config.rb : support default setting_name as rc file name 2005-05-29(Sun) 03:04:53 +0900 Koichi Sasada * ndk/config.rb : support filename encoding(utf-8) 2005-05-29(Sun) 02:49:12 +0900 Koichi Sasada * ndk/server_state.rb, ndk/config.rb, ndk/logger.rb : new logging scheme is added * nadokarc : add indent and log detail setting * plugins/backlogbot.nb : use logger's message store 2005-05-27(Fri) 21:32:35 +0900 Koichi Sasada * nadokarc : comment out Quit_Message and change Default_log, Log_TimeFormat setting * ndk/client.rb : fix welcome message * ndk/config.rb : change Default_log, System_log path, Log_TimeFormat and add Log_MessageFormat. 2005-05-26(Thu) 22:11:48 +0900 Koichi Sasada * plugins/autodumpbot.nb, plugins/sixamobot.nb : change copyright year 2005-05-26(Thu) 22:07:15 +0900 Koichi Sasada * ndk/config.rb : fix a bug 2005-05-26(Thu) 21:51:10 +0900 Koichi Sasada * nadokarc : change default quit message 2005-05-26(Thu) 21:45:49 +0900 Koichi Sasada * nadokarc : fix ACL comment 2005-05-26(Thu) 21:42:29 +0900 Koichi Sasada * plugins/backlogbot.nb : fix configuration example 2005-05-26(Thu) 21:24:50 +0900 Koichi Sasada * log/.deleteme : remove 2005-05-26(Thu) 21:23:03 +0900 Koichi Sasada * plugins/weatherbot.nb : add parentheses * plugins/rss_checkbot.nb : add configuration :over_message 2005-05-26(Thu) 21:03:24 +0900 Koichi Sasada * some files : change copyright year 2005-05-26(Thu) 20:52:32 +0900 Koichi Sasada * plugins/googlebot.nb : fix plural support bug 2005-05-26(Thu) 20:27:16 +0900 Koichi Sasada * plugins/googlebot.nb : fix configuration example and support plural * plugins/rss_checkbot.nb : change bot_state format and separate rss check function * lib/rss_check.rb : return failed information if exception is raised 2005-05-26(Thu) 11:21:31 +0900 Koichi Sasada * ndk/bot.rb : some refactoring 2005-05-26(Thu) 10:53:40 +0900 Koichi Sasada * plugins/autoawaybot.nb : add configuration example and some refactoring 2005-05-26(Thu) 10:44:33 +0900 Koichi Sasada * plugins/timestampbot.nb : add :client configuration (if true, output timestamp to clients. default: false) 2005-05-26(Thu) 10:37:29 +0900 Koichi Sasada * plugins/timestampbot.nb : fix configuration example 2005-05-26(Thu) 10:29:16 +0900 Koichi Sasada * plugins/message.nb => plugins/messagebot.nb * plugins/autoaway.nb => plugins/autoawaybot.nb * plugins/autodump.nb => plugins/autodumpbot.nb * plugins/sixamo.nb => plugins/sixamobot.nb * plugins/mailcheck.nb => plugins/mailcheckbot.nb * plugins/sixamobot.nb : fix configuration example 2005-05-26(Thu) 10:18:18 +0900 Koichi Sasada * plugins/rss_check.nb => plugins/rss_checkbot.nb, * plugins/cron.nb => plugins/cronbot.nb * plugins/mailcheck.nb, plugins/modemanager.nb, plugins/weba.nb, plugins/drbot.nb, plugins/rss_checkbot.nb : fix configuration example 2005-05-26(Thu) 02:35:09 +0900 Koichi Sasada * ndk/version.rb : 0.7.0 2005-05-25(Wed) 14:22:32 +0900 Koichi Sasada * ndk/config.rb, nadokarc : change bot configuration format (BotConfig format) * ndk/config.rb : Nadoka::require is supported (if reload configuration, required files by Nadoka::require are cleared) * ndk/bot.rb : BotClass (Array) => BotClasses (Hash (name => body)) * plugins/googlebot.nb : fix setting description and fix google_key file path search logic * plugins/dict_bot.nb => plugins/dictbot.nb * plugins/backlogbot.nb : fix setting description 2005-05-22(Sun) 23:40:28 +0900 Koichi Sasada * plugins/dict_bot.nb : /dic(.)>.../ => /\Adic(.)>.../ * plugins/googlebot.nb : remove words in results * plugins/pastebot.nb : support fpaste 2005-05-22(Sun) 23:28:53 +0900 Koichi Sasada * ndk/logger.rb : support directory auto generation 2005-05-22(Sun) 01:53:38 +0900 Koichi Sasada * nadoka/ : mkdir * lib/ : mkdir * ndk_*.rb : move to nadoka/ and change require path * tagparts.rb, rss_check.rb : move to lib/ and change require path * ndk_manager.rb (NDK_Manager) : move to ndk/server.rb and rename NDK_Server * ndk_err.rb : move to ndk/error.rb 2005-03-15(Tue) 17:30:25 +0900 Koichi Sasada * ndk_state.rb : re-fix state management 2005-03-04(Fri) 16:28:18 +0900 Koichi Sasada * 0.6.4 released * ndk_version.rb : 0.6.5 2005-03-04(Fri) 13:23:37 +0900 Koichi Sasada * ndk_state.rb : fix server message pattern 2005-01-20(Thu) 23:15:33 +0900 Koichi Sasada * nadokarc : fix typo 2004-12-20(Mon) 20:15:03 +0900 Koichi Sasada * plugins/weatherbot.nb : change unkown region error message and ignore region's case 2004-12-20(Mon) 20:04:53 +0900 Koichi Sasada * plugins/weatherbot.nb : strip region name 2004-12-20(Mon) 19:53:18 +0900 Koichi Sasada * plugins/dict_bot.nb, plugins/pastebot.nb, plugins/weatherbot.nb added (test version) 2004-12-03(Fri) 14:45:19 +0900 Koichi Sasada * ndk_state.rb : fix again (patched by U.Nakamura) 2004-12-03(Fri) 13:59:48 +0900 Koichi Sasada * ndk_state.rb : fix bug about mode change by server * plugins/modemanager.rb : fix bug to detect mode 2004-11-24(Wed) 13:20:17 +0900 Koichi Sasada * plugins/autodump.nb : fix bug (apply patch from shugo) 2004-08-25(Wed) 15:00:49 +0900 Koichi Sasada * nadokarc : fix bug(add ',') 2004-08-20(Fri) 18:48:36 +0900 Koichi Sasada * plugins/googlebot.nb : fix googlec prompt 2004-08-19(Thu) 01:17:13 +0900 Koichi Sasada * nadokarc : add '#nadoka' channel setting 2004-08-19(Thu) 01:12:03 +0900 Koichi Sasada * plugins/autoaway.nb : fix bug(add condition at on_timer) 2004-08-16(Mon) 13:07:15 +0900 Koichi Sasada * ndk_state.rb, ndk_manager.rb : fix kick bug([nadoka:271]) 2004-08-14(Sat) 21:24:28 +0900 Koichi Sasada * plugins/modemanager.nb : fix mode check routine(match -> include?) 2004-08-13(Fri) 11:04:36 +0900 Koichi Sasada * 0.6.3 released * ndk_version.rb : 0.6.4 2004-08-13(Fri) 10:18:03 +0900 Koichi Sasada * plugins/modemanager.nb : fix bug * plugins/autodump.nb : added * plugins/*.nb : fix hash access([] to fetch(key, default)). 2004-08-13(Fri) 10:04:26 +0900 Koichi Sasada * ndk_state.rb : fix enbug * nadokarc : change default channel and fix typo * plugins/drbot.nb : fix configuration example's bug * plugins/sixamo.nb : fix typo 2004-08-05(Thu) 08:25:48 +0900 Koichi Sasada * ndk_state.rb : fix some bugs 2004-08-02(Mon) 07:51:51 +0900 Koichi Sasada * ndk_client.rb : fix pong message field * ndk_state.rb : fix on_quit process * plugins/modemanager.nb : don't set mode if already set 2004-07-28(Wed) 05:07:00 +0900 Koichi Sasada * plugins/google.nb : support gooo*gle 2004-07-27(Tue) 11:16:05 +0900 Koichi Sasada * plugins/ndk_client.rb : fix typo 2004-07-26(Mon) 02:13:58 +0900 Koichi Sasada * plugins/backlogbot.nb : fixed a bug * plugins/modemanager.nb : applied unak's patch (support delaied distribution, and so on) * ndk_client.rb : fix exception handling 2004-07-24(Sat) 21:26:30 +0900 Koichi Sasada * plugins/modemanager.nb : added 2004-07-24(Sat) 07:30:10 +0900 Koichi Sasada * ndk_manager.rb : fix server login process(reconnect if server replies error) * ndk_manager.rb, ndk_state.rb : support 'KICK' state * ndk_manager.rb, ndk_staet.rb : support safe channel * plugins/backlogbot.nb : fix around safe channel bugs * plugins/googlebot.nb(googlec) : fix charcode problem 2004-07-21(Wed) 19:17:13 +0900 Koichi Sasada * ndk_bot.rb(canonical_channel_name) : added and this alias 'ccn' is added * ndk_state.rb : rename channel_member to channel_users and channel_member_mode to channel_user_mode 2004-07-21(Wed) 00:57:44 +0900 Koichi Sasada * ndk_manager.rb : part only from own nick when server disconnect 2004-07-20(Tue) 11:33:46 +0900 Koichi Sasada * 0.6.2 release * ndk_version.rb : 0.6.3 2004-07-20(Tue) 04:06:17 +0900 Koichi Sasada * ndk_client.rb : use NdkCommandDescription to list items * ndk_config.rb(identical_channel_name) : add euc-jp option to regexp 2004-07-20(Tue) 02:23:35 +0900 Koichi Sasada * plugins/sixamo.nb : change talk ratio algorithm 2004-07-20(Tue) 02:23:35 +0900 Koichi Sasada * plugins/sixamo.nb : fix talk ratio algorithm 2004-07-20(Tue) 02:18:01 +0900 Koichi Sasada * plugins/sixamo.nb : down talk rate and memorize his talk 2004-07-20(Tue) 02:00:45 +0900 Koichi Sasada * ndk_logger.rb : synchronize IO object output 2004-07-20(Tue) 01:48:45 +0900 Koichi Sasada * ndk_manager.rb : fix invoke time event timing 2004-07-20(Tue) 00:37:02 +0900 Koichi Sasada * ndk_manager.rb : add @pong_fail_count and MAX_PONG_FAIL to check server response * ndk_client.rb : fix usage 100% problem(remove Thread.pass and ignore message when server connection is closed) 2004-07-19(Mon) 22:10:05 +0900 Koichi Sasada * 0.6.1 releaseds * ndk_version.rb : 0.6.2 2004-07-19(Mon) 21:52:27 +0900 Koichi Sasada * ndk_manager.rb : TimerIntervalSec to 60 2004-07-19(Mon) 21:40:17 +0900 Koichi Sasada * ndk_manager.rb : fix time thread timing * ndk_manager.rb : fix SIGINT hook * ndk_version.rb : remove debug code 2004-07-19(Mon) 21:00:21 +0900 Koichi Sasada * 0.6.0 released * ndk_version.rb : 0.6.1 2004-07-19(Mon) 20:59:11 +0900 Koichi Sasada * plugins/mailcheck.nb : updated from http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/ruexli/nadoka-plugins/ 2004-07-19(Mon) 19:50:40 +0900 Koichi Sasada * rice/irc.rb : support long nick * ndk_manager.rb : fix time event invoke timing * ndk_logger.rb(slog, clog, write_log) : add nostamp option * plugins/timestampbot.nb 2004-07-17(Sat) 00:06:54 +0900 Koichi Sasada * plugins/googlebot.nb : fix char code bugs * ndk_manager.rb : trap SIGINT to quit program * ndk_client.rb : add exception hook to protect unexpected disconnect 2004-07-14(Wed) 04:29:47 +0900 Koichi Sasada * ToDo : removed * README : fix url 2004-07-14(Wed) 04:27:53 +0900 Koichi Sasada * ndk_manager.rb : fix exception handling 2004-07-14(Wed) 04:03:27 +0900 Koichi Sasada * ndk_config.rb : add DefaultBotFiles * ndk_config.rb : ch_config(ch, key) is added * ndk_config.rb(make_bot_instance) : logging message to bot_state * ndk_manager.rb : fix error recovery process(set @connected to false) * plugins/backlogbot.nb : fix some bugs (user to user messages and so on) * plugins/weba.nb : fix configuration example's bug 2004-07-12(Mon) 16:51:24 +0900 Koichi Sasada * ndk_version.rb : add rev to version string if this in trunk 2004-07-12(Mon) 16:39:59 +0900 Koichi Sasada * ChangeLog : add rev keyword 2004-07-11(Sun) 11:37:14 +0900 Koichi Sasada * plugins/backlogbot.nb : fix schemes 2004-07-10(Sat) 22:48:35 +0900 Koichi Sasada * nadokarc : fix filenameencoding bugs * ndk_logger.rb : fix user to user message's logname 2004-07-10(Sat) 21:22:18 +0900 Koichi Sasada * ndk_manager.rb : ignore NOTICE messages when logged in * plugins/backlogbot.rb : ignore non channel messages 2004-07-10(Sat) 17:33:10 +0900 Koichi Sasada * ndk_version.rb : check trunk * ndk_bot.rb : add on_quit_from_channel event * plugins/backlogbot.nb: added 2004-07-09(Fri) 07:45:29 +0900 Koichi Sasada * plugins/drbot.nb : rename method name kick_* -> send/recv... 2004-07-09(Fri) 03:52:01 +0900 Koichi Sasada * plugins/drbot.nb, plugins/drbcl.rb : added * plugins/mailcheck.nb, plugins/message.nb :added (contributed from Kazuhiro NISHIYAMA) 2004-07-09(Fri) 01:39:01 +0900 Koichi Sasada * ndk_client.rb(client_notice) : added * ndk_manager.rb, ndk_client.rb : support '/nadoka status' command and add NDK_Manager#ndk_state, NDK_Bot#bot_state, NDK_Client#state * plugins/rss_check.nb(bot_state) : added 2004-07-08(Thu) 08:31:33 +0900 Koichi Sasada * tagparts.rb : add tree2string 2004-07-07(Wed) 17:53:27 +0900 Koichi Sasada * ndk_client.rb : fix socket bugs 2004-07-07(Wed) 17:15:07 +0900 Koichi Sasada * plugins/weba.nb : add basic authentication, and some fixs * plugins/shellbot.nb, plugins/evalbot.nb : undo 2004-07-07(Wed) 16:04:30 +0900 Koichi Sasada * plugins/shellbot.nb, plugins/evalbot.nb : fix bugs around constant search path * ndk_state.rb(channel_member_mode) : added * ndk_bot.rb : remove @raw_prefix 2004-07-07(Wed) 10:18:51 +0900 Koichi Sasada * plugins/weba.nb : fix some issues 2004-07-06(Tue) 22:38:58 +0900 Koichi Sasada * ndk_client.rb : fix exception bug * ndk_manager.rb, ndk_config.rb : fix reload process * nadokarc : fix typo * plugins/weba.nb : fix some bugs, support writing * tagparts.rb : added(required by weba) 2004-07-05(Mon) 15:40:09 +0900 Koichi Sasada * ndk_config.rb : change default Channel_Info, :timing as :startup * ndk_manager.rb : fix nick change bugs * plugins/weba.nb : added(test version) 2004-07-05(Mon) 13:13:26 +0900 Koichi Sasada * ndk_manager.rb : add execption hook(client thread) 2004-07-05(Mon) 09:43:12 +0900 Koichi Sasada * nadokarc : fix bug 2004-07-05(Mon) 08:31:35 +0900 Koichi Sasada * ndk_logger.rb : ignore IOError when logging * plugins/googlebot.nb : add option ':googlec_maxwords' 2004-07-05(Mon) 08:02:43 +0900 Koichi Sasada * nadokarc : auto detect filename encoding(carelessly) 2004-07-05(Mon) 07:28:17 +0900 Koichi Sasada * nadokarc, ndk_config.rb : add 'Mode' option and Channel_Info[:initial_mode] option * nadokarc, ndk_config.rb : add 'FilenameEncoding' option * ndk_logger.rb, ndk_config.rb : support japanese character encoding channel name * ndk_config.rb : fix canonical_channel_name and add identical_channel_name * ndk_manager.rb : support "/nadoka reconnect" command * ndk_manager.rb : ping to server and check this response, due to check connection * ndk_manager.rb, ndk_state.rb : fix behavior when server connection is closed 2004-07-04(Sun) 17:50:06 +0900 Koichi Sasada * ndk_logger.rb : synchronize file output 2004-07-04(Sun) 16:15:24 +0900 Koichi Sasada * plugins/googlebot.nb : fix character code bugs 2004-07-03(Sat) 03:25:24 +0900 Koichi Sasada * plugins/googlebot.nb : added * nadokarc : add default join channel '#nadoka_check' 2004-06-01(Tue) 16:51:29 +0900 Koichi Sasada * plugins/checkbot.nb : added(incomplete) * plugins/rss_check.nb : change notice format * ndk_client.rb : fix login messages(commands) 2004-05-14(Fri) 02:36:04 +0900 Koichi Sasada * ndk_manager.rb : fix part process at enter_away 2004-05-14(Fri) 02:27:06 +0900 Koichi Sasada * ndk_config.rb(canonical_channel_name) : added and all configuration keys of channel information are canonical * ndk_state.rb(canonical_channel_name) : added and all state keys of channel are canonical 2004-05-09(Sun) 18:11:30 +0900 Koichi Sasada * ndk_manager.rb : correct command to clients 2004-05-09(Sun) 17:27:46 +0900 Koichi Sasada * ndk_manager.rb : change away nick lag time to 2 sec 2004-05-09(Sun) 16:46:30 +0900 Koichi Sasada * rice/irc.rb : fix some regexp bugs * ndk_client.rb(send_motd) : use client original nick * ndk_client.rb ; check io is still open * plugins/rss_check.nb : change string compare operator from '=~' to '==' 2004-05-09(Sun) 03:33:29 +0900 Koichi Sasada * ndk_manager.rb, ndk_state.rb(nick_succ) : change nick.succ algorithm and timing(always change) * ndk_manager.rb : refactoring around nick and away nick * ndk_config.rb : change login timing action default to do nothing 2004-05-05(Wed) 17:19:14 +0900 Koichi Sasada * ndk_version.rb : 0.5.5 prepared 2004-05-05(Wed) 16:24:03 +0900 Koichi Sasada * ChangeLog : change date format * 0.5.4 released Sun, 02 May 2004 03:34:54 +0900 Koichi Sasada * nadoka.rb : add location of nadoka.rb to load path Sat, 01 May 2004 23:58:55 +0900 Koichi Sasada * ndk_manager.rb : support nadoka control with signal and bot event on_sigusr[12] Sat, 01 May 2004 23:04:40 +0900 Koichi Sasada * ndk_manager.rb : fix typo Sat, 01 May 2004 19:11:29 +0900 Koichi Sasada * change source management system to Subversion Sat, 01 May 2004 14:38:04 +0900 Koichi Sasada * ndk_manager.rb : fix bootstrap process * rice/irc.rb : add easy flood control * 0.5.3 released Sat, 01 May 2004 14:03:52 +0900 Koichi Sasada * nadokarc : fix typo Sat, 01 May 2004 13:24:14 +0900 Koichi Sasada * ndk_config.rb : add nadoka server name * nadokarc : fix IRCnet servers * 0.5.2 released Sat, 01 May 2004 06:35:28 +0900 Koichi Sasada * ndk_logger.rb : added * ndk_bot.rb : add @bot_config * ndk_* : use @logger instead of @config to logging * ndk_config.rb : extend BotConfig format * ndk_manager.rb, rice/irc.rb : fix reconnect process * some refactoring Fri, 30 Apr 2004 02:57:46 +0900 Koichi Sasada * ndk_manager.rb : ignore(and report) exception while '/nadoka reload' reloading Wed, 28 Apr 2004 11:24:23 +0900 Koichi Sasada * ndk_err.rb : change exception hierarchy * ndk_manager.rb : dup filtered message Wed, 28 Apr 2004 10:31:29 +0900 Koichi Sasada * 0.5.1 released Wed, 28 Apr 2004 02:37:34 +0900 Koichi Sasada * plugins/shellbot.nb : remove debug print Wed, 28 Apr 2004 02:07:06 +0900 Koichi Sasada * plugins/evalbot.nb, plugins/shellbot.nb : added * ndk_bot.rb : add on_client_privmsg, on_nadoka_command * ndk_err.rb : add some exception class * ndk_manager.rb : fix some bugs Tue, 27 Apr 2004 22:29:07 +0900 Koichi Sasada * ndk_version.rb : added * ndk_manager.rb : add Privmsg_Filter, Notice_Filter Mon, 26 Apr 2004 10:46:49 +0900 Koichi Sasada * 0.5.0 released Mon, 26 Apr 2004 10:29:47 +0900 Koichi Sasada * ndk_manager.rb : change name on_message_default to on_every_message * ndk_manager.rb, ndk_bot.rb : change first argument of bot event handlers to PrefixObject Sun, 25 Apr 2004 22:59:07 +0900 Koichi Sasada * plugins/rss_check.nb : add on_privmsg * ndk_manager.rb : make ndk_error message more detail Sun, 25 Apr 2004 20:47:16 +0900 Koichi Sasada * ndk_manager.rb, ndk_bot.rb : fix typo(bot_destruct) * ndk_err.rb : add NDK_BotBreak * ndk_state.rb : remove debug output * plugins/rss_check.rb : exception handling when translation error Sun, 25 Apr 2004 18:21:30 +0900 Koichi Sasada * ndk_manager.rb : add on_message_default Sun, 25 Apr 2004 14:55:56 +0900 Koichi Sasada * rss_check.rb : fix recent check Sat, 24 Apr 2004 23:48:07 +0900 Koichi Sasada * ndk_manager.rb : fix bug TCPServer.new. reported by Tadaaki OKABE . * ndk_config.rb : fix ndk_config.rb script. * rss_check.rb : add LIRS support * rss_check.nb : add space around uri Sat, 24 Apr 2004 13:07:22 +0900 Koichi Sasada * rss_check.rb, plugins/rss_check.nb : added Sat, 24 Apr 2004 02:37:47 +0900 Koichi Sasada * ndk_config.rb : fix to use string strip Sat, 24 Apr 2004 01:20:55 +0900 Koichi Sasada * ndk_manager.rb, etc : introduce invoke_event mechanism * ndk_config.rb : support Quit_Message which showed when program is quitting * ndk_config.rb : support Channel_info{"ch" => {:part_message => '...'}} * ndk_manager.rb, etc : fix exception handling with thread * ndk_manager.rb : support /nadoka restart Fri, 23 Apr 2004 22:27:17 +0900 Koichi Sasada * ndk_state.rb : remove debug print Fri, 23 Apr 2004 19:56:54 +0900 Koichi Sasada * support tracing channel state. so nadoka doesn't need to command NAMES when a client join * ndk_manager.rb : support channel join when client login (:timing => :login) by U.Nakamura * ndk_config.rb : fix topic log format and output * ndk_manager.rb, ndk_config.rb : add Client_server_host * 0.4.0 released Fri, 23 Apr 2004 10:35:04 +0900 Koichi Sasada * ndk_manger.rb, ndk_client.rb, ndk_config.rb : support away action with Away_Message, Away_Nick * nadoka.rb : fix version string * ndk_config.rb : ACL -> ACL_Object * 0.3.5 released Fri, 23 Apr 2004 02:45:13 +0900 Koichi Sasada * add a copyright statement to each file * fix a ACL bug Thu, 22 Apr 2004 18:31:39 +0900 Koichi Sasada * rice/irc.rb : mitigate IRC message check(CRLF) Thu, 22 Apr 2004 17:58:25 +0900 Koichi Sasada * ndk_manager.rb : fix on_timer event call * 0.3.4 released Thu, 22 Apr 2004 17:01:45 +0900 Koichi Sasada * ndk_client.rb : fix that send message to a client which has not logged in. * ndk_config.rb : fix ${channel_name} sanitizing * ndk_client.rb (login) : fix client login sequence * ndk_config.rb : fix expand dir with log_dir * ndk_manager.rb : close when client socket when client closed * ndk_config.rb : add Client_server_acl * ndk_config.rb : GC.start when reload config * 0.3.3 released Thu, 22 Apr 2004 11:02:00 +0900 Koichi Sasada * ndk_bot.rb : add on_client_login, on_client_logout event * plugins/sixamo.nb : ver. up sixamo bot san * 0.3.2 released Wed, 21 Apr 2004 21:19:27 +0900 Koichi Sasada * ndk_manager.rb (logging) : add some logging action * ndk_manager.rb : fix a ctcp VERSION bug. Wed, 21 Apr 2004 20:00:59 +0900 Koichi Sasada * ndk_config.rb : change the Server information setting format * ndk_err.rb : add Nadoka::NDK_RestartProgram(not used) * 0.3.0 released Wed, 21 Apr 2004 18:32:21 +0900 Koichi Sasada * plugins/sixamo.nb : modify to use configuration * ndk_config.rb : Hostname use Socket.gethostname Wed, 21 Apr 2004 13:48:42 +0900 Koichi Sasada * plugins/sixamo.nb : added Wed, 21 Apr 2004 02:32:26 +0900 Koichi Sasada * ndk_manager.rb, ndk_client.rb : move 'send_from_client' method to ndk_client * ndk_err.rb : add NDK_QuitProgram class Tue, 20 Apr 2004 18:06:47 +0900 Koichi Sasada * ndk_manager.rb (send_to_bot) : fix bot round robin routine * ndk_bot.rb : add NDK_BotBreak to interrupt bot round robin * ndk_config.rb : $C encode with URI.encode * ndk_config.rb : Bots -> BotFiles * ndk_config.rb : add Log_TimeFormat * ndk_config.rb : Plugin_dir suppoort Enumerable Tue, 20 Apr 2004 13:33:19 +0900 Koichi Sasada * 0.1 released nadoka-0.10.0/lib/000077500000000000000000000000001371026053200135725ustar00rootroot00000000000000nadoka-0.10.0/lib/notify_socket.rb000066400000000000000000000044211371026053200170000ustar00rootroot00000000000000# frozen_string_literal: true # The MIT License (MIT) # # Copyright (c) 2019 Kazuhiro NISHIYAMA # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # see https://www.freedesktop.org/software/systemd/man/sd_notify.html class NotifySocket def initialize(path=ENV['NOTIFY_SOCKET']) @notify_socket = nil return unless path @notify_socket = Addrinfo.unix(path, :DGRAM).connect end def []=(key, value) return unless @notify_socket @notify_socket.puts "#{key}=#{value}" end def ready! self['READY'] = 1 end def reloading! self['RELOADING'] = 1 end def stopping! self['STOPPING'] = 1 end # notify_socket.status = 'Completed 66% of file system check…' def status=(state) self['STATUS'] = state end # notify_socket.errno = 2 def errno=(error_code) self['ERRNO'] = error_code end # notify_socket.buserror = 'org.freedesktop.DBus.Error.TimedOut' def buserror=(error_code) self['BUSERROR'] = error_code end # notify_socket.mainpid = 4711 def mainpid=(pid) self['MAINPID'] = pid end def watchdog! self['WATCHDOG'] = 1 end # notify_socket.watchdog_usec = 20_000_000 def watchdog_usec=(usec) self['WATCHDOG_USEC'] = usec end def extend_timeout_usec=(usec) self['EXTEND_TIMEOUT_USEC'] = usec end end nadoka-0.10.0/lib/rss_check.rb000066400000000000000000000103631371026053200160660ustar00rootroot00000000000000# # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # Create : K.S. Sat, 24 Apr 2004 12:10:31 +0900 # require "rss/parser" require "rss/1.0" require "rss/2.0" require "rss/syndication" require "rss/dublincore" require "open-uri" require 'uri' require 'yaml/store' require 'csv' require 'stringio' require 'zlib' class RSS_Check class RSS_File def initialize path, init_now @uri = URI.parse(path) @entry_time = @file_time = (init_now ? Time.now : Time.at(0)) end def check begin if (mt=mtime) > @file_time @file_time = mt check_entries else [] end rescue => e [{ :about => e.message, :title => "RSS Check Error (#{@uri})", :ccode => 'UTF-8' }] end end def date_of e if e.respond_to? :dc_date e.dc_date || Time.at(0) else e.pubDate || Time.at(0) end end def check_entries rss = RSS::Parser.parse(read_content, false) et = @entry_time items = rss.items.sort_by{|e| date_of(e) }.map{|e| e_date = date_of(e) if e_date > @entry_time if e_date > et et = e_date end { :about => e.about, :title => e.title, :ccode => 'UTF-8' } end }.compact @entry_time = et items end def read_content case @uri.scheme when 'http', 'https' open(@uri){|f| if f.content_encoding.any?{|e| /gzip/ =~ e} Zlib::GzipReader.new(StringIO.new(f.read)).read || '' else f.read end } else open(@uri.to_s){|f| f.read } end end def mtime case @uri.scheme when 'http', 'https' open(@uri){|f| f.last_modified || Time.now } else File.mtime(@rss_file) end end end class LIRS_File < RSS_File def check_entries et = @entry_time res = [] CSV::Reader.parse(read_content){|row| last_detected = Time.at(row[2].data.to_i) if last_detected > @entry_time && row[1].data != row[2].data if last_detected > et et = last_detected end res << { :about => row[5].data, :title => row[6].data, :ccode => 'EUC-JP' } end } @entry_time = et res end end def initialize paths, cache_file=nil, init_now=false @paths = paths @db = YAML::Store.new(cache_file) if cache_file @rss_files = paths.map{|uri| load_file(uri) || if /LIRS:(.+)/ =~ uri LIRS_File.new($1, init_now) else RSS_File.new(uri, init_now) end } end def check @rss_files.map{|rf| rf.check }.flatten end def save debug = $DEBUG $DEBUG = false @db.transaction{ @paths.each_with_index{|path, i| @db[path] = @rss_files[i] } } if @db ensure $DEBUG = debug end def load_file file debug = $DEBUG $DEBUG = false @db.transaction{ @db[file] } if @db ensure $DEBUG = debug end def clear debug = $DEBUG $DEBUG = false if @db @db.transaction{ @db.keys.each{|k| @db.delete k } } end ensure $DEBUG = debug end end if $0 == __FILE__ rss_uri = %w( http://www.ruby-lang.org/ja/index.rdf http://slashdot.jp/slashdotjp.rss http://www3.asahi.com/rss/index.rdf http://pcweb.mycom.co.jp/haishin/rss/index.rdf http://japan.cnet.com/rss/index.rdf http://blog.japan.cnet.com/umeda/index.rdf http://jvn.doi.ics.keio.ac.jp/rss/jvnRSS.rdf ) lirs_uri = [ 'LIRS:http://www.rubyist.net/~kazu/samidare/sites.lirs.gz' ] rssc = RSS_Check.new( rss_uri + lirs_uri, ARGV.shift || './rss_cache', false # false ) require 'kconv' rssc.check.each{|e| puts e[:about] title = (e[:ccode] == 'UTF-8') ? e[:title].toeuc : e[:title] puts title } rssc.dump end nadoka-0.10.0/lib/tagparts.rb000066400000000000000000000074261371026053200157550ustar00rootroot00000000000000# # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id:$ # require 'cgi' module TagParts class TagItem include Enumerable def initialize tag, body, ignore_empty = false @tag = tag.to_s @attr = {} @body = [] @ignore_empty = ignore_empty body.each{|e| add! e } end attr_reader :body, :tag, :attr def make_attr_str @attr.map{|k,v| " #{CGI.escapeHTML(k.to_s)}='#{CGI.escapeHTML(v)}'" }.join end def to_s if @body.size > 0 || @ignore_empty body = @body.flatten.map{|e| if e.kind_of? String CGI.escapeHTML(e.to_s) else e.to_s end } "<#{@tag}#{make_attr_str}\n>#{body}\n" else "<#{@tag}#{make_attr_str} /\n>" end end def inspect ">" end def add!(elem) if elem.kind_of? Hash @attr.update elem else @body << elem end end def [](k) @attr[k] end def []=(k, v) @attr[k] = v end def each @body.flatten.each{|e| yield e } end def each_leaf @body.each{|e| if e.kind_of? TagItem e.each_leaf(&Proc.new) else yield e end } end def each_node yield(self) @body.each{|e| if e.kind_of? TagItem e.each_node(&Proc.new) else yield e end } end alias << add! end def ignore_empty_tag? false end # do nothing. please override def tag_encoding str str end def tree2string tag tag_encoding(tree2string_(tag)) end def tree2string_ tag bs = tag.map{|body| if body.kind_of? TagItem tree2string_(body) else CGI.escapeHTML(body.to_s) end } tagname = tag.tag attr = tag.make_attr_str if bs.size > 0 || ignore_empty_tag? "<#{tagname}#{attr}\n>#{bs}\n" else "<#{tagname}#{attr}\n/>" end end @@method_prefix = '_' def self.newtag sym, ignore_empty, klass = TagParts klass.module_eval <<-EOS def #{@@method_prefix}#{sym}(*args) TagItem.new(:#{sym}, args, #{ignore_empty}) end EOS end TagParts.module_eval <<-EOS def #{@@method_prefix}(tag, *args) TagItem.new(tag, args, false) end EOS def method_missing m, *args if make_unknown_tag? && (/^#{@@method_prefix}(.+)/ =~ m.to_s) TagItem.new($1, args) else super end end def make_unknown_tag? true end end module HTMLParts include TagParts def make_unknown_tag? false end def ignore_empty_tag? true end # copy from cgi.rb PARTS_1 = %w{ TT I B BIG SMALL EM STRONG DFN CODE SAMP KBD VAR CITE ABBR ACRONYM SUB SUP SPAN BDO ADDRESS DIV MAP OBJECT H1 H2 H3 H4 H5 H6 PRE Q INS DEL DL OL UL LABEL SELECT OPTGROUP FIELDSET LEGEND BUTTON TABLE TITLE STYLE SCRIPT NOSCRIPT TEXTAREA FORM A BLOCKQUOTE CAPTION } PARTS_2 = %w{ IMG BASE BR AREA LINK PARAM HR INPUT COL META } PARTS_3 = %w{ HTML BODY P DT DD LI OPTION THEAD TFOOT TBODY COLGROUP TR TH TD HEAD } (PARTS_1 + PARTS_2 + PARTS_3).each{|e| elem = e.downcase TagParts.newtag elem, true } end __END__ include HTMLParts tags = _html( _head( _title('hogehoge')), _body( _br(), _h1('huga-'), _p('hogehoge', _a('hogehoge', 'href' => 'dokka'), 'huga'), _p('hogehoge', 'huga', ['ho-', 'hu']) )) puts tags.to_s puts tree2string(tags) p( tags.to_s == tree2string(tags) ) nadoka-0.10.0/nadoka.gemspec000066400000000000000000000020431371026053200156250ustar00rootroot00000000000000# -*- encoding: utf-8 -*- $LOAD_PATH.unshift File.dirname(__FILE__) require 'ndk/version' Gem::Specification.new do |s| s.name = "nadoka" s.version = Nadoka::VERSION s.authors = ["Kazuhiro NISHIYAMA", "SASADA Koichi"] s.email = ["kzhr.nsym\@gmail.com"] s.homepage = "https://github.com/nadoka/nadoka" s.summary = %q{IRC logger, monitor and proxy program ("bot")} s.description = %q{ Nadoka is a tool for monitoring and logging IRC conversations and responding to specially formatted requests. You define and customize these responses in Ruby. Nadoka is conceptually similar to Madoka, an older proxy written in Perl. }.tr_s(" \n", " ").strip s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] # specify any dependencies here; for example: # s.add_development_dependency "rspec" # s.add_runtime_dependency "rest-client" end nadoka-0.10.0/nadoka.rb000077500000000000000000000047671371026053200146270ustar00rootroot00000000000000#!/usr/bin/env ruby # ## ## Nadoka: ## Irc Client Server Program ## # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # Create : K.S. 03/07/10 20:29:07 # $LOAD_PATH.unshift File.dirname(__FILE__) require 'ndk/version' if $0 == __FILE__ require 'optparse' require 'ndk/server' require 'ndk/bot' $stdout.sync=true $NDK_Debug = false if ENV.key?('NOTIFY_SOCKET') require 'lib/notify_socket' $NDK_NOTIFY_SOCKET = NotifySocket.new else $NDK_NOTIFY_SOCKET = Object.new def $NDK_NOTIFY_SOCKET.method_missing(*) # ignore end end unless defined? Process.daemon def Process.daemon(nochdir = nil, noclose = nil) exit!(0) if fork Process.setsid exit!(0) if fork Dir.chdir('/') unless nochdir File.umask(0) unless noclose STDIN.reopen('/dev/null') STDOUT.reopen('/dev/null', 'w') STDERR.reopen('/dev/null', 'w') end end end rcfile = nil pidfile = nil daemon = false optparse = OptionParser.new{|opts| opts.banner = "Usage: ruby #{$0} [options]" opts.separator "" opts.separator "Require options:" opts.on("-r", "--rc [RCFILE]", "Specify rcfile(required)"){|f| rcfile = f } opts.separator "" opts.separator "Optional:" opts.on("-d", "--debug", "Debug Nadoka"){ $NDK_Debug = true $DEBUG = true puts 'Enter Nadoka Debug mode' } opts.on("--daemon", "run as daemon"){ daemon = true } opts.on("--pid [PIDFILE]", "Put process pid into PIDFILE"){|f| pidfile = f } opts.separator "" opts.separator "Common options:" opts.on_tail("-h", "--help", "Show this message"){ puts Nadoka.version puts opts exit } opts.on_tail("-v", "--version", "Show version"){ puts Nadoka.version } } optparse.parse!(ARGV) unless rcfile puts Nadoka.version puts optparse exit end if daemon Process.daemon end if pidfile open(pidfile, "w") {|f| f.puts Process.pid } end begin GC.start Nadoka::NDK_Server.new(rcfile).start rescue Nadoka::NDK_QuitProgram $NDK_NOTIFY_SOCKET.stopping! rescue Nadoka::NDK_RestartProgram $NDK_NOTIFY_SOCKET.reloading! GC.start ObjectSpace.each_object(Socket) {|sock| sock.close} retry rescue Exception => e $NDK_NOTIFY_SOCKET.stopping! open('nadoka_fatal_error', 'w'){|f| f.puts e f.puts e.backtrace.join("\n") } end if pidfile File.unlink(pidfile) end end nadoka-0.10.0/nadokarc000066400000000000000000000170701371026053200145360ustar00rootroot00000000000000## -*-ruby-*- vim: set filetype=ruby : ## ## Nadoka Sample resource file ## $Id$ # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # class NADOKA_Config < Nadoka::NDK_ConfigBase ############################################### # System setting # Setting_name = 'IRCNet' # # If you don't specify Setting_name, # # system use rc file name as setting name # # if your rc file is "test.rc", setting name is "test" # # (remove last .rc) # # 0: quiet # 1: ch log only # 2: normal # 3: debug Loglevel = 2 ############################################### # client server setting Client_server_port = 6667 # or nil (no listen) Client_server_host = nil # You can specify binding host(interface) Client_server_pass = 'NadokaPassWord' Client_server_acl = nil Client_server_ssl_cert_file = nil Client_server_ssl_key_file = nil # ACL(Access Control List) example: # Client_server_acl = %q{ # deny all # allow 192.168.1.1 # allow 192.168.2.0/24 # } # ACL setting must be String # # or you can set acl directly like follows: # # ACL_Object = ::ACL.new(...) # Create a self-signed SSL certificate: # $ openssl req -new -days 365 -x509 -nodes -out cert.pem -keyout key.pem # # And specify cert files: # Client_server_ssl_cert_file = 'cert.pem' # Client_server_ssl_key_file = 'key.pem' ############################################### # server setting # IRCnet servers in Japan # see http://www.ircnet.ne.jp/servers.html Servers = [ { :host => 'irc.ircnet.ne.jp', :port => 6667, # default: 6667 :pass => nil, # default: nil }, # { :host => 'irc.media.kyoto-u.ac.jp', # :port => (6660 .. 6669), # }, # { :host => 'irc.huie.hokudai.ac.jp', # without :port, use 6667 as default port # }, # IPv6 Sample # { # :host => 'irc6.ircnet.ne.jp', # :port => 6667, # }, # { # :host => 'dh6.ircnet.ne.jp', # :port => (6660 .. 6669), # }, ] #Servers = [ # SSL Sample # { # :host => 'ircs.example.net', # :port => 6697, # :ssl_params => { # with :ssl_params hash, use ssl # # :ca_cert => "/etc/ssl/certs", # default: openssl's default certificates # }, # }, #] ############################################### # userinfo User = ENV['USER'] || ENV['USERNAME'] || 'nadokatest' Nick = ENV['USER'] || ENV['USERNAME'] || 'ndk_nick' Hostname = Socket.gethostname Realname = 'nadoka user' Mode = nil Away_Message = 'away' # If this item is String, your nick will # be that when no clients are connected. Away_Nick = nil # Quit_Message = "Quit Nadoka" ############################################### # channel info # log filename format # ${setting_name} : Setting name # ${channel_name} : Channel name # %? : Time#strftime format(see ruby reference) # Default_log = { :file => '${setting_name}-${channel_name}/%y%m%d.log', :time_format => '%H:%M:%S', :message_format => { 'PRIVMSG' => '<{nick}> {msg}', 'NOTICE' => '{{nick}} {msg}', 'JOIN' => '+ {nick} ({prefix:user}@{prefix:host})', 'NICK' => '* {nick} -> {newnick}', 'QUIT' => '- {nick} (QUIT: {msg}) ({prefix:user}@{prefix:host})', 'PART' => '- {nick} (PART: {msg}) ({prefix:user}@{prefix:host})', 'KICK' => '- {nick} was kicked by {kicker} ({msg})', 'MODE' => '* {nick} changed mode ({msg})', 'TOPIC' => '* TOPIC: {msg} (by {nick})', 'SYSTEM' => '[NDK] {orig}', 'OTHER' => '{orig}', 'SIMPLE' => '{orig}', }, } System_log = { :file => '${setting_name}-system.log', :time_format => '%y/%m/%d-%H:%M:%S', :message_format => { 'PRIVMSG' => '{ch} <{nick}> {msg}', 'NOTICE' => '{ch} {{nick}} {msg}', 'JOIN' => '{ch} + {nick} ({prefix:user}@{prefix:host})', 'NICK' => '{ch} * {nick} -> {newnick}', 'QUIT' => '{ch} - {nick} (QUIT: {msg}) ({prefix:user}@{prefix:host})', 'PART' => '{ch} - {nick} (PART: {msg}) ({prefix:user}@{prefix:host})', 'KICK' => '{ch} - {nick} was kicked by {kicker} ({msg})', 'MODE' => '{ch} * {nick} changed mode ({msg})', 'TOPIC' => '{ch} * TOPIC: {msg} (by {nick})', 'SYSTEM' => '[NDK] {orig}', 'OTHER' => nil, 'SIMPLE' => nil, }, } Talk_log = { :file => '${setting_name}-talk/%y%m%d.log', :time_format => Default_log[:time_format], :message_format => { 'PRIVMSG' => '[{sender} => {receiver}] {msg}', 'NOTICE' => '{{sender} => {receiver}} {msg}', } } Channel_info = { # nadoka talk (from Japanese server only) '#nadoka' => { # timing # :startup / when nadoka start up <= default # :login / when user login # otherwise nadoka won't login automatically :timing => :startup, :log => '${setting_name}-nadoka-chan/%y%m%d.log', # :part_message => "bye bye" # :initial_mode => "+s" }, '#nadoka:*.jp' => { :timing => :startup, :log => '${setting_name}-nadoka-chan-jp/%y%m%d.log', }, # nadoka bot channel '#nadoka_check' => { :timing => :startup, :log => '${setting_name}-nadoka-check/%y%m%d.log', # you can specify store backlog lines :backlog_lines => 5, }, # # '#log_setting_example' => { # :log => { # :file => 'logfilename', # log filename # # :io => $stderr, # or IO object is supported # :time_format => '%H:%M:%S', # # :channel_name_in_file_name => 'hoge' # # :message_format => { # 'PRIVMSG' => '<{nick}> {msg}', # 'NOTICE' => '{{nick}} {msg}', # 'JOIN' => '+ {nick} to {ch}', # 'NICK' => '* {nick} -> {newnick}', # 'QUIT' => '- {nick} ({msg})', # 'PART' => '- {nick} from {ch} ({msg})', # 'KICK' => '- {nick} kicked by {kicker} ({msg}) from {ch}', # 'MODE' => '* {nick} changed mode ({msg})', # 'TOPIC' => '<{ch} TOPIC> {msg}', # 'SYSTEM' => '[NDK] {msg}', # 'OTHER' => nil, # 'SIMPLE' => nil, # # # for more complex log output: # # :logwriterclass => your_log_writer_class, # # :logwriter => your_log_writer_instance, # # :other_logwriterclass_specific_setting => ..., # }, # }, } BackLog_Lines = 20 # # FilenameEncoding = 'euc' # FilenameEncoding = 'sjis' # FilenameEncoding = 'utf8' # # If you not specify FilenameEncoding, nadoka infers # suitable encoding (see ndk/config.rb). # ############################################### # Directory Log_dir = './log' #Plugins_dir = './plugins' Plugins_dir = Default_Plugins_dir # You can set Plugins_dir as Enumerable object. # ex: ['./dir1', './dir2', ...] ############################################### # Bots BotConfig = [ # :BotName1, # :BotName1, # 'BotName1', # :BotName2, # { :name => :BotName3, # :set1 => setting1, # }, # { :name => :BotName3, # :set1 => setting2, # }, # { :name => :BotName4, # :set1 => setting3, # }, :BackLogBot, # strongly recommended ] ############################################### # Misc Privmsg_Filter = nil Notice_Filter = nil end nadoka-0.10.0/ndk/000077500000000000000000000000001371026053200136005ustar00rootroot00000000000000nadoka-0.10.0/ndk/bot.rb000066400000000000000000000121341371026053200147120ustar00rootroot00000000000000# # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's licence. # # # $Id$ # Create : K.S. 04/04/19 00:39:48 # # # To make bot for nadoka, see this code. # module Nadoka class NDK_Bot # To initialize bot instance, please override this. def bot_initialize # do something end # This method will be called when reload configuration. def bot_destruct # do something end # override me def bot_state info = "#<#{self.class}: #{@bot_config.inspect}>" if info.length > 100 info[0..100] + '...' else info end end # To access bot configuration, please use this. # # in configuration file, # BotConfig = [ # :BotClassName1, # :BotClassName2, # { # :name => :BotClassName3, # :setX => X, # :setY => Y, # ... # }, # ] # # You can access above setting via @bot_config # def bot_init_utils bot_init_available_channel bot_init_same_bot end def bot_init_available_channel if @bot_config.key?(:channels) channels = '\A(?:' + @bot_config[:channels].collect{|ch| Regexp.quote(ch) }.join('|') + ')\z' @available_channel = Regexp.compile(channels) else @available_channel = @bot_config[:ch] || // end end def bot_init_same_bot @same_bot = @bot_config[:same_bot] || /(?!)/ end def same_bot?(ch) if @state.channel_users(ccn(ch)).find{|x| @same_bot =~ x } true else false end end def ccn2rcn ccn chs = @manager.state.current_channels[ccn] chs ? chs.name : ccn end # Mostly, you need this method. def send_notice ch, msg rch = ccn2rcn(ch) msg = Cmd.notice(rch, msg) @manager.send_to_server msg @manager.send_to_clients_otherwise msg, nil end # Usually, you must not use this def send_privmsg ch, msg rch = ccn2rcn(ch) msg = Cmd.privmsg(rch, msg) @manager.send_to_server msg @manager.send_to_clients_otherwise msg, nil end # Change user's mode as 'mode' on ch. def change_mode ch, mode, user rch = ccn2rcn(ch) send_msg Cmd.mode(rch, mode, user) end # Change your nick to 'nick'. def change_nick nick send_msg Cmd.nick(nick) end # Send command or reply(?) to server. def send_msg msg @manager.send_to_server msg end # ccn or canonical_channel_name def canonical_channel_name ch @config.canonical_channel_name ch end alias ccn canonical_channel_name =begin # ... # def on_[IRC Command or Reply(3 digits)] prefix(nick only), param1, param2, ... # # end # # like these def on_privmsg prefix, ch, msg end def on_join prefix, ch end def on_part prefix, ch, msg='' end def on_quit prefix, msg='' end def on_xxx prefix, *params end In above methods, you can access nick, user, host information via prefix argument variable like this. - prefix.nick - prefix.user - prefix.host ###### # special event # This method will be called when received every message def on_every_message prefix, command, *args # end # if 'nick' user quit client and part ch, this event is called. def on_quit_from_channel ch, nick, qmsg # do something end # It's special event that will be called about a minute. def on_timer timeobj # do something end # It's special event that will be called when new client join. def on_client_login client_count, client # do something end # It's special event that will be called when a client part. def on_client_logout client_count, client # do something end # undocumented def on_client_privmsg client, ch, msg # do something end # undocumented def on_nadoka_command client, command, *params # do something end # undocumented def on_server_connected # do something end # on signal 'sigusr[12]' trapped def on_sigusr[12] # no arguments # do something end You can access your current state on IRC server via @state. - @state.nick # your current nick - @state.channels # channels which you are join ['ch1', 'ch2', ...] # need canonicalized channel name - @state.channel_users(ch) # channel users ['user1', ...] - @state.channel_user_mode(ch, nick) =end Cmd = ::Nadoka::Cmd Rpl = ::Nadoka::Rpl def initialize manager, config, bot_config @manager = manager @config = config @logger = config.logger @state = manager.state @bot_config = bot_config bot_initialize end def config @bot_config end def self.inherited klass NDK_Config::BotClasses[klass.name.downcase.intern] = klass end end end nadoka-0.10.0/ndk/client.rb000066400000000000000000000174151371026053200154130ustar00rootroot00000000000000# # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # Create : K.S. 04/04/17 16:50:10 # require 'thread' module Nadoka class NDK_Client def initialize config, sock, manager @sock = sock @config = config @logger = config.logger @manager= manager @state = manager.state @queue = Queue.new @remote_host = @sock.peeraddr[2] @thread = Thread.current @connected = false # client information @realname = nil @hostname = nil end attr_writer :logger attr_reader :remote_host def start send_thread = Thread.new{ begin while q = @queue.pop begin send_to_client q end end rescue Exception => e @manager.ndk_error e end } begin if login @connected = true begin @manager.invoke_event :leave_away, @manager.client_count @manager.invoke_event :invoke_bot, :client_login, @manager.client_count, self while msg = recv_from_client send_from_client msg, self end rescue NDK_QuitClient # finish ensure @manager.invoke_event :invoke_bot, :client_logout, @manager.client_count, self end end rescue Exception @manager.ndk_error $! ensure @logger.slog "Client #{@realname}@#{@remote_host} disconnected." @sock.close send_thread.kill if send_thread && send_thread.alive? end end def kill @thread && @thread.kill end def recv_from_client while !@sock.closed? begin str = @sock.gets if str msg = ::RICE::Message::parse str case msg.command when 'PING' send_msg Cmd.pong(*msg.params[0]), false when 'PONG' # ignore else @logger.dlog "[C>] #{str}" return msg end else break end rescue ::RICE::UnknownCommand, ::RICE::InvalidMessage @logger.slog "Invalid Message: #{str}" rescue Exception => e @manager.ndk_error e break end end end def push msg if @connected @queue << msg end end alias << push def login pass = nil nick = nil @username = nil while (nick == nil) || (@username == nil) msg = recv_from_client return nil if msg == nil case msg.command when 'USER' @username, @hostname, @servername, @realname = msg.params when 'NICK' nick = msg.params[0] when 'PASS' pass = msg.params[0] else raise "Illegal login sequence: #{msg}" end end if @config.client_server_pass && (@config.client_server_pass != pass) send_reply Rpl.err_passwdmismatch(nick, "Password Incorrect.") return false end send_reply Rpl.rpl_welcome( nick, 'Welcome to the Internet Relay Network '+"#{nick}!#{@username}@#{@remote_host}") send_reply Rpl.rpl_yourhost(nick, "Your host is nadoka, running version #{NDK_Version}") send_reply Rpl.rpl_created( nick, 'This server was created ' + NDK_Created.asctime) send_reply Rpl.rpl_myinfo( nick, "nadoka #{NDK_Version} aoOirw abeiIklmnoOpqrstv") send_motd(nick) send_command Cmd.nick(@state.nick), nick nick = @manager.state.nick @manager.state.current_channels.each{|ch, chs| send_command Cmd.join(chs.name) if chs.topic send_reply Rpl.rpl_topic(@state.nick, chs.name, chs.topic) else send_reply Rpl.rpl_notopic(@state.nick, chs.name, "No topic is set.") end send_reply Rpl.rpl_namreply( @state.nick, chs.state, chs.name, chs.names.join(' ')) send_reply Rpl.rpl_endofnames(@state.nick, chs.name, "End of NAMES list.") } @logger.slog "Client #{@realname}@#{@remote_host} connected." true end def send_motd nick send_reply Rpl.rpl_motdstart(nick, "- Nadoka Message of the Day - ") send_reply Rpl.rpl_motd(nick, "- Enjoy IRC chat with Nadoka chan!") send_reply Rpl.rpl_motd(nick, "- ") send_reply Rpl.rpl_endofmotd(nick, "End of MOTD command.") end # :who!~username@host CMD .. def send_command cmd, nick = @manager.state.nick msg = add_prefix(cmd, "#{nick}!#{@username}@#{@remote_host}") send_msg msg end # :serverinfo REPL ... def send_reply repl msg = add_prefix(repl, @config.nadoka_server_name) send_msg msg end def send_msg msg, logging=true @logger.dlog "[C<] #{msg}" if logging unless @sock.closed? begin @sock.write msg.to_s rescue Exception => e @manager.ndk_error e end end end def send_to_client msg if /^\d+/ =~ msg.command send_reply msg else send_msg msg end end def add_prefix cmd, prefix = "#{@manager.state.nick}!#{@username}@#{@remote_host}" cmd.prefix = prefix cmd end def add_prefix2 cmd, nick cmd.prefix = "#{nick}!#{@username}@#{@remote_host}" cmd end ::RICE::Command.regist_command('NADOKA') # client -> server handling def send_from_client msg, from until @manager.connected # ignore return end case msg.command when 'NADOKA' nadoka_client_command msg.params[0], msg.params[1..-1] return when 'QUIT' raise NDK_QuitClient when 'PRIVMSG', 'NOTICE' if /^\/nadoka/ =~ msg.params[1] _, cmd, *args = msg.params[1].split(/\s+/) nadoka_client_command cmd, args return end if @manager.send_to_bot(:client_privmsg, self, msg.params[0], msg.params[1]) @manager.send_to_server msg @manager.send_to_clients_otherwise msg, from end else @manager.send_to_server msg end end def client_notice msg self << Cmd.notice(@state.nick, msg) end def state "client from #{@remote_host}(#{@username}, #{@hostname}, #{@servername}, #{@realname})" end NdkCommandDescription = { # 'QUIT' => 'quite nadoka program', 'RESTART' => 'restart nadoka program(not relaod *.rb programs)', 'RELOAD' => 'reload configurations and bot programs(*.nb)', 'RECONNECT' => 'reconnect next server', 'STATUS' => 'show nadoka running status', } def nadoka_client_command cmd, args cmd ||= '' case cmd.upcase when 'QUIT' @manager.invoke_event :quit_program client_notice 'nadoka will be quit. bye!' when 'RESTART' @manager.invoke_event :restart_program, self client_notice 'nadoka will be restart. see you later.' when 'RELOAD' @manager.invoke_event :reload_config, self when 'RECONNECT' @manager.invoke_event :reconnect_to_server, self when 'STATUS' @manager.ndk_status.each{|msg| client_notice msg} when 'HELP' self << Cmd.notice(@state.nick, 'available: ' + NdkCommandDescription.keys.join(', ')) if args[1] self << Cmd.notice(@state.nick, "#{args[1]}: #{NdkCommandDescription[args[1].upcase]}") end else if @manager.send_to_bot :nadoka_command, self, cmd, *args self << Cmd.notice(@state.nick, 'No such command. Use /NADOKA HELP.') end end end end end nadoka-0.10.0/ndk/config.rb000066400000000000000000000353531371026053200154030ustar00rootroot00000000000000# # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # Create : K.S. 04/04/17 16:50:33 # # # You can check RCFILE with following command: # # ruby ndk_config.rb [RCFILE] # require 'uri' require 'socket' require 'kconv' require 'ndk/logger' module Nadoka class NDK_ConfigBase # system # 0: quiet, 1: normal, 2: system, 3: debug Loglevel = 2 Setting_name = nil # client server Client_server_port = 6667 # or nil (no listen) Client_server_host = nil Client_server_pass = 'NadokaPassWord' # or nil Client_server_acl = nil Client_server_ssl_cert_file = nil Client_server_ssl_key_file = nil ACL_Object = nil # Server_list = [ # { :host => '127.0.0.1', :port => 6667, :pass => nil } ] Servers = [] Reconnect_delay = 150 Default_channels = [] Login_channels = [] # User = ENV['USER'] || ENV['USERNAME'] || 'nadokatest' Nick = 'ndkusr' Hostname = Socket.gethostname Servername = '*' Realname = 'nadoka user' Mode = nil Away_Message = 'away' Away_Nick = nil Quit_Message = "Quit Nadoka #{::Nadoka::NDK_Version}" # Channel_info = {} # log Default_log = { :file => '${setting_name}-${channel_name}/%y%m%d.log', :time_format => '%H:%M:%S', :message_format => { 'PRIVMSG' => '<{nick}> {msg}', 'NOTICE' => '{{nick}} {msg}', 'JOIN' => '+ {nick} ({prefix:user}@{prefix:host})', 'NICK' => '* {nick} -> {newnick}', 'QUIT' => '- {nick} (QUIT: {msg}) ({prefix:user}@{prefix:host})', 'PART' => '- {nick} (PART: {msg}) ({prefix:user}@{prefix:host})', 'KICK' => '- {nick} kicked by {kicker} ({msg})', 'MODE' => '* {nick} changed mode ({msg})', 'TOPIC' => '<{ch} TOPIC> {msg} (by {nick})', 'SYSTEM' => '[NDK] {orig}', 'OTHER' => '{orig}', 'SIMPLE' => '{orig}', }, } System_log = { :file => '${setting_name}-system.log', :time_format => '%y/%m/%d-%H:%M:%S', :message_format => { 'PRIVMSG' => '{ch} <{nick}> {msg}', 'NOTICE' => '{ch} {{nick}} {msg}', 'JOIN' => '{ch} + {nick} ({prefix:user}@{prefix:host})', 'NICK' => '{ch} * {nick} -> {newnick}', 'QUIT' => '{ch} - {nick} (QUIT: {msg}) ({prefix:user}@{prefix:host})', 'PART' => '{ch} - {nick} (PART: {msg}) ({prefix:user}@{prefix:host})', 'KICK' => '{ch} - {nick} kicked by {kicker} ({msg})', 'MODE' => '{ch} * {nick} changed mode ({msg})', 'TOPIC' => '{ch} <{ch} TOPIC> {msg} (by {nick})', 'SYSTEM' => '[NDK] {orig}', 'OTHER' => nil, 'SIMPLE' => nil, }, } Debug_log = { :io => $stdout, :time_format => '%y/%m/%d-%H:%M:%S', :message_format => System_log[:message_format], } Talk_log = { :file => '${setting_name}-talk/%y%m%d.log', :time_format => Default_log[:time_format], :message_format => { 'PRIVMSG' => '[{sender} => {receiver}] {msg}', 'NOTICE' => '{{sender} -> {receiver}} {msg}', } } System_Logwriter = nil Debug_Logwriter = nil Default_Logwriter = nil Talk_Logwriter = nil BackLog_Lines = 20 # file name encoding setting # 'euc' or 'sjis' or 'jis' or 'utf8' FilenameEncoding = case RUBY_PLATFORM when /mswin/, /cygwin/, /mingw/ 'sjis' else if /UTF-?8/i =~ ENV['LANG'] 'utf8' else 'euc' end end # dirs Default_Plugins_dir = File.expand_path('../../plugins', __FILE__) Plugins_dir = './plugins' Log_dir = './log' # bots BotConfig = [] # filters Privmsg_Filter = [] Notice_Filter = [] Primitive_Filters = {} # ... Privmsg_Filter_light = [] Nadoka_server_name = 'NadokaProgram' def self.inherited subklass ConfigClass << subklass end end ConfigClass = [NDK_ConfigBase] class NDK_Config NDK_ConfigBase.constants.each{|e| eval %Q{ def #{e.downcase} @config['#{e.downcase}'.intern] end } } def initialize manager, rcfile = nil @manager = manager @bots = [] load_config(rcfile || './nadokarc') end attr_reader :config, :bots, :logger def remove_previous_setting # remove setting class base_klass = ConfigClass.shift while klass = ConfigClass.shift Object.module_eval{ remove_const(klass.name) } end ConfigClass.push(base_klass) # clear required files RequiredFiles.replace [] # remove current NadokaBot Object.module_eval %q{ remove_const :NadokaBot module NadokaBot def self.included mod Nadoka::NDK_Config::BotClasses['::' + mod.name.downcase] = mod end end } # clear bot class BotClasses.each{|k, v| Object.module_eval{ if /\:\:/ !~ k.to_s && const_defined?(v.name) remove_const(v.name) end } } BotClasses.clear # destruct bot instances @bots.each{|bot| bot.bot_destruct } @bots = [] GC.start end def load_bots # for compatibility return load_bots_old if @config[:botconfig].kind_of? Hash @bots = @config[:botconfig].map{|bot| if bot.kind_of? Hash next nil if bot[:disable] name = bot[:name] cfg = bot raise "No bot name specified. Check rcfile." unless name else name = bot cfg = nil end load_botfile name.to_s.downcase make_bot_instance name, cfg }.compact end # for compatibility def load_bots_old (@config[:botfiles] + (@config[:defaultbotfiles]||[])).each{|file| load_botfile file } @config[:botconfig].keys.each{|bk| bkn = bk.to_s bkni= bkn.intern unless BotClasses.any?{|n, c| n == bkni} if @config[:botfiles] raise "No such BotClass: #{bkn}" else load_botfile "#{bkn.downcase}.nb" end end } @bots = BotClasses.map{|bkname, bk| if @config[:botconfig].has_key? bkname if (cfg = @config[:botconfig][bkname]).kind_of? Array cfg.map{|c| make_bot_instance bk, c } else make_bot_instance bk, cfg end else make_bot_instance bk, nil end }.flatten end def server_setting if svrs = @config[:servers] svl = [] svrs.each{|si| ports = si[:port] || 6667 host = si[:host] pass = si[:pass] ssl_params = si[:ssl_params] if ports.respond_to? :each ports.each{|port| svl << {:host => host, :port => port, :pass => pass, :ssl_params => ssl_params} } else svl << {:host => host, :port => ports, :pass => pass, :ssl_params => ssl_params} end } @config[:server_list] = svl end end def make_logwriter log return unless log case log when Hash if log.has_key?(:logwriter) return log[:logwriter] elsif log.has_key?(:logwriterclass) klass = log[:logwriterclass] elsif log.has_key?(:io) klass = IOLogWriter elsif log.has_key?(:file) klass = FileLogWriter else klass = FileLogWriter end opts = @config[:default_log].merge(log) klass.new(self, opts) when String opts = @config[:default_log].dup opts[:file] = log FileLogWriter.new(self, opts) when IO opts = @config[:default_log].dup opts[:io] = log IOLogWriter.new(self, opts) else raise "Unknown LogWriter setting" end end def make_default_logwriter if @config[:default_log].kind_of? Hash dl = @config[:default_log] else # defult_log must be Hash dl = @config[:default_log] @config[:default_log] = NDK_ConfigBase::Default_log.dup end @config[:default_logwriter] ||= make_logwriter(dl) @config[:system_logwriter] ||= make_logwriter(@config[:system_log]) @config[:debug_logwriter] ||= make_logwriter(@config[:debug_log]) @config[:talk_logwriter] ||= make_logwriter(@config[:talk_log]) end def channel_setting # treat with channel information if chs = @config[:channel_info] dchs = [] lchs = [] cchs = {} chs.each{|ch, setting| ch = identical_channel_name(ch) setting = {} unless setting.kind_of?(Hash) if !setting[:timing] || setting[:timing] == :startup dchs << ch elsif setting[:timing] == :login lchs << ch end # log writer setting[:logwriter] ||= make_logwriter(setting[:log]) || @config[:default_logwriter] cchs[ch] = setting } chs.replace cchs @config[:default_channels] = dchs @config[:login_channels] = lchs end end def acl_setting if @config[:client_server_acl] && !@config[:acl_object] require 'drb/acl' acl = @config[:client_server_acl].strip.split(/\s+/) @config[:acl_object] = ACL.new(acl) @logger.slog "ACL: #{acl.join(' ')}" end end def load_config(rcfile) load(rcfile) if rcfile @config = {} klass = ConfigClass.last klass.ancestors[0..klass.ancestors.index(NDK_ConfigBase)].reverse_each{|kl| kl.constants.each{|e| @config[e.downcase.intern] = klass.const_get(e) } } @config[:setting_name] ||= File.basename(@manager.rc).sub(/\.?rc$/, '') if $NDK_Debug @config[:loglevel] = 3 end make_default_logwriter @logger = NDK_Logger.new(@manager, self) @logger.slog "load config: #{rcfile}" server_setting channel_setting acl_setting load_bots end def ch_config ch, key channel_info[ch] && channel_info[ch][key] end def canonical_channel_name ch ch = ch.toeuc.sub(/^\!.{5}/, '!') identical_channel_name ch end def identical_channel_name ch # use 4 gsub() because of the compatibility of RFC2813(3.2) ch = ch.toeuc.downcase.tr('[]\\~', '{}|^').tojis if ch.respond_to?(:force_encoding) ch.force_encoding(Encoding::ASCII_8BIT) end ch end RName = { # ('&','#','+','!') '#' => 'CS-', '&' => 'CA-', '+' => 'CP-', '!' => 'CE-', } def make_logfilename tmpl, rch, cn unless cn cn = rch.sub(/^\!.{5}/, '!') case @config[:filenameencoding].to_s.downcase[0] when ?e # EUC cn = cn.toeuc.downcase when ?s # SJIS cn = cn.tosjis.downcase when ?u # utf-8 cn = cn.toutf8.downcase else # JIS cn = cn.toeuc.downcase.tojis cn = URI.encode(cn) end # escape cn = cn.sub(/^[\&\#\+\!]|/){|c| RName[c] } cn = cn.tr("*:/", "__I") end # format str = Time.now.strftime(tmpl) str.gsub(/\$\{setting_name\}/, setting_name). gsub(/\$\{channel_name\}|\{ch\}/, cn) end def log_format timefmt, msgfmts, msgobj text = log_format_message(msgfmts, msgobj) if timefmt && !msgobj[:nostamp] text = "#{msgobj[:time].strftime(timefmt)} #{text}" end text end def log_format_message msgfmts, msgobj type = msgobj[:type] format = msgfmts.fetch(type, @config[:default_log][:message_format][type]) if format.kind_of? Proc text = format.call(params) elsif format if format.respond_to?(:force_encoding) format.force_encoding(Encoding::ASCII_8BIT) end text = format.gsub(/\{([a-z]+)\}|\{prefix\:([a-z]+)\}/){|key| if $2 method = $2.intern if msgobj[:orig].respond_to?(:prefix) prefix = msgobj[:orig].prefix || '' if prefix.respond_to?(:force_encoding) prefix.force_encoding(Encoding::ASCII_8BIT) end /^(.+?)\!(.+?)@(.+)/ =~ prefix case method when :nick $1 when :user $2 when :host $3 else "!!unknown prefix attribute: #{method}!!" end end else if m = msgobj[$1.intern] if m.respond_to?(:force_encoding) m.dup.force_encoding(Encoding::ASCII_8BIT) else m end else "!!unknown attribute: #{$1}!!" end end } else text = msgobj[:orig].to_s end end def make_bot_instance bk, cfg bk = BotClasses[bk.to_s.downcase.intern] unless bk.kind_of? Class bot = bk.new @manager, self, cfg || {} @logger.slog "bot instance: #{bot.bot_state}" bot end def load_botfile file loaded = false if @config[:plugins_dir].respond_to? :each @config[:plugins_dir].each{|dir| if load_file File.expand_path("#{file}.nb", dir) loaded = true break end } else loaded = load_file File.expand_path("#{file}.nb", @config[:plugins_dir]) end unless loaded raise "No such bot file: #{file}" end end def load_file file if FileTest.exist? file Nadoka.require_bot file true else false end end RequiredFiles = [] BotClasses = {} end def self.require_bot file return if NDK_Config::RequiredFiles.include? file NDK_Config::RequiredFiles.push file begin ret = ::Kernel.load(file) rescue NDK_Config::RequiredFiles.pop raise end ret end end module NadokaBot # empty module for bot namespace # this module is reloadable def self.included mod Nadoka::NDK_Config::BotClasses['::' + mod.name.downcase] = mod end end if $0 == __FILE__ require 'pp' pp Nadoka::NDK_Config.new(nil, ARGV.shift) end nadoka-0.10.0/ndk/error.rb000066400000000000000000000017411371026053200152610ustar00rootroot00000000000000# # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # Create : K.S. 04/04/20 23:57:17 # module Nadoka class NDK_Error < Exception end class NDK_QuitClient < NDK_Error end class NDK_BotBreak < NDK_Error end class NDK_BotSendCancel < NDK_Error end class NDK_QuitProgram < NDK_Error end class NDK_RestartProgram < NDK_Error end class NDK_ReconnectToServer < NDK_Error end class NDK_InvalidMessage < NDK_Error end #### class NDK_FilterMessage_SendCancel < NDK_Error end class NDK_FilterMessage_Replace < NDK_Error def initialize msg @msg = msg end attr_reader :msg end class NDK_FilterMessage_OnlyBot < NDK_Error end class NDK_FilterMessage_OnlyLog < NDK_Error end class NDK_FilterMessage_BotAndLog < NDK_Error end end nadoka-0.10.0/ndk/logger.rb000066400000000000000000000156761371026053200154230ustar00rootroot00000000000000# # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # Create : K.S. 04/05/01 02:04:18 require 'kconv' require 'fileutils' require 'thread' module Nadoka class LogWriter def initialize config, opts @opts = opts @config = config @time_fmt = opts[:time_format] @msg_fmts = opts[:message_format] end def write_log msg raise "override me" end def log_format msgobj @config.log_format @time_fmt, @msg_fmts, msgobj end def logging msgobj msg = log_format(msgobj) return if msg.empty? write_log msg end end class LogUnWriter < LogWriter def logging _ end end class IOLogWriter < LogWriter Lock = Mutex.new def initialize config, opts super @io = opts[:io] end def write_log msg Lock.synchronize{ @io.puts msg } end end class FileLogWriter < LogWriter def initialize config, opts super @filename_fmt = opts[:file] @channel_name_in_file_name = opts[:channel_name_in_file_name] end def logging msgobj msg = log_format(msgobj) return if msg.empty? write_log_file make_logfilename(@filename_fmt, msgobj[:ch] || '', @channel_name_in_file_name), msg end def write_log_file basefile, msg basedir = File.expand_path(@config.log_dir) + '/' logfile = File.expand_path(basefile, basedir) ldir = File.dirname(logfile) + '/' if !FileTest.directory?(ldir) raise "insecure directory: #{ldir} (pls check rc file.)" if /\A#{Regexp.quote(basedir)}/ !~ ldir # make directory recursively FileUtils.mkdir_p(ldir) end open(logfile, 'a'){|f| f.flock(File::LOCK_EX) f.puts msg } end def make_logfilename tmpl, ch, cn @config.make_logfilename tmpl, ch, cn end end class NDK_Logger class MessageStore def initialize limit @limit = limit @pool = [] end attr_reader :pool def limit=(lim) @limit = lim end def truncate while @pool.size > @limit @pool.shift end end def push msgobj truncate @pool.push msgobj end def clear @pool.clear end end class MessageStoreByTime < MessageStore def truncate lim = Time.now.to_i - @limit while true if @pool[0][:time].to_i < lim @pool.shift else break end end end end class MessageStores def initialize type, lim, config @limit = lim @class = type == :time ? MessageStoreByTime : MessageStore @config = config @pools = {} end attr_reader :pools def push msgobj ch = msgobj[:ccn] unless pool = @pools[ch] limit = (@config.channel_info[ch] && @config.channel_info[ch][:backloglines]) || @limit @pools[ch] = pool = @class.new(limit) end pool.push msgobj end def each_channel_pool @pools.each{|ch, store| yield ch, store.pool } end end def initialize manager, config @manager = manager @config = config @dlog = @config.debug_log @message_stores = MessageStores.new(:size, @config.backlog_lines, @config) end attr_reader :message_stores # debug message def dlog msg if @config.loglevel >= 3 msgobj = make_msgobj msg, 'DEBUG' @config.debug_logwriter.logging msgobj end end alias debug dlog # system message def slog msg, nostamp = false msgobj = make_msgobj(msg, 'SYSTEM', nostamp) if @config.loglevel >= 2 @config.system_logwriter.logging msgobj @message_stores.push msgobj end str = @config.system_logwriter.log_format(msgobj) @manager.send_to_clients Cmd.notice(@manager.state.nick, str) if @manager.state dlog str end # channel message def clog ch, msg, nostamp = false clog_msgobj ch, make_msgobj(msg, 'SIMPLE', nostamp, ch) end # other irc log message def olog msg olog_msgobj make_msgobj(msg, 'OTHER') end ######################################### def make_msgobj msg, type = msg.command, nostamp = false, ch = nil msgobj = { :time => Time.now, :type => type, :orig => msg, :nostamp => nostamp, :ch => ch, } $NDK_NOTIFY_SOCKET.status = msgobj.inspect.tr("\n", ' ') msgobj end def clog_msgobj ch, msgobj if msgobj[:ccn] == :__talk__ logwriter = @config.talk_logwriter else logwriter = (@config.channel_info[ch] && @config.channel_info[ch][:logwriter]) || @config.default_logwriter end @message_stores.push msgobj logwriter.logging msgobj end def olog_msgobj msgobj if @config.loglevel >= 1 @config.system_logwriter.logging msgobj @message_stores.push msgobj end end # logging def logging msg user = @manager.nick_of(msg) rch = msg.params[0] ch = @config.canonical_channel_name(rch) msgobj = make_msgobj(msg) msgobj[:ch] = rch # should be raw msgobj[:ccn] = ch msgobj[:nick] = user msgobj[:msg] = msg.params[1] case msg.command when 'PRIVMSG', 'NOTICE', 'TOPIC', 'JOIN', 'PART' unless /\A[\&\#\+\!]/ =~ ch # talk? msgobj[:sender] = user msgobj[:receiver] = rch msgobj[:ccn] = :__talk__ end clog_msgobj ch, msgobj when 'NICK', 'QUIT' # ignore. see below. when 'MODE' msgobj[:msg] = msg.params[1..-1].join(', ') if @manager.state.current_channels[ch] clog_msgobj ch, msgobj else olog_msgobj msgobj end when 'KICK' msgobj[:kicker] = msg.params[1] msgobj[:msg] = msg.params[2] clog_msgobj ch, msgobj when /^\d+/ # reply str = msg.command + ' ' + msg.params.join(' ') olog str else # other command olog msg.to_s end end def logging_nick ccn, rch, nick, newnick, msg msgobj = make_msgobj(msg) msgobj[:ch] = rch # should be raw msgobj[:ccn] = ccn msgobj[:nick] = nick msgobj[:newnick] = newnick clog_msgobj ccn, msgobj end def logging_quit ccn, rch, user, qmsg, msg msgobj = make_msgobj(msg) msgobj[:ch] = rch # should be raw msgobj[:ccn] = ccn msgobj[:nick] = user msgobj[:msg] = qmsg clog_msgobj ccn, msgobj end ### end end nadoka-0.10.0/ndk/server.rb000066400000000000000000000512351371026053200154410ustar00rootroot00000000000000# # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # Create : K.S. 04/04/17 17:00:44 # require 'rice/irc' require 'ndk/error' require 'ndk/config' require 'ndk/server_state' require 'ndk/client' begin require 'openssl' rescue LoadError end module Nadoka Cmd = ::RICE::Command Rpl = ::RICE::Reply class NDK_Server TimerIntervalSec = 60 MAX_PONG_FAIL = 5 def initialize rc @rc = rc @clients = [] @prev_timer = Time.now @server_thread = nil @clients_thread = nil @state = nil @state = NDK_State.new self reload_config @server = nil @cserver = nil @connected = false @exitting = false @pong_recieved = true @pong_fail_count = 0 @isupport = {} set_signal_trap end attr_reader :state, :connected, :rc attr_reader :isupport def client_count @clients.size end def next_server_info svinfo = @config.server_list.sort_by{rand}.shift @config.server_list.push svinfo [svinfo[:host], svinfo[:port], svinfo[:pass], svinfo[:ssl_params]] end def reload_config @config.remove_previous_setting if defined?(@config) @config = NDK_Config.new(self, @rc) # reset logger @logger = @config.logger @state.logger = @logger @state.config = @config @clients.each{|c| c.logger = @logger } end def start_server_thread @server_thread = Thread.new{ begin @server = make_server() @logger.slog "Server connection to #{@server.server}:#{@server.port}." @pong_recieved = true @server.start(1){|sv| sv << Cmd.quit(@config.quit_message) if @config.quit_message } rescue RICE::Connection::Closed, SystemCallError, IOError @connected = false part_from_all_channels @logger.slog "Connection closed by server. Trying to reconnect." sleep @config.reconnect_delay retry unless @exitting rescue NDK_ReconnectToServer @connected = false part_from_all_channels begin @server.close if @server rescue RICE::Connection::Closed, SystemCallError, IOError end @logger.slog "Reconnect request (no server response, or client request)." sleep @config.reconnect_delay retry unless @exitting rescue Exception => e ndk_error e @clients_thread.kill if @clients_thread && @clients_thread.alive? end } end def make_server host, port, @server_passwd, ssl_params = next_server_info server = ::RICE::Connection.new(host, port, "\r\n", ssl_params) server.regist{|rq, wq| Thread.stop @rq = rq begin @connected = false server_main_proc rescue Exception => e ndk_error e @server_thread.kill if @server_thread && @server_thread.alive? @clients_thread.kill if @clients_thread && @clients_thread.alive? ensure @server.close end } server end def server_main_proc ## login # send passwd if @server_passwd send_to_server Cmd.pass(@server_passwd) end # send nick if @config.away_nick && client_count == 0 @state.original_nick = @config.nick @state.nick = @config.away_nick else @state.nick = @config.nick end send_to_server Cmd.nick(@state.nick) # send user info send_to_server Cmd.user(@config.user, @config.hostname, @config.servername, @config.realname) # wait welcome message while q = recv_from_server case q.command when '001' break when '433', '437' # Nickname is already in use. # 437 Nick/channel is temporarily unavailable nick = @state.nick_succ(q.params[1]) @state.nick = nick send_to_server Cmd.nick(nick) when 'NOTICE' # ignore when 'ERROR' msg = "Server login fail!(#{q})" @server_thread.raise NDK_ReconnectToServer when '020' # ignore when 'PRIVMSG' # ignore when '004' # ignore else msg = "Server login fail!(#{q})" @logger.slog msg raise msg end end # change user mode if @config.mode send_to_server Cmd.mode(@state.nick, @config.mode) end # join to default channels if @state.current_channels.size > 0 # if reconnect @state.current_channels.each{|ch, chs| join_to_channel ch } else # default join process @config.default_channels.each{|ch| join_to_channel ch } end @connected = true @isupport = {} ## if @clients.size == 0 enter_away end invoke_event :invoke_bot, :server_connected $NDK_NOTIFY_SOCKET.ready! # loop while q = recv_from_server case q.command when 'PING' send_to_server Cmd.pong(q.params[0]) next when 'PRIVMSG' if ctcp_message?(q.params[1]) ctcp_message(q) end when 'JOIN' @state.on_join(nick_of(q), q.params[0]) when 'PART' @state.on_part(nick_of(q), q.params[0]) when 'NICK' @state.on_nick(nick_of(q), q.params[0], q) when 'QUIT' @state.on_quit(nick_of(q), q.params[0], q) when 'TOPIC' @state.on_topic(nick_of(q), q.params[0], q.params[1]) when 'MODE' @state.on_mode(nick_of(q), q.params[0], q.params[1..-1]) when 'KICK' @state.on_kick(nick_of(q), q.params[0], q.params[1], q.params[2]) when '353' # RPL_NAMREPLY @state.on_353(q.params[2], q.params[3]) when '332' # RPL_TOPIC @state.on_332(q.params[1], q.params[2]) when '403' # ERR_NOSUCHCHANNEL @state.on_403(q.params[1]) when '433', '436', '437' # ERR_NICKNAMEINUSE, ERR_NICKCOLLISION, ERR_UNAVAILRESOURCE # change try nick case q.params[1] when /\A[\#&!+]/ # retry join after 1 minute Thread.start(q.params[1]) do |ch| sleep 60 join_to_channel ch end else nick = @state.nick_succ(q.params[1]) send_to_server Cmd.nick(nick) @logger.slog("Retry nick setting: #{nick}") end when '005' # RPL_ISUPPORT or RPL_BOUNCE if /supported/i =~ q.params[-1] q.params[1..-2].each do |param| if /\A(-)?([A-Z0-9]+)(?:=(.*))?\z/ =~ param negate, key, value = $~.captures if negate @isupport.delete(key) else @isupport[key] = value || true end end end end @logger.dlog "isupport: #{@isupport.inspect}" else # end send_to_clients q @logger.logging q send_to_bot q end end def join_to_channel ch if @config.channel_info[ch] && @config.channel_info[ch][:key] send_to_server Cmd.join(ch, @config.channel_info[ch][:key]) else send_to_server Cmd.join(ch) end end def enter_away return if @exitting || !@connected send_to_server Cmd.away(@config.away_message) if @config.away_message # change nick if @state.nick != @config.away_nick && @config.away_nick @state.original_nick = @state.nick send_to_server Cmd.nick(@config.away_nick) end # part channel @config.login_channels.each{|ch| if @config.channel_info[ch] && @state.channels.include?(ch) if @config.channel_info[ch][:part_message] send_to_server Cmd.part(ch, @config.channel_info[ch][:part_message]) else send_to_server Cmd.part(ch) end end } end def leave_away return if @exitting || !@connected send_to_server Cmd.away() if @config.away_nick && @state.original_nick sleep 2 # wait for server response send_to_server Cmd.nick(@state.original_nick) @state.original_nick = nil sleep 1 # wait for server response end @config.login_channels.each{|ch| send_to_server Cmd.join(ch) } end def start_clients_thread return unless @config.client_server_port @clients_thread = Thread.new{ begin @cserver = TCPServer.new(@config.client_server_host, @config.client_server_port) @logger.slog "Open Client Server Port: #{@cserver.addr.join(' ')}" if @config.client_server_ssl_cert_file && @config.client_server_ssl_key_file context = OpenSSL::SSL::SSLContext.new context.cert = OpenSSL::X509::Certificate.new(File.read(@config.client_server_ssl_cert_file)) context.key = OpenSSL::PKey::RSA.new(File.read(@config.client_server_ssl_key_file)) @cserver = OpenSSL::SSL::SSLServer.new(@cserver, context) @cserver.start_immediately = false end while true # wait for client connections Thread.start(@cserver.accept){|cc| cc.accept if OpenSSL::SSL::SSLSocket === cc rescue NameError client = nil begin if !@config.acl_object || @config.acl_object.allow_socket?(cc) client = NDK_Client.new(@config, cc, self) @clients << client client.start else @logger.slog "ACL denied: #{cc.peeraddr.join(' ')}" end rescue Exception => e ndk_error e ensure @clients.delete client invoke_event :enter_away, client_count cc.close unless cc.closed? end } end rescue Exception => e ndk_error e ensure @clients.each{|cl| cl.kill } if @cserver @logger.slog "Close Client Server Port: #{@cserver.addr.join(' ')}" @cserver.close unless @cserver.closed? end @server_thread.kill if @server_thread.alive? end } end def start start_server_thread start_clients_thread timer_thread = Thread.new{ begin @pong_recieved = true @pong_fail_count = 0 while true slp = Time.now.to_i % TimerIntervalSec slp = TimerIntervalSec if slp < (TimerIntervalSec / 2) sleep slp send_to_bot :timer, Time.now if @connected if @pong_recieved @pong_fail_count = 0 $NDK_NOTIFY_SOCKET.watchdog! else # fail @pong_fail_count += 1 @logger.slog "PONG MISS: #{@pong_fail_count}" if @pong_fail_count > MAX_PONG_FAIL @pong_fail_count = 0 invoke_event :reconnect_to_server end end @pong_recieved = false @server << Cmd.ping(@server.server) else @pong_recieved = true @pong_fail_count = 0 end end rescue Exception => e ndk_error e end } begin sleep rescue Interrupt @exitting = true rescue Nadoka::NDK_Error @exitting = true raise ensure @server_thread.kill if @server_thread && @server_thread.alive? @clients_thread.kill if @clients_thread && @clients_thread.alive? timer_thread.kill if timer_thread && timer_thread.alive? @server.close if @server end end def send_to_server msg str = msg.to_s if /[\r\n]/ =~ str.chomp @logger.dlog "![>S] #{str}" raise NDK_InvalidMessage, "Message must not include [\\r\\n]: #{str.inspect}" else @logger.dlog "[>S] #{str}" @server << msg end end def recv_from_server while q = @rq.pop # Event if q.kind_of? Array exec_event q next end # Server -> Nadoka message if !@config.primitive_filters.nil? && !@config.primitive_filters[q.command].nil? && !@config.primitive_filters[q.command].empty? next unless filter_message(@config.primitive_filters[q.command], q) end case q.command when 'PING' @server << Cmd.pong(q.params[0]) when 'PONG' @pong_recieved = true when 'NOTICE' @logger.dlog "[ e @logger.dlog "[NDK] Message Replaced: #{e}" return e.msg rescue NDK_FilterMessage_OnlyBot @logger.dlog "[NDK] Message only bot" send_to_bot msg return false rescue NDK_FilterMessage_OnlyLog @logger.dlog "[NDK] Message only log" @logger.logging msg return false rescue NDK_FilterMessage_BotAndLog @logger.dlog "[NDK] Message log and bot" send_to_bot msg @logger.logging msg return false end msg end def invoke_event ev, *arg arg.unshift ev @rq && (@rq << arg) end def exec_event q # special event case q[0] when :reload_config # q[1] must be client object begin reload_config @logger.slog "configuration is reloaded" rescue Exception => e @logger.slog "error is occure while reloading configuration" ndk_error e end when :quit_program @exitting = true Thread.main.raise NDK_QuitProgram when :restart_program @exitting = true Thread.main.raise NDK_RestartProgram when :reconnect_to_server @connected = false @server_thread.raise NDK_ReconnectToServer when :invoke_bot # q[1], q[2] are message and argument send_to_bot q[1], *q[2..-1] when :enter_away if q[1] == 0 enter_away end when :leave_away if q[1] == 1 leave_away end end end def set_signal_trap list = Signal.list Signal.trap(:INT){ # invoke_event :quit_program Thread.main.raise NDK_QuitProgram } if list['INT'] Signal.trap(:TERM){ # invoke_event :quit_program Thread.main.raise NDK_QuitProgram } if list.any?{|e| e == 'TERM'} Signal.trap(:HUP){ # reload config invoke_event :reload_config } if list['HUP'] trap(:USR1){ # SIGUSR1 invoke_event :invoke_bot, :sigusr1 } if list['USR1'] trap(:USR2){ # SIGUSR2 invoke_event :invoke_bot, :sigusr2 } if list['USR2'] end def about_me? msg qnick = Regexp.quote(@state.nick || '') if msg.prefix =~ /^#{qnick}!/ true else false end end def own_nick_change? msg if msg.command == 'NICK' && msg.params[0] == @state.nick nick_of(msg) else false end end def part_from_all_channels @state.channels.each{|ch, cs| cmd = Cmd.part(ch) cmd.prefix = @state.nick #m send_to_clients cmd } @state.clear_channels_member end # server -> clients def send_to_clients msg if msg.command == 'PRIVMSG' && !(msg = filter_message(@config.privmsg_filter_light, msg)) return end if(old_nick = own_nick_change?(msg)) @clients.each{|cl| cl.add_prefix2(msg, old_nick) cl << msg } elsif about_me? msg @clients.each{|cl| cl.add_prefix(msg) cl << msg } else @clients.each{|cl| cl << msg } end end def ping_to_clients @clients.each{|cl| cl << Cmd.ping(cl.remote_host) } end # clientA -> other clients # bot -> clients def send_to_clients_otherwise msg, elt @clients.each{|cl| if cl != elt cl.add_prefix(msg) unless msg.prefix cl << msg end } invoke_event :invoke_bot, msg if elt @logger.logging msg end def ctcp_message? arg arg[0] == ?\x1 end def ctcp_message msg if /\001(.+)\001/ =~ msg.params[1] ctcp_cmd = $1 case ctcp_cmd when 'VERSION' send_to_server Cmd.notice(nick_of(msg), "\001VERSION #{Nadoka.version}\001") when 'TIME' send_to_server Cmd.notice(nick_of(msg), "\001TIME #{Time.now}\001") else end end end def nick_of msg if /^([^!]+)\!?/ =~ msg.prefix.to_s $1 else @state.nick end end class PrefixObject def initialize prefix parse_prefix prefix @prefix = prefix end attr_reader :nick, :user, :host, :prefix def parse_prefix prefix if /^(.+?)\!(.+?)@(.+)/ =~ prefix.to_s # command @nick, @user, @host = $1, $2, $3 else # server reply @nick, @user, @host = nil, nil, prefix end end def to_s @prefix end end def make_prefix_object msg prefix = msg.prefix if prefix PrefixObject.new(prefix) else if /^d+$/ =~ msg.command PrefixObject.new(@config.nadoka_server_name) else PrefixObject.new("#{@state.nick}!#{@config.user}@#{@config.nadoka_server_name}") end end end # dispatch to bots def send_to_bot msg, *arg selector = 'on_' + if msg.respond_to? :command if /^\d+$/ =~ msg.command # reply prefix = make_prefix_object msg RICE::Reply::Replies_num_to_name[msg.command] else # command prefix = make_prefix_object msg msg.command.downcase end else prefix = nil msg.to_s end @config.bots.each{|bot| begin if bot.respond_to? selector unless prefix bot.__send__(selector, *arg) else bot.__send__(selector, prefix, *msg.params) end end if prefix && bot.respond_to?(:on_every_message) bot.__send__(:on_every_message, prefix, msg.command, *msg.params) end rescue NDK_BotBreak break rescue NDK_BotSendCancel return false rescue Exception ndk_error $! end } true end def ndk_status [ '== Nadoka Running Status ==', '- nadoka version: ' + Nadoka.version, '- connecting to ' + "#{@server.server}:#{@server.port}", '- clients status:', @clients.map{|e| '-- ' + e.state}, '- Bots status:', @config.bots.map{|bot| '-- ' + bot.bot_state}, '== End of Status ==' ].flatten end def ndk_error err @logger.slog "Exception #{err.class} - #{err}" @logger.slog "-- backtrace --" err.backtrace.each{|line| @logger.slog "| " + line } end end end nadoka-0.10.0/ndk/server_state.rb000066400000000000000000000156241371026053200166430ustar00rootroot00000000000000# # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # Create : K.S. 04/04/20 10:42:27 # module Nadoka class NDK_State class ChannelState def initialize name @name = name @topic = nil @member= {} # member data # { "nick" => "mode", ... } end attr_accessor :topic attr_reader :member, :name # get members nick array def members @member.keys end # get user's mode def mode nick @member[nick] end def names @member.map{|nick, mode| prefix = if /o/ =~ mode '@' elsif /v/ =~ mode '+' else '' end prefix + nick } end def state '=' end def clear_members @member = {} end ##################################### def on_join nick, mode='' @member[nick] = mode end def on_part nick @member.delete nick end def on_nick nick, newnick if @member.has_key? nick @member[newnick] = @member[nick] @member.delete nick end end def on_kick nick if @member.has_key? nick @member.delete nick end end MODE_WITH_NICK_ARG = 'ov' MODE_WITH_ARGS = 'klbeI' MODE_WITHOUT_ARGS = 'aimnqpsrt' def on_mode nick, args while mode = args.shift modes = mode.split(//) flag = modes.shift modes.each{|m| if MODE_WITH_NICK_ARG.include? m chg_mode args.shift, flag, m elsif MODE_WITH_ARGS.include? m args.shift elsif MODE_WITHOUT_ARGS.include? m # ignore end } end end def chg_mode nick, flag, mode if @member.has_key? nick if flag == '+' @member[nick] += mode elsif flag == '-' @member[nick].gsub!(mode, '') end end end def to_s str = '' @member.each{|k, v| str << "#{k}: #{v}, " } str end end def initialize manager @manager = manager @config = nil @logger = nil @current_nick = nil @original_nick = nil @try_nick = nil @current_channels = {} end attr_reader :current_channels attr_reader :current_nick attr_accessor :original_nick attr_writer :logger, :config def nick=(n) @try_nick = nil @current_nick = n end def nick @current_nick end def nick_succ fail_nick if @try_nick if @try_nick.length == fail_nick @try_nick.succ! else @try_nick = fail_nick[0..-2] + '0' end else @try_nick = fail_nick + '0' end end def channels @current_channels.keys end def channel_raw_names @current_channels.map{|k, cs| cs.name} end # need canonicalized channel name def channel_users ch if @current_channels.has_key? ch @current_channels[ch].members else [] end end # need canonicalized channel name def channel_user_mode ch, user if channel_users(ch).include?(user) @current_channels[ch].mode(user) else '' end end def canonical_channel_name ch @config.canonical_channel_name ch end def clear_channels_member @current_channels.each{|ch, cs| cs.clear_members } end # def on_join user, rch ch = canonical_channel_name(rch) if user == nick @current_channels[ch] = ChannelState.new(rch) else if @current_channels.has_key? ch @current_channels[ch].on_join(user) end end end def on_part user, rch ch = canonical_channel_name(rch) if user == nick @current_channels.delete ch else if @current_channels.has_key? ch @current_channels[ch].on_part user end end end def on_nick user, newnick, msg if user == nick @current_nick = newnick @try_nick = nil end # logging @current_channels.each{|ch, chs| if chs.on_nick user, newnick @config.logger.logging_nick ch, chs.name, user, newnick, msg end } end def on_quit user, qmsg, msg if user == nick @current_channels = {} # clear else # logging @current_channels.each{|ch, chs| if chs.on_part(user) @config.logger.logging_quit ch, chs.name, user, qmsg, msg @manager.invoke_event :invoke_bot, :quit_from_channel, chs.name, user, qmsg end } end end def on_mode user, rch, args ch = canonical_channel_name(rch) if @current_channels.has_key? ch @current_channels[ch].on_mode user, args end end def on_kick kicker, rch, user, comment ch = canonical_channel_name(rch) if user == nick @current_channels.delete ch else if @current_channels.has_key? ch @current_channels[ch].on_kick user end end end def on_topic user, rch, topic ch = canonical_channel_name(rch) if @current_channels.has_key? ch @current_channels[ch].topic = topic end end def on_332 rch, topic ch = canonical_channel_name(rch) if @current_channels.has_key? ch @current_channels[ch].topic = topic end end # RPL_NAMREPLY # ex) :lalune 353 test_ndk = #nadoka :test_ndk ko1_nmdk # def on_353 rch, users ch = canonical_channel_name(rch) if @current_channels.has_key? ch chs = @current_channels[ch] users.split(/ /).each{|e| /^([\@\+])?(.+)/ =~ e case $1 when '@' mode = 'o' when '+' mode = 'v' else mode = '' end chs.on_join $2, mode } # change initial mode if @config.channel_info[ch] && (im = @config.channel_info[ch][:initial_mode]) && chs.members.size == 1 @manager.send_to_server Cmd.mode(rch, im) end end end def safe_channel? ch ch[0] == ?! end # ERR_NOSUCHCHANNEL # ex) :NadokaProgram 403 simm !hoge :No such channel def on_403 ch if safe_channel?(ch) && ch[1] != ?! if @config.channel_info[ch] && @config.channel_info[ch][:auto_create] @manager.join_to_channel( "!" + ch) end end end end end nadoka-0.10.0/ndk/version.rb000066400000000000000000000024211371026053200156110ustar00rootroot00000000000000# # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # Create : K.S. 04/04/27 16:35:40 # module Nadoka VERSION = '0.10.0' NDK_Version = VERSION.dup NDK_Created = Time.now if File.directory?(File.expand_path('../../.git', __FILE__)) git_describe = nil Dir.chdir(File.expand_path('../..', __FILE__)) do git_describe = `git describe --tags --long --dirty=-dt` if $?.success? git_describe = "(#{git_describe.strip})" else git_describe = nil end end NDK_Version.concat("+git#{git_describe}") end if /trunk/ =~ '$HeadURL$' NDK_Version.concat('-trunk') rev = '-' $LOAD_PATH.each{|path| path = path + '/ChangeLog' if FileTest.exist?(path) if /^\# ChangeLog of Nadoka\(\$Rev: (\d+) \$\)$/ =~ open(path){|f| f.gets} rev = "rev: #{$1}" break end end } NDK_Version.concat(" (#{rev})") end def self.version "Nadoka Ver.#{NDK_Version}" + " with Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" end end if __FILE__ == $0 puts Nadoka::NDK_Version end nadoka-0.10.0/plugins/000077500000000000000000000000001371026053200145055ustar00rootroot00000000000000nadoka-0.10.0/plugins/autoawaybot.nb000066400000000000000000000022221371026053200173630ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # This bot is created by # akira yamada # # $Id$ # =begin == Abstract Auto away if no action == Configuration BotConfig = [ { :name => :AutoAwayBot, :threshold => 15*60, # sec :message => 'Away', } ] =end class AutoAwayBot < Nadoka::NDK_Bot def bot_initialize @threshold = @bot_config.fetch(:threshold, 15*60).to_i @message = @bot_config.fetch(:message, 'Away') @lastseen = Time.now @in_away = false end def on_client_privmsg client, ch, msg if @threshold > 0 @lastseen = Time.now end if @in_away @manager.send_to_server Nadoka::Cmd.away() end end def on_timer time if @threshold > 0 && Time.now - @lastseen > @threshold && !@in_away @manager.send_to_server Nadoka::Cmd.away(@message) end end def on_rpl_unaway *arg @in_away = false end def on_rpl_nowaway *arg @in_away = true end end nadoka-0.10.0/plugins/autodumpbot.nb000066400000000000000000000122511371026053200173720ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # This bot is created by # akira yamada # # $Id$ # =begin == Abstract Auto save configuration == Configuration add these program to nadokarc(or your configuration file) begin dump_file = File.join(Log_dir, Setting_name + '-channel_info.dump') channel_info = {} begin File.open(dump_file, 'r') {|i| channel_info = Marshal.load(i)} rescue TypeError File.open(dump_file, 'r') {|i| channel_info = YAML.load(i.read)} end Channel_info = channel_info rescue Channel_info = { } end more detail, see [nadoka:162] =end class AutoDumpBot < Nadoka::NDK_Bot def bot_initialize @auto_dump = @bot_config.fetch(:auto_dump, true) @default_dump_style = @bot_config.fetch(:dump_style, 'yaml') @default_timing = @bot_config.fetch(:dump_timing, :startup) @cinfo = {} @manager.state.channels.each do |ch| @manager.send_to_server Nadoka::Cmd.mode(ch) end end def canonical_channel_name ch @manager.state.canonical_channel_name ch end def on_join prefix, ch if @manager.state.current_nick == prefix.nick dch = canonical_channel_name(ch) @cinfo[dch] ||= {} @cinfo[dch][:name] = ch @cinfo[dch][:mode] ||= [] @manager.send_to_server Nadoka::Cmd.mode(ch) end end def on_part prefix, ch, msg='' if @manager.client_count > 0 && @manager.state.current_nick == prefix.nick @cinfo.delete(canonical_channel_name(ch)) end end def on_client_logout count, client if count == 1 && @auto_dump dump_cinfo end end MODE_WITH_NICK_ARG = 'ov' MODE_WITH_ARGS = 'klbeI' MODE_WITHOUT_ARGS = 'aimnqpsrt' def on_mode prefix, nick, ch, *args dch = canonical_channel_name(ch) if @manager.state.current_channels.has_key? dch @cinfo[dch] ||= {} @cinfo[dch][:name] = ch @cinfo[dch][:mode] ||= [] while mode = args.shift modes = mode.split(//) flag = modes.shift modes.each{|m| if MODE_WITH_NICK_ARG.include? m #chg_cinfo dch, flag, m, args.shift elsif MODE_WITH_ARGS.include? m chg_cinfo dch, flag, m, args.shift elsif MODE_WITHOUT_ARGS.include? m chg_cinfo dch, flag, m, nil end } end end end def on_rpl_channelmodeis prefix, nick, ch, *arg on_mode(prefix, nick, ch, *arg) end def on_nadoka_command client, command, *params if command == 'dump' dump_cinfo(params.shift) raise ::Nadoka::NDK_BotSendCancel end end def chg_cinfo ch, flag, mode, arg @cinfo[ch][:mode] ||= [] @cinfo[ch][:mode].delete_if do |m| m[0, 1] == mode end if flag == '+' if arg @cinfo[ch][:mode] << mode + ' ' + arg else @cinfo[ch][:mode] << mode end end end def dump_cinfo(style = nil) style = @default_dump_style unless style channel_info = {} @cinfo.keys.each do |ch| name = @cinfo[ch][:name] if @state.current_channels.include?(ch) name = @state.current_channels[ch].name end channel_info[name] = cinfo = {} config = @manager.instance_eval {@config} if config.default_channels.detect {|tch| canonical_channel_name(tch) == ch} cinfo[:timing] = :startup elsif config.login_channels.detect {|tch| canonical_channel_name(tch) == ch} cinfo[:timing] = :login else cinfo[:timing] = @default_timing end if @cinfo[ch][:mode].include?('i') cinfo[:timing] = :startup elsif !cinfo.include?(:timing) cinfo[:timing] = :login end @cinfo[ch][:mode].each do |m| if m[0] == ?k cinfo[:key] = m[2 .. -1] end end m1, m2 = @cinfo[ch][:mode].partition {|m| m.size == 1} imode = nil unless m1.empty? imode ||= '+' imode << m1.join('') end unless m2.empty? imode ||= '+' imode << m2.join(' +') end cinfo[:initial_mode] = imode end begin dump_file = File.join(@config.log_dir, @config.setting_name + '-channel_info.dump') File.open(dump_file, 'wb') do |f| f.chmod(0600) dump_channel_info(channel_info, style, f) end @logger.slog "current channel information was dumped (#{style})" rescue Exception => e @logger.slog "current channel information could not be dumped" @logger.slog e.message end end def dump_channel_info(channel_info, style, port) if style == 'marshal' Marshal.dump(channel_info, port) elsif style == 'yaml' require 'yaml' YAML.dump(channel_info, port) elsif style == 'text' port.puts "Channel_info = {" channel_info.each do |ch, info| port.puts " #{ch.dump} => {" info.each do |tag, value| if tag.kind_of?(Symbol) if value.kind_of?(Symbol) port.puts " :#{tag.to_s} => :#{value.to_s}," else port.puts " :#{tag.to_s} => #{value.to_s.dump}," end end end port.puts " }," end port.puts "}" else raise RuntimeError, "unsupported dump style: #{style.dump}" end end end # vim:filetype=ruby: nadoka-0.10.0/plugins/autoop.nb000077500000000000000000000016041371026053200163410ustar00rootroot00000000000000# -*-ruby-*- # # $Id$ # =begin == Name Auto OP == Abstract Auto OP == Configuration BotConfig = [ { :name => :AutoOP, :friends => [ { :nick => 'hoge' }, { :user => 'fuga' }, ] }, ] == License This program is free software with ABSOLUTELY NO WARRANTY. You can re-distribute and/or modify this program under the same terms of the Ruby's license. == Author NARUSE, Yui =end class AutoOP < Nadoka::NDK_Bot def bot_initialize @friends = @bot_config.fetch(:friends, []) end def friend?(prefix) @friends.any? do |friend| friend[:nick] == prefix.nick or friend[:user] == prefix.user[/~?(.*)\z/, 1] end end def on_join(prefix, ch) return unless /o/ =~ @state.channel_user_mode(ch, @state.nick) if prefix.nick != @state.nick && friend?(prefix) change_mode(ch, "+o", prefix.nick) end end end nadoka-0.10.0/plugins/backlogbot.nb000066400000000000000000000044701371026053200171420ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # =begin == Abstract BackLogBot support rich backlog management scheme. == Configuration # # Maybe you don't have to write config(default setting is very useful) # BotConfig = [ { :name => :BackLogBot, :clear => false, # if true, clear backlogs when output once :talker => false, # if true, talker will be pseudo talker :prefix => 'BL', :sender => 'backlogbot', :time_format => '%m/%d-%H:%M', } ] =end class BackLogBot < Nadoka::NDK_Bot def bot_initialize @stores = @logger.message_stores @talker = @bot_config.fetch(:talker, false) @clear = @bot_config.fetch(:clear, false) @prefix = @bot_config.fetch(:prefix, 'BL') @sender = @bot_config.fetch(:sender, 'backlogbot') @sepr = @bot_config.fetch(:separate, true) @tmfmt = @bot_config.fetch(:time_format, '%m/%d-%H:%M') @msgfmts= @bot_config.fetch(:message_format, @config.default_log[:message_format]) @pattern= @bot_config.fetch(:pattern, nil) end def channel_message? ch /\A[\&\#\+\!]/ =~ ch end def on_client_login client_count, client @stores.each_channel_pool{|ch, msgobjs| if @state.current_channels[ch] msgobjs.each{|msgobj| msg = @config.log_format_message(@msgfmts, msgobj) rch = msgobj[:ch] time = msgobj[:time] cmd = Cmd.notice(rch, "#{@prefix}(#{time.strftime(@tmfmt)}) #{msg}") client.add_prefix(cmd, @talker ? nick : @sender) client.send_msg cmd } else msgobjs.each{|msgobj| if ch == :__talk__ msg = @config.log_format_message(@config.talk_log[:message_format], msgobj) else msg = @config.log_format_message(@config.system_log[:message_format], msgobj) end next unless @pattern.nil? || @pattern =~ msg rch = msgobj[:ch] time = msgobj[:time] cmd = Cmd.notice(@state.nick, "#{@prefix}(#{time.strftime(@tmfmt)}) #{msg}") client.send_msg cmd } end } @stores.clear if @clear end end nadoka-0.10.0/plugins/checkbot.nb000066400000000000000000000021321371026053200166060ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ class CheckBot < Nadoka::NDK_Bot def bot_initialize @ch = @bot_config[:ch] || '#nadoka' @tm = @bot_config[:tm] || 30 # min @prevtm = Time.now # override me @botheader = 'checkbot' end def on_timer tm if time? && need_check? check end end def time? tm = Time.now if tm.to_i - @tm * 60 > @prevtm.to_i @prevtm = tm true else false end end ############################## # use this def check_notice text if @ch.respond_to? :each @ch.each{|ch| send_notice(ch, "#{@botheader}: #{text}") sleep 5 # Flood Protection } else send_notice(@ch, "#{@botheader}: #{text}") sleep 5 # Flood Protection end end ############################ # override me def need_check? true end def check # some work end end nadoka-0.10.0/plugins/codesearchbot.nb000066400000000000000000000035551371026053200176430ustar00rootroot00000000000000# -*-ruby; coding: utf-8 -*- vim:set ft=ruby: # # Copyright (c) 2012 Kazuhiro NISHIYAMA # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # require 'open-uri' require 'uri' require 'nkf' module CodeSearch ResultRegexp = /number of (regexp results: \d+)
\s*number of (source results: \d+)/ # https://code.google.com/p/codesearch/ EngineURI = { 'debian' => 'http://codesearch.debian.net/', } def codesearch_result engine, key engine ||= 'debian' key_uri = URI.encode(NKF.nkf('-w', key)) engine_uri = EngineURI[engine.downcase] return "unknown engine: #{engine}" unless engine_uri url = "#{engine_uri}search?q=#{key_uri}" open(url){|f| result = f.read if ResultRegexp =~ result "#{$~.captures.join(', ')} for #{key} - #{url}" else "#{key} - not found in #{engine} codesearch" end } end end if __FILE__ == $0 if ARGV.empty? abort("usage: #{$0} keyword") end include CodeSearch puts codesearch_result('debian', ARGV.join(' ')) exit end class CodeSearchBot < Nadoka::NDK_Bot include CodeSearch def bot_initialize if @bot_config.key?(:channels) channels = '\A(?:' + @bot_config[:channels].collect{|ch| Regexp.quote(ch) }.join('|') + ')\z' @available_channel = Regexp.compile(channels) else @available_channel = @bot_config[:ch] || // end @bot_name = @bot_config[:bot_name] || 'CodeSearchBot' @open_search = OpenSearch.new(@bot_config) @ch_kcode = @bot_config[:ch_kcode] end def on_privmsg prefix, ch, msg return unless /\Acodesearch(\:([a-z]+))?>\s*(.+)/ =~ msg msg = "codesearch#$1 bot: #{codesearch_result($2, $3.toutf8)}" if @ch_kcode == :jis msg = msg.tojis end send_notice ch, msg end end nadoka-0.10.0/plugins/cronbot.nb000066400000000000000000000005661371026053200165030ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # class CronBot < Nadoka::NDK_Bot def on_timer t @state.channels.each{|ch| send_notice(ch, t.to_s) } end end nadoka-0.10.0/plugins/crubybot.nb000066400000000000000000000047731371026053200166720ustar00rootroot00000000000000# -*-ruby-*- require 'ffi/clang' require 'tempfile' # dependency: # * ffi-clang.gem # * libclang # * lang/clang34 in FreeBSD ports # * llvm of homebrew # # example setting: # { # :name => :CRubyBot, # :channels => [ "#ruby-ja" ], # :ruby_srcdir => '/path/of/ruby/src', # } # class CRubyBot < Nadoka::NDK_Bot def bot_initialize if @bot_config.key?(:channels) channels = '\A(?:' + @bot_config[:channels].collect{|ch| Regexp.quote(ch) }.join('|') + ')\z' @available_channel = Regexp.compile(channels) else @available_channel = @bot_config.fetch(:ch, //) end unless @ruby_srcdir = @bot_config[:ruby_srcdir] raise "ruby_srcdir is not specified" end unless Dir.exist?(@ruby_srcdir) raise "ruby_srcdir(#{@ruby_srcdir}) does not exist" end @ruby_srcdir << '/' if @ruby_srcdir[-1] != '/' @translation_unit = nil @translation_unit_rev = nil end def bot_state "<#{self.class.to_s}>" end def translation_unit rev = IO.read("#@ruby_srcdir/revision.h").to_s[/\d+/].to_i return @translation_unit if @translation_unit_rev == rev ary = Dir[File.join @ruby_srcdir, "*.c"] f = Tempfile.open(["crubybot-ruby", ".c"]) ary = Dir[File.join @ruby_srcdir, "*.c"] ary << File.join(@ruby_srcdir, "win32/win32.c") ary.each do |fn| f.puts %[#include "#{fn}"] end f.flush index = FFI::Clang::Index.new @translation_unit = index.parse_translation_unit(f.path, "-I#{@ruby_srcdir}/include") @translation_unit_rev = rev f.close(true) @translation_unit end def find_def(name, kind) translation_unit.cursor.visit_children do |cursor, parent| if cursor.kind == kind && cursor.definition? && cursor.spelling == name loc = cursor.location path = loc.file if path.start_with?(@ruby_srcdir) return [path[@ruby_srcdir.size..-1], loc.line] end end next :recurse end return nil end def on_privmsg(client, ch, message) return unless @available_channel === ch case message when /\A\S*(struct|fun\w*)\s+(\w+)/ kind1 = $1 name = $2 kind = case kind1 when "struct" :cursor_struct when /\Afun/ :cursor_function end path, line = find_def(name, kind) return unless path send_notice(ch, "crubybot: #{kind1} #{name} at " \ "https://github.com/ruby/ruby/blob/trunk/#{path}#L#{line}") end end end nadoka-0.10.0/plugins/dictbot.nb000066400000000000000000000020131371026053200164520ustar00rootroot00000000000000# -*- ruby; coding: utf-8 -*- vim:set ft=ruby: =begin This plugin is test version. =end require 'uri' require 'open-uri' require 'kconv' class DictBot < Nadoka::NDK_Bot def bot_initialize @available_channel = @bot_config[:ch] || /.*/ @nkf = @bot_config[:nkf] || "-Wj" end def on_privmsg prefix, ch, msg if @available_channel === ch msg = msg.toutf8 if /\Adic(.)>\s*(.+)\s*/ =~ msg res = yahoo_dict $1, $2 send_notice(ch, NKF.nkf(@nkf, res)) end end end YAHOO_DICT_TYPE ={ 't' => 2, 'e' => 1, 'j' => 0, 'w' => 3, 'r' => 5, } def yahoo_dict type, word "dict bot> " + if type = YAHOO_DICT_TYPE[type] word = URI.encode(word) uri = "http://dic.yahoo.co.jp/dsearch?ei=UTF-8&p=#{word}&stype=0&dtype=#{type}" open(uri){|f| if // =~ f.read "#{$1} - #{uri}" else uri end } else "unknown type: #{type}" end end end nadoka-0.10.0/plugins/drbcl.rb000077500000000000000000000007111371026053200161220ustar00rootroot00000000000000# # An example for drbot.nb # # require 'drb/drb' class Invokee include DRbUndumped def initialize invoker @invoker = invoker @invoker.add_observer self end def update *args p args end def destruct @invoker.delete_observer self end end uri = ARGV.shift || raise DRb.start_service invoker = DRbObject.new(nil, uri) begin invokee = Invokee.new(invoker) p invoker puts '---' gets ensure invokee.destruct end nadoka-0.10.0/plugins/drbot.nb000066400000000000000000000031511371026053200161400ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # =begin == Abstract An example using DRb. You can access to nadoka via DRb protocol. See sample drbcl.rb == Configuration BotConfig = [ { :name => :DRbot, :port => 12345, :host => '', } ] =end require 'drb/drb' require 'observer' class DRbot < Nadoka::NDK_Bot class Dispatcher include Observable def initialize bot @bot = bot end def send_to_client prefix, command, args changed notify_observers(prefix, command, args) end def recv_from_client *args @bot.kick_from_client args end def notify_observers(*arg) if defined? @observer_state and @observer_state if defined? @observer_peers @observer_peers.dup.each{|e| begin e.update(*arg) rescue Exception @observer_peers.delete e end } end @observer_state = false end end end def bot_initialize @port = @bot_config.fetch(:port, 12346) @host = @bot_config.fetch(:host, '') @invoker = Dispatcher.new(self) @uri = "druby://#{@host}:#{@port}" @drbserver = DRb.start_service(@uri, @invoker) end def bot_destruct @drbserver.stop_service end def on_every_message prefix, command, *args @invoker.send_to_client prefix, command, args end def recv_from_client *args end end nadoka-0.10.0/plugins/evalbot.nb000066400000000000000000000021241371026053200164610ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # ruby evaluator on IRC # # $Id$ # require 'timeout' class EvalBot < Nadoka::NDK_Bot EvalNick = 'nadoka_eval' def on_client_privmsg client, ch, message if ch == EvalNick ans = eval_message(message) msg = Cmd.privmsg(@state.nick, 'ans: ' + ans) client.send_to_client client.add_prefix(msg, EvalNick) raise ::Nadoka::NDK_BotSendCancel end end def on_nadoka_command client, command, *params if command == 'eval' msg = Cmd.privmsg(@state.nick, 'Hello, this is ruby evaluator') client.send_to_client client.add_prefix(msg, EvalNick) raise ::Nadoka::NDK_BotSendCancel end end def eval_message message begin ans = Thread.new{ $SAFE = 4 timeout(3){ Kernel.eval(message) } }.value.to_s rescue Exception => e ans = e.message end end end nadoka-0.10.0/plugins/githubissuesbot.nb000066400000000000000000000075071371026053200202620ustar00rootroot00000000000000# -*-ruby; coding: utf-8 -*- vim:set ft=ruby: # Copyright (C) 2013 Kazuhiro NISHIYAMA # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # =begin == Configuration: BotConfig << { :name => :GithubIssuesBot, :bot_name => 'gh', :ch => '#nadoka_check', :tm => 30, # min #:nkf => "--oc=CP50221 --ic=UTF-8 --fb-xml", :owner => "nadoka", :repo => "nadoka", } =end require 'open-uri' require 'time' begin require 'json' rescue LoadError require 'rubygems' require 'json' end module GithubIssues module_function DEFAULT_HEADER = { 'User-Agent' => "Nadoka-GithubIssuesBot/0.1", } def uri_read(uri, header) debug = $GITHUB_ISSUES_DEBUG p uri if debug uri.open("r", header) do |f| p f.meta if debug return f.read end rescue OpenURI::HTTPError => e p e.io.meta if debug if e.io.meta["x-ratelimit-remaining"] == "0" raise "#{e}: because x-ratelimit-remaining=0" end raise "#{e}: #{uri}" end def issues(owner, repo, since, state='open', header=DEFAULT_HEADER) since = since.utc.strftime("%Y-%m-%dT%H:%M:%SZ") uri = URI("https://api.github.com/repos/#{owner}/#{repo}/issues?since=#{since}&state=#{state}") json = uri_read(uri, header) issues = JSON.parse(json) issues.each do |issue| comments_uri = URI("#{issue['comments_url']}?since=#{since}") json = uri_read(comments_uri, header) comments = JSON.parse(json) if comments.empty? yield [:issue, issue, issue_to_s(issue)] else comments.each do |comment| yield [:comment, comment, comment_to_s(comment, issue)] end end end end def user_to_s(user) return unless user "@#{user['login']} " end def time_to_s(time) return unless time time = Time.parse(time) time.localtime.strftime("%H:%M ") end def issue_to_s(issue) return unless issue "#{issue['html_url']} [#{issue['state']}] #{time_to_s(issue['updated_at'])}#{user_to_s(issue['user'])}#{issue['title']}" end def comment_to_s(comment, issue=nil) return unless comment if issue info = "[#{issue['state']}] " else end "#{comment['html_url']} #{info}#{time_to_s(comment['updated_at'])}#{user_to_s(comment['user'])}#{comment['body']}" end end if __FILE__ == $0 $GITHUB_ISSUES_DEBUG = $DEBUG owner = "rubima" repo = "rubima" since = Time.now - 60*60*3*7 GithubIssues.issues(owner, repo, since) do |_, _, s| puts s.gsub(/\s+/, ' ') end GithubIssues.issues(owner, repo, since, 'close') do |_, _, s| puts s.gsub(/\s+/, ' ') end exit end require 'nkf' class GithubIssuesBot < Nadoka::NDK_Bot def bot_initialize @ch = @bot_config.fetch(:ch, '#nadoka_check') @tm = @bot_config.fetch(:tm, 30) # min @prevtm = Time.now @nkf_options = @bot_config.fetch(:nkf, "--oc=CP50221 --ic=UTF-8 --fb-xml") @owner = @bot_config.fetch(:owner, "nadoka") @repo = @bot_config.fetch(:repo, "nadoka") end def bot_state nt = Time.at(@prevtm.to_i + @tm * 60) "<#{self.class}: next check at #{nt.asctime}@#{@ch}>" end def send_notice(ch, msg) msg = msg.gsub(/\s+/, ' ') if @nkf_options msg = NKF.nkf(@nkf_options, msg) end super(ch, msg) end def on_timer tm check end def check tm = Time.now if tm.to_i - @tm * 60 > @prevtm.to_i make_notice tm end end def make_notice tm since = @prevtm @prevtm = tm GithubIssues.issues(@owner, @repo, since) do |_, _, s| send_notice @ch, s end GithubIssues.issues(@owner, @repo, since, 'close') do |_, _, s| send_notice @ch, s end rescue Exception => e send_notice(@ch, "github issues bot error for #{@owner}/#{@repo}: #{e}") @manager.ndk_error e end end nadoka-0.10.0/plugins/gonzuibot.nb000066400000000000000000000017361371026053200170550ustar00rootroot00000000000000# gonzui bot # require 'open-uri' require 'uri' class GonzuiBot < Nadoka::NDK_Bot ResultRegexp = %r(>Results (\d+) - (\d+) of (\d+)\s*(.+)/ =~ msg send_notice ch, "gonzui bot: #{gonzui_result($1, $2)}" end end EngineURI = { 'raa' => 'http://raa.ruby-lang.org/gonzui/', 'gnome' => 'http://gonzui.tagonome.org/', 'cpan' => 'http://cpansearch.bulknews.net/', 'b-src' => 'http://b-src.cbrc.jp/', } def gonzui_result engine, key engine ||= 'raa' key_uri = URI.encode(key) engine_uri = EngineURI[engine.downcase] return "unknown engine: #{engine}" unless engine_uri url = "#{engine_uri}search?q=#{key_uri}" open(url){|f| result = f.read if ResultRegexp =~ result "#{$3} for #{key} - #{url}" else "#{key} - not found in #{engine}" end } end end nadoka-0.10.0/plugins/googlebot.nb000066400000000000000000000277061371026053200170230ustar00rootroot00000000000000# -*-ruby; coding: utf-8 -*- vim:set ft=ruby: # # Copyright (c) 2004-2005 SASADA Koichi # Copyright (c) 2009, 2010 Kazuhiro NISHIYAMA # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # =begin == Usage with irc client google> keyword -> search keyword by google with default search langage google:[lang]> keyword -> search keyword by google with [lang] langage googlec> k1 k2 k3 k4 k5(max 5 words) -> search and show each hit count googlec> k1 k2 k3 k4 k5(max 5 words) -> search and show each hit count with default count language googlec:[lang]> k1 k2 k3 k4 k5(max 5 words) -> search and show each hit count with [lang] langage == Configuration: BotConfig = [ { :name => :GoogleBot, :ch => /.*/, :headers => { #"User-Agent" => "Ruby/#{RUBY_VERSION}", 'Referer' => 'https://github.com/nadoka/nadoka', }, # API key :api_key => 'INSERT_YOUR_API_KEY', # Custom search engine ID :cx => '017576662512468239146:omuauf_lfve', :googlec_maxwords => 5, :search_default_lang => 'ja', :count_default_lang => '', :ch_kcode => :tojis, }, ] =end unless "".respond_to?(:encode) require 'iconv' end require 'kconv' require 'shellwords' require 'cgi' require 'open-uri' begin require 'json' rescue LoadError require 'rubygems' require 'json' end if __FILE__ == $0 # for test module Nadoka class NDK_Bot def bot_init_utils end def initialize @bot_config = Hash.new @bot_config[:headers] = { "User-Agent" => "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) snap Chromium/80.0.3987.132 Chrome/80.0.3987.132 Safari/537.36", } bot_initialize end end end end class GoogleBot < Nadoka::NDK_Bot def bot_initialize bot_init_utils @search_default_lang = (@bot_config[:search_default_lang] || 'ja').sub(/^lang_/, '') @googlec_maxwords = @bot_config[:googlec_maxwords] || 5 @count_default_lang = (@bot_config[:count_default_lang] || '').sub(/^lang_/, '') @headers = @bot_config.fetch(:headers, {}) @api_key = @bot_config[:api_key] @cx = @bot_config[:cx] @uri_slog = @bot_config.fetch(:uri_slog, false) @ch_kcode = @bot_config.fetch(:ch_kcode, :tojis) end def on_privmsg prefix, ch, msg return unless @available_channel === ch return if same_bot?(ch) msg = NKF.nkf('-w', msg) if response = dispatch_command(msg) send_notice(ch, response.send(@ch_kcode)) end end SEARCHER = %w!web calc code local video blogs news books images ime imed patent suggest!.freeze SEARCHER_RE = Regexp.new("(?:" + SEARCHER.join('|') + ")").freeze def search_searcher key SEARCHER.each{|searcher| if /\A#{key}/ =~ searcher return searcher end }; nil end def dispatch_command msg begin case msg when /^g>\s*(.+)/ custom_search $1 when /^goo(o*)gle( #{SEARCHER_RE})?(:.*?)?>\s*(.+)/o, /^gu(u*)guru(#{SEARCHER_RE})?(:.+)?>\s*(.+)/o "goo#{$1}gle#{$2} bot#{$3}: #{search($1.length, $3, $4, $2)}" when /^googlec( #{SEARCHER_RE})?(:.*?)?>\s*(.+)/o "googlec#{$1} bot#{$2}: #{googlec($1, $3, $2)}" when /^g(\w+)?(:.*?)?>\s*(.+)/ searcher = $1 ? search_searcher($1) : 'web' "google #{searcher} bot#{$2}: #{search(0, $2, $3, searcher)}" if searcher end rescue Exception => e @manager.ndk_error e "google bot: #{e.class} (#{e.message} @ #{e.backtrace[0]})" end end def custom_search word uri = "https://www.googleapis.com/customsearch/v1" uri << "?key=#{@api_key}&cx=#{@cx}&q=" uri << CGI.escape(word) @logger.slog "GoogleBot: #{uri}" if @uri_slog result = open(uri, @headers) do |f| JSON.parse(f.read) end @logger.slog "GoogleBot: #{result}" if @uri_slog count = result["searchInformation"]["totalResults"].to_i if count == 0 return "no match" end count = count.to_s.gsub(/(\d)(?=\d{3}+$)/, '\\1,') item, = result["items"] title = item["title"] url = item["link"] "#{title} - #{url} (and #{count} hit#{(count.to_i > 1) ? 's' : ''})".delete("\r\n") rescue OpenURI::HTTPError => e @logger.slog "GoogleBot: #{e.inspect}" if @uri_slog result = JSON.parse(e.io.read) if @uri_slog @logger.slog "GoogleBot: #{result.inspect}" end result["error"]["errors"][0]["reason"].delete("\r\n") end def do_search word, cnt, lang, searcher='web' i = 0 begin uri = "http://ajax.googleapis.com/ajax/services/search/" uri << searcher uri << "?v=1.0&q=" uri << CGI.escape(word) if @api_key uri << "&key=#{CGI.escape(@api_key)}" end cnt = cnt.to_i if cnt > 0 uri << "&start=#{cnt.to_i}" end if lang uri << "&hl=#{CGI.escape(lang)}" if searcher == 'web' uri << "&lr=lang_#{CGI.escape(lang)}" end end @logger.slog "GoogleBot: #{uri}" if @uri_slog result = open(uri, @headers) do |f| JSON.parse(f.read) end def result.estimatedTotalResultsCount self["responseData"]["cursor"]["estimatedResultCount"] end result rescue Exception => e retry if (i+=1) < 5 raise end end def api_search word, cnt, lang, searcher result = do_search word, cnt, lang, searcher if result["responseData"].nil? # {"responseData": null, "responseDetails": "qps rate exceeded", "responseStatus": 503} return "error #{result['responseStatus']}: #{result['responseDetails']}" end count = result.estimatedTotalResultsCount.to_i if count > 0 count = count.to_s.gsub(/(\d)(?=\d{3}+$)/, '\\1,') url = title = '' e = result["responseData"]["results"][0] url = e['unescapedUrl'] || e['url'] || e['postUrl'] title = show_char_code_and_erase_tag(e['titleNoFormatting']) url = shorten_url(url) "#{title} - #{url} (and #{count} hit#{(count.to_i > 1) ? 's' : ''})" else "no match" end end def google_calc exp @logger.slog("google_calc<#{exp.dump}") uri = "https://www.google.co.jp/search?ie=UTF8&oe=UTF-8&q=#{CGI.escape(exp)}" html = open(uri, @headers) do |f| f.read end open("g.html", "wb") { |f| f.write html } if $DEBUG if /class=r [^<>]+>(.+?)<\/b>/u =~ html result = $1 # @logger.slog("google_calc>#{result.dump}") result.gsub!(/(.+?)<\/sup>/u) { "^(#{$1})" } result.gsub!(/<.+?>/u, '') result.gsub!(/&\#215;/u, "\303\227") return result elsif /<[^<>]+ id="cwos"[^<>]*>([^<>]+)]+ id="cwles"[^<>]*>([^<>]+)#{result.dump}") result.gsub!(/ /u, " ") result.gsub!(/\s+/, " ") return result elsif /
#{result.dump}") return result elsif /(.*)<\/g-card>/ =~ html result = $1 result.sub!(//u, '') return result elsif /
= ([^<>]+)#{result.dump}") return result else #IO.write('g.html', html) if STDOUT.tty? "response error" end rescue Exception $!.to_s end def google_suggest(word, lang) uri = "http://suggestqueries.google.com/complete/search?output=firefox" uri << "&q=" uri << CGI.escape(word) if lang uri << "&hl=#{CGI.escape(lang)}" end @logger.slog "GoogleBot: #{uri}" if @uri_slog result = open(uri, @headers) do |f| JSON.parse(f.read) end result[1].join(", ") end def google_code key return "http://google.com/codesearch#search/&q=#{CGI.escape(key)}&ct=os" end if defined?(URI.encode_www_form) def encode_www_form(enum) URI.encode_www_form(enum) end else def encode_www_form(enum) enum.map do |k, v| "#{URI.encode(k)}=#{URI.encode(v)}" end.join('&') end end # see http://www.google.com/intl/ja/ime/cgiapi.html def google_ime text, d=false url = 'http://www.google.com/transliterate?' url << encode_www_form('langpair' => 'ja-Hira|ja', 'text' => text) data = open(url,@headers){|f| # TODO: gsub fix invalid JSON, should remove after fix response # see http://www.google.com/support/forum/p/ime/thread?tid=06501c8b7a16add3&hl=ja JSON.parse(f.read.gsub(/,(?=\n\])/,'')) } if d result = data.map do |org, candidates| "#{org}=#{candidates.join('/')}" end.join(' ') else result = data.map do |org, candidates| candidates[0] end.join('') end show_char_code_and_erase_tag(result) rescue Exception $!.to_s[/.+/] # first line end def search cnt, lang, word, searcher=nil lang = lang_check(lang) searcher = searcher_check(searcher) word = search_char_code(word) case searcher when 'code' google_code word when 'calc' google_calc word when 'ime' google_ime word when 'imed' google_ime word, true when 'suggest' google_suggest word, lang else api_search word, cnt, lang, searcher end end def googlec lang, word, searcher=nil lang = lang_check(lang, @count_default_lang) searcher = searcher_check(searcher) words = Shellwords.shellwords(word).map{|e| "\"#{e}\""} return 'too many options' if words.size > @googlec_maxwords words.map{|rw| w = search_char_code(rw) result = do_search "'#{w}'", 0, lang, searcher "#{rw}(#{result.estimatedTotalResultsCount.to_s.gsub(/(\d)(?=\d{3}+$)/, '\\1,')})" }.join(', ') end def erase_tag str CGI.unescapeHTML(str.gsub(/\<.+?\>/, '')) end def lang_check lang, default = @search_default_lang if !lang @search_default_lang else lang = lang[1..-1] if lang.empty? nil elsif /^lang_/ =~ lang lang.sub(/^lang_/, '') else lang end end end def searcher_check searcher if !searcher 'web' else searcher = searcher.strip if SEARCHER.include?(searcher) searcher else 'web' end end end def show_char_code_and_erase_tag str if str.respond_to?(:encode) return CGI.unescapeHTML(erase_tag(str.toutf8)) end return CGI.unescapeHTML(erase_tag(str.toeuc)) case $KCODE when 'EUC', 'SJIS' CGI.unescapeHTML(str.gsub(/\<.+?\>/, '')) when 'NONE', 'UTF-8' begin str = Iconv.conv("EUC-JP", "UTF-8", str) CGI.unescapeHTML(str.gsub(/\<.+?\>/, '')) rescue => e "(char code problem: #{e.class}[#{e.message.dump}])" end else str end end def search_char_code str if str.respond_to?(:encode) return str.toutf8 end case $KCODE when 'EUC', 'SJIS' str.toeuc when 'NONE' begin Iconv.conv("UTF-8", "EUC-JP", str.toeuc) rescue => e raise "(char code problem: #{e.class})" end when 'UTF-8' str else raise end end def shorten_url(url) case url when %r!\Ahttp://www\.amazon\.co\.jp/.*(/dp/.+)\z! "http://amazon.jp#{$1}" else # default: do nothing url end end end if __FILE__ == $0 if ARGV.empty? puts "ad hoc test usage:" puts " ruby -vd plugins/googlebot.nb 'gc>1+1'" puts " ruby -vd plugins/googlebot.nb 'gc>1ドルを円ã§'" end require 'logger' google_bot = GoogleBot.new google_bot.instance_eval do @logger = Object.new def @logger.slog(log) STDERR.puts "slog>#{log}" end end ARGV.each do |arg| puts arg puts google_bot.dispatch_command(arg) end end nadoka-0.10.0/plugins/identifynickserv.nb000066400000000000000000000015171371026053200204120ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (c) 2009 Kazuhiro NISHIYAMA # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # =begin == Abstract send IDENTIFY to NickServ. == Configuration BotConfig << { :name => :IdentifyNickServ, #:nickserv => "NickServ", :nick => "nadoka", :pass => "hoge", } =end class IdentifyNickServ < Nadoka::NDK_Bot def bot_initialize @nickserv = @bot_config.fetch(:nickserv, "NickServ") @nick = @bot_config.fetch(:nick, false) @pass = @bot_config.fetch(:pass, false) end def on_server_connected(*args) if @pass if @nick send_privmsg @nickserv, "IDENTIFY #{@nick} #{@pass}" else send_privmsg @nickserv, "IDENTIFY #{@pass}" end end end end nadoka-0.10.0/plugins/mailcheckbot.nb000066400000000000000000000113161371026053200174550ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (C) 2004 Kazuhiro NISHIYAMA # All rights reserved. # This is free software with ABSOLUTELY NO WARRANTY. # # You can redistribute it and/or modify it under the terms of # the Ruby's licence. # =begin BotConfig = [ { :name => :MailCheckBot, :Maildir => "~/Maildir/.ml.debian-security-announce", :template => "biff(DSA): %{subject}s", :channels => ['#nadoka'], }, { :name => :MailCheckBot, :mh_dir => [ "~/Mail/ml/nadoka", "~/Mail/ml/yarv-dev", ], :channels => %w[#nadoka #nadoka_check], :template => "biff(%{x-ml-name}s): %{subject}s - http://www.atdot.net/mla/%{x-ml-name}s/%{x-mail-count}d", } ] =end require 'nkf' class MailCheckBot < Nadoka::NDK_Bot class Error < StandardError; end class MailCheckInfo def initialize @biffed = Hash.new update_entries do |fields| true end end def entries files = [] @glob_patterns.each do |pattern| files.concat(Dir.glob(pattern)) end files end def mailread(filename) fields = Hash.new("".freeze) header_lines = "" message_id = nil File.foreach(filename) do |line| case line when /^\s*$/ break when /^message-id:\s*/i message_id = $' return message_id, fields if @biffed.key?(message_id) end header_lines.concat(line) end header_lines.split(/^(?!\s)/).each do |line| line = NKF.nkf("-e", line) line.gsub!(/\s+/, ' ') key, value = line.split(/:/, 2) key.downcase! value = value.to_s value.strip! # use last field if same keys found fields[key.freeze] = value.freeze end return message_id, fields rescue # ignore missing files after glob end def update_entries old_entries = @biffed.keys new_entries = entries (new_entries - old_entries).each do |filename| mid, fields = mailread(filename) next unless mid # the file is not probably a mail message next if @biffed.key?(mid) # already biffed yield(fields) @biffed[mid.freeze] = true end if not(old_entries.empty?) and new_entries.empty? @biffed.clear end end end class MaildirInfo < MailCheckInfo def initialize(dir) full_path = File.expand_path(dir).freeze @glob_patterns = [ File.expand_path("cur/*,", dir).freeze, File.expand_path("new/*", dir).freeze, ].freeze super() end end class MhInfo < MailCheckInfo def initialize(dir) full_path = File.expand_path("#{dir}/*") @glob_patterns = [ full_path.freeze, ].freeze super() end end def bot_initialize @on_timer = nil p [:MailCheckBot, :bot_initialize] if $DEBUG @m_infos = {} @bot_config.freeze @channels = @bot_config[:channels].collect do |ch| NKF.nkf("-j -m0", ch).freeze end.freeze @template = (@bot_config[:template] || "biff: %{subject}s").freeze if @bot_config.key?(:Maildir) if @bot_config.key?(:mh_dir) raise Error, "both :Maildir and :mh_dir found in #{@bot_config.inspect}" end @bot_config[:Maildir].each do |dir| raise Error, "duplicated Maildir: #{dir}" if @m_infos.key?(dir) @m_infos[dir] = MaildirInfo.new(dir) end elsif @bot_config.key?(:mh_dir) @bot_config[:mh_dir].each do |dir| raise Error, "duplicated MH dir: #{dir}" if @m_infos.key?(dir) @m_infos[dir] = MhInfo.new(dir) end else raise Error, ":Maildir or :mh_dir not found in #{@bot_config.inspect}" end end def bot_state keys = @m_infos.keys dir, = File.basename(keys[0]) if keys.size > 1 dir = "#{dir}, ..." end "\#<#{self.class}: #{dir} -> #{@channels.join(' ')}>" end def on_timer t p [:MailCheckBot, :on_timer, t] if $DEBUG if @on_timer $stderr.puts "MailCheckBot#on_timer duplicated (t=#{t} and old t=#{@on_timer})" if $DEBUG return end @on_timer = t mailcheck ensure @on_timer = false end def mailcheck @m_infos.each do |dir, m_info| m_info.update_entries do |fields| bark(apply_template(fields)) end end end def apply_template(fields) msg = @template.dup msg.gsub!(/%\{([a-z0-9_\-]+)\}([sd])/i) do field_name, field_type = $1, $2 field_name.downcase! case field_type when 's' fields[field_name].to_s when 'd' fields[field_name].to_i.to_s else "(field type bug: `#{field_type}')" end end NKF.nkf("-j -m0", msg).freeze end def bark(msg) @channels.each do |ch| send_notice(ch, msg) end end end nadoka-0.10.0/plugins/marldiabot.nb000077500000000000000000000061041371026053200171500ustar00rootroot00000000000000# -*-ruby-*- require 'cgi' require 'net/http' require 'rexml/document' require 'uri' require 'time' require 'json' class MarldiaBot < Nadoka::NDK_Bot def bot_initialize @chats = @bot_config.fetch(:chats, nil) @proxy_addr = @bot_config.fetch(:proxy_addr, nil) @proxy_port = @bot_config.fetch(:proxy_port, 80) @chs = @chats.keys @chats.each_value do |chat| chat[:uri] = URI(chat[:url]) chat[:query] = 'type=xml&' + chat[:data]. map{|k,v|"#{CGI.escape k.to_s}=#{CGI.escape v.to_s}"}.join('&') req = Net::HTTP::Get.new(chat[:uri].path+ '?' + chat[:query]) chat[:req] = req end end def bot_state "<#{self.class.to_s}>" end def slog(msg, nostamp = false) current_method = caller.first[/:in \`(.*?)\'/, 1].to_s msg.each_line do |line| @logger.slog "#{self.class.to_s}##{current_method} #{line}", nostamp end end def on_timer(t) @chats.each_pair do |ch, chat| uri = chat[:uri] current_id = chat[:current_id] body = nil messages = [] Net::HTTP::Proxy(@proxy_addr, @proxy_port).start(uri.host, uri.port) do |http| body = http.request(chat[:req]).body end doc = REXML::Document.new(body) doc.each_element('feed/entry/log/article') do |art| id = art.text('id') break if current_id == id time = Time.parse(art.text('updated')) next if time + 10 * 60 < Time.now user = art.text('author/name') text = CGI.unescapeHTML(art.text('body')).gsub(/\n|<.*?>/," ") text = text[/^[\w\W]{0,100}/u] text.gsub!(/([\w\W])/u){|c|c == "\xA0" ? " " : c} messages << "#{time.strftime('%H:%M')} #{user}: #{text}" end messages.reverse_each do |message| send_notice ch, message sleep 0.5 end chat[:current_id] = doc.text('/feed/entry/log/article/id') end rescue Errno::ETIMEDOUT, Timeout::Error, SocketError rescue Errno::ECONNRESET => err slog "%s: %s (%s)" % [err.backtrace[0], err.message, err.class] rescue Exception => err detail = ("%s: %s (%s)\n" % [err.backtrace[0], err.message, err.class]) + err.backtrace[1..-1].join("\n") slog "Exception\n#{detail}" end def send_marldia(ch, message) chat = @chats[ch] uri = chat[:uri] req = Net::HTTP::Post.new(uri.path) req.body = chat[:query] + '&type=xml&body=' + CGI.escape(message) Net::HTTP::Proxy(@proxy_addr, @proxy_port).start(uri.host, uri.port) {|http| http.read_timeout = @timeout res = http.request(req) @logger.dlog res.code } return true rescue Errno::ECONNRESET => err slog "%s: %s (%s)" % [err.backtrace[0], err.message, err.class] rescue Exception => err detail = ("%s: %s (%s)\n" % [err.backtrace[0], err.message, err.class]) + err.backtrace[1..-1].join("\n") slog "Exception\n#{detail}" return false end def on_client_privmsg(client, ch, message) ch.downcase! return unless @chs.include?(ch) msg = send_marldia(ch, message) ? 'sent to marldia: ' : 'marldia send faild: ' msg << message slog msg end end nadoka-0.10.0/plugins/messagebot.nb000066400000000000000000000045231371026053200171630ustar00rootroot00000000000000# -*-ruby; coding: utf-8 -*- vim:set ft=ruby: # # Copyright (C) 2004 Kazuhiro NISHIYAMA # All rights reserved. # This is free software with ABSOLUTELY NO WARRANTY. # # You can redistribute it and/or modify it under the terms of # the Ruby's licence. # =begin BotConfig = [ { :name => :MessageBot, :message_file => 'message.yaml', :root_key => Setting_name, :channels => %w[#nadoka #Ruby:*.jp], :nkf => "-Ww", }, ] =end require 'nkf' require 'time' require 'yaml/store' class MessageBot < Nadoka::NDK_Bot def bot_initialize @store = YAML::Store.new(@bot_config[:message_file]) @root_key = @bot_config[:root_key] @channels = @bot_config[:channels].collect{|ch| ch.downcase } @nkf = @bot_config[:nkf] || "-WjXm0" load_message end def load_message @store.transaction do |db| if db.root?(@root_key) h = db[@root_key] @list = h['list'] || Hash.new @message = h['message'] || Hash.new else @list = Hash.new @message = Hash.new end end end def save_message @store.transaction do |db| db[@root_key] = { 'list' => @list, 'message' => @message, } end end def on_privmsg prefix, ch, msg user = prefix.nick c = NKF.nkf('-w', ch.to_s).downcase return unless @channels.include?(c) u = user.downcase now = Time.now key = "#{c} #{u}" message_id = "<#{now.strftime('%Y%m%d%H%M%S')}.#{now.usec}.#{u}@#{c}>" if @list.key?(key) message_id_list = @list[key] message_id_list.each do |message_id| h = @message[message_id] next if h.key?('delivered') message = "#{h['from']}ã•ã‚“ã‹ã‚‰#{h['to']}ã•ã‚“ã¸ä¼è¨€ã€Œ#{h['body']}ã€" send_notice(ch, NKF.nkf(@nkf, message)) @message[message_id]['delivered'] = now end @list.delete(key) save_message end if /^ä¼è¨€ (\S+) (.+)$/ =~ NKF.nkf('-w', msg.to_s) to_nick, body = $1, $2 @message[message_id] = { 'from' => user, 'to' => to_nick, 'date' => now, 'channel' => ch, 'body' => body, } key = "#{c} #{to_nick.downcase}" @list[key] ||= [] @list[key].push(message_id) save_message send_notice(ch, NKF.nkf(@nkf, "#{$1}ã•ã‚“ã¸ã®ä¼è¨€ã‚’承りã¾ã—㟠> #{u}ã•ã‚“")) end end end nadoka-0.10.0/plugins/modemanager.nb000066400000000000000000000060361371026053200173120ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # =begin == Abstract Mode management bot == Configuration === nadokarc BotConfig => [ { :name => :ModeManager, # # You can specify modes setting in file or # String directly. # :file => 'modes', # or :modes_file => 'modes', :modes => <<-__EOS__, [all] +o=*.jp __EOS__ # wait for setting modes (by second, avarage) :wait => 5, } ] === mode setting format [ or 'all'] <+-> pattern <+-> /regexp pattern/ : 'b' or 'v' or 'o' <+->: '+' means add mode, '-' means remove mode [example] ------------------------------- [all] +o=*.jp +o=/.*\.net/ [#nadoka] +o=kiyoya* unak* -o=ko1* ----------------------------------------- =end class ModeManager < Nadoka::NDK_Bot class Fnmexp def initialize str @str = str end def =~ x File.fnmatch @str, x, File::FNM_CASEFOLD end end def parse_setting setting s = nil setting.each_line{|l| l.strip! if /^\[(all|[\#\&\!\+].+)\]/ =~ l s = @config.identical_channel_name($1) elsif /^\[%(.+)\]/ =~ l # for compatibility with madoka s = @config.identical_channel_name("\##{$1}:*.jp") elsif s && l.sub!(/^([+-][bvo]+)\s*=\s*/, '') mode = $1 l.scan(/\S+/){|prefix| if %r!^/(.*)\/$! =~ prefix user = [mode, Regexp.new($1, Regexp::IGNORECASE)] else user = [mode, Fnmexp.new(prefix)] end @userlist[s] << user } end } end def bot_initialize @userlist = Hash.new{|h, k| h[k] = []} if file = (@bot_config[:modes_file] || @bot_config[:file]) begin parse_setting File.read(file) rescue Exception => e "operator manager: #{e.class}(#{e.message})" end end if setting = @bot_config[:modes] parse_setting setting end @wait = @bot_config[:wait].to_i @wait = 3 if @wait <= 0 end def search_mode_in_list list, prefix list.each{|e| if e[1] =~ prefix return e[0] end } nil end def search_mode ch, prefix search_mode_in_list(@userlist['all'], prefix) || search_mode_in_list(@userlist[ch], prefix) end def on_join prefix, rch ch = @config.canonical_channel_name(rch) Thread.new{ sleep(rand(@wait * 20) / 10.0) if prefix.nick != @state.nick && (/o/ =~ @state.channel_user_mode(ch, @state.nick)) if mode = search_mode(ch, prefix.to_s) current_modes = @state.channel_user_mode(ch, prefix.nick).split(//) if /^\+/ =~ mode noneed = mode.split(//)[1..-1].all?{|c| current_modes.include?(c)} else noneed = mode.split(//)[1..-1].all?{|c| !current_modes.include?(c)} end change_mode(rch, mode, prefix.nick) unless noneed end end } end end nadoka-0.10.0/plugins/opensearchbot.nb000066400000000000000000000107411371026053200176650ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (C) 2011 Kazuhiro NISHIYAMA # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # =begin == Usage with irc client bing> keyword -> search keyword by bing googlecode> keyword -> search keyword by Google Code Search Data API (Deprecated) koders> keyword -> search keyword by koders == Configuration: BotConfig << { :name => :OpenSearchBot, :bot_name => 'bing', :ch => //, :referer => 'https://github.com/nadoka/nadoka', :ch_kcode => :jis, :html => 'http://www.bing.com/search?q={searchTerms}', :rss => 'http://api.search.live.com/rss.aspx?source=web&query={searchTerms}' , } BotConfig << { :name => :OpenSearchBot, :bot_name => 'googlecode', :ch => //, :ch_kcode => :jis, :referer => 'https://github.com/nadoka/nadoka', # Google Code Search Data API (Deprecated) # http://code.google.com/intl/ja/apis/codesearch/docs/2.0/developers_guide.h tml :html => 'http://www.google.com/codesearch?q={searchTerms}', :rss => 'http://www.google.com/codesearch/feeds/search?q={searchTerms}', } BotConfig << { :name => :OpenSearchBot, :bot_name => 'koders', :ch => //, :referer => 'https://github.com/nadoka/nadoka', :ch_kcode => :jis, # http://www.koders.com/search/KodersDescriptionOS1_1.xml :html => 'http://www.koders.com/?s={searchTerms}', :rss => 'http://www.koders.com/?s={searchTerms}&results=code&output=rss&OSve rsion=1.1', } =end require 'open-uri' require 'uri' require 'cgi' class OpenSearch def initialize(options) @html = options[:html] @rss = options[:rss] @referer = options[:referer] || 'https://github.com/nadoka/nadoka' end def result(key) escaped_key = CGI.escape(key) link = @html.sub(/\{searchTerms\}/) { escaped_key } uri = @rss.sub(/\{searchTerms\}/) { escaped_key } open(uri, "Referer" => @referer) do |f| result = f.read if /<([A-Za-z]+):totalResults>(\d+)<\/\1:totalResults>/ =~ result total = $2.to_i return "#{total.to_s.gsub(/\d(?=\d{3}+\z)/,'\&,')} result#{total > 1 ? 's' : ''} in #{link}" else return "#{key} - not found in #{link}" end end end end if __FILE__ == $0 h = { 'bing' => { :referer => 'https://github.com/nadoka/nadoka', :html => 'http://www.bing.com/search?q={searchTerms}', :rss => 'http://api.search.live.com/rss.aspx?source=web&query={searchTerms}', }, 'googlecode' => { :referer => 'https://github.com/nadoka/nadoka', # Google Code Search Data API (Deprecated) # http://code.google.com/intl/ja/apis/codesearch/docs/2.0/developers_guide.html :html => 'http://www.google.com/codesearch?q={searchTerms}', :rss => 'http://www.google.com/codesearch/feeds/search?q={searchTerms}', }, 'koders' => { :referer => 'https://github.com/nadoka/nadoka', # http://www.koders.com/search/KodersDescriptionOS1_1.xml :html => 'http://www.koders.com/?s={searchTerms}', :rss => 'http://www.koders.com/?s={searchTerms}&results=code&output=rss&OSversion=1.1', }, 'youtube' => { :referer => 'https://github.com/nadoka/nadoka', :html => 'http://www.youtube.com/results?search_query={searchTerms}', :rss => 'http://gdata.youtube.com/feeds/api/videos?q={searchTerms}', }, } engine = ARGV.shift if h.key?(engine) open_search = OpenSearch.new(h[engine]) ARGV.each do |key| result = open_search.result(key) puts result end else STDERR.puts "usage: #{$0} {#{h.keys.sort.join('|')}} key ..." end exit end class OpenSearchBot < Nadoka::NDK_Bot def bot_initialize if @bot_config.key?(:channels) channels = '\A(?:' + @bot_config[:channels].collect{|ch| Regexp.quote(ch) }.join('|') + ')\z' @available_channel = Regexp.compile(channels) else @available_channel = @bot_config[:ch] || // end @bot_name = @bot_config[:bot_name] || 'OpenSearchBot' @open_search = OpenSearch.new(@bot_config) @pattern = @bot_config[:pattern] || /\A#{Regexp.quote(@bot_name)}\s*[<:>]\s*(.+)/ @ch_kcode = @bot_config[:ch_kcode] end def on_privmsg prefix, ch, msg if @pattern =~ msg key = $1 if @ch_kcode == :jis ret = @open_search.result(key.toutf8).tojis else ret = @open_search.result(key) end send_notice ch, "#{@bot_name} bot: #{ret}" end end end nadoka-0.10.0/plugins/opshop.nb000066400000000000000000000006411371026053200163370ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # operator shop # # $Id$ # class OpShop < Nadoka::NDK_Bot def on_join prefix, ch if prefix.nick != @state.nick change_mode(ch, "+o", prefix.nick) end end end nadoka-0.10.0/plugins/pastebot.nb000066400000000000000000000017751371026053200166610ustar00rootroot00000000000000=begin This plugin is test version. =end require 'open-uri' class PasteBot < Nadoka::NDK_Bot def bot_initialize @ch = @bot_config[:ch] || /./ @msg = @bot_config[:mgs] || /\Apaste>/ @service_uri = @bot_config[:service_uri] || 'http://www.atdot.net/sp' @fmsg = @bot_config[:mgs] || /\Afpaste>/ @fservice_uri = @bot_config[:service_uri] || 'http://www.atdot.net/fp' end def nick_escape nick nick.gsub(/[^A-Za-z\d\-_]/, '_') end def on_privmsg prefix, ch, msg nick = nick_escape prefix.nick if @ch === ch if @msg === msg nid = '' open("#{@service_uri}/check/newid"){|f| nid = f.gets } send_notice ch, "#{@service_uri}/view/#{nid}_#{nick}" end if @fmsg === msg nid = '' open("#{@fservice_uri}/check/newid"){|f| nid = f.gets } send_notice ch, "#{@fservice_uri}/view/#{nid}_#{nick}" end end end end nadoka-0.10.0/plugins/roulettebot.nb000066400000000000000000000013541371026053200174010ustar00rootroot00000000000000=begin This plugin is test version. =end require 'shellwords' require 'kconv' class RouletteBot < Nadoka::NDK_Bot def bot_initialize @available_channel = @bot_config[:ch] || /.*/ end def on_privmsg prefix, ch, msg if @available_channel === ch if /\Aroulette>\s*(.+)\s*/ =~ msg send_notice(ch, "roulette bot: #{randomize($1)[0].tojis}") elsif /\Ashuffle>\s*(.+)\s*/ =~ msg send_notice(ch, "shuffle bot: #{ randomize($1).join(' ').tojis}") elsif /\Arandom>\s*((\d+)|)/ =~ msg num = $2 ? $2.to_i : 1000 send_notice(ch, "random bot: #{prefix.nick} -> #{rand num}") end end end def randomize msgs res = Shellwords.shellwords(msgs.toeuc).sort_by{rand} end end nadoka-0.10.0/plugins/rss_checkbot.nb000066400000000000000000000052301371026053200174770ustar00rootroot00000000000000# -*-ruby; coding: utf-8 -*- vim:set ft=ruby: # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # =begin configuration like follows: BotConfig = [ { :name => :RSS_CheckBot, :rss_paths => [ 'http://www.ruby-lang.org/ja/index.rdf', ], :cache => "./rss-cache", :ch => '#nadoka_check', :tm => 30 # check interval time(minute) :over_message => nil # } =end require 'lib/rss_check' require 'kconv' class RSS_CheckBot < Nadoka::NDK_Bot def bot_initialize @cache = File.expand_path(@bot_config.fetch(:cache, '~/.rss_check')) @paths = @bot_config.fetch(:rss_paths, ['http://www.ruby-lang.org/ja/index.rdf']) @ch = @bot_config.fetch(:ch, '#nadoka_check') @tm = @bot_config.fetch(:tm,30) # min @over = @bot_config.fetch(:over_message, nil) @rssc = RSS_Check.new(@paths, @cache, true) @prevtm= Time.now end def bot_state nt = Time.at(@prevtm.to_i + @tm * 60) "<#{self.class}: next check at #{nt.asctime}@#{@ch}>" end def __on_privmsg prefix, ch, msg if /^rss> check/ =~ msg && ch == @ch && prefix.nick == @state.nick make_notice Time.now end end def send_notice(ch, msg) if ch.respond_to? :each ch.each{|c| super(c, msg.tojis) sleep 5 # Flood Protection } else super(ch, msg.tojis) sleep 5 # Flood Protection end end def on_timer tm check end def check tm = Time.now if tm.to_i - @tm * 60 > @prevtm.to_i make_notice tm end end def make_notice tm @prevtm = tm begin items = @rssc.check @rssc.save rescue => e send_notice(@ch, "rss bot error: #{e}") @manager.ndk_error e return end make_notice_thread items end def make_notice_thread items Thread.new{ begin items.each{|e| if e[:ccode] == 'UTF-8' begin # convert from U+FF5E FULLWIDTH TILDE to U+301C WAVE DASH title = e[:title].gsub(/\357\275\236/u, "\343\200\234") title = title.toeuc rescue Exception # maybe, char code translation error next end else title = e[:title] end send_notice(@ch, "rss bot: #{title} - #{e[:about]}") } rescue Exception => e send_notice(@ch, "rss bot error: #{e}") @manager.ndk_error e end send_notice(@ch, "rss bot: #{@over}") if @over } end end nadoka-0.10.0/plugins/samplebot.nb000066400000000000000000000007461371026053200170230ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # # bot_file_name and BotClassName must be same name # (BotClassName.downcase == bot_file_name) class SampleBot < Nadoka::NDK_Bot # Yes person def on_privmsg prefix, ch, msg send_notice(ch, "Yes, #{prefix.nick}!") end end nadoka-0.10.0/plugins/sendpingbot.nb000066400000000000000000000005231371026053200173420ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (c) 2004-2006 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # class SendPingBot < Nadoka::NDK_Bot def on_timer *args @manager.ping_to_clients end end nadoka-0.10.0/plugins/shellbot.nb000066400000000000000000000023531371026053200166450ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # shell command bot # # $Id$ # require 'timeout' require 'kconv' class ShellBot < Nadoka::NDK_Bot ShellNick = 'nadoka_shell' def on_client_privmsg client, ch, message if ch == ShellNick ans = exec_shell(message) ans.each{|line| msg = Cmd.privmsg(@state.nick, 'ans: ' + line) client.send_to_client client.add_prefix(msg, ShellNick) } raise ::Nadoka::NDK_BotSendCancel end end def on_nadoka_command client, command, *params if command == 'shell' msg = Cmd.privmsg(@state.nick, 'Hello, this is shell command executor') client.send_to_client client.add_prefix(msg, ShellNick) raise ::Nadoka::NDK_BotSendCancel end end def exec_shell message begin ans = Thread.new{ begin timeout(3){ str = `#{message}`.to_s str.tojis } rescue Exception => e e.message end }.value rescue Exception => e ans = e.message end end end nadoka-0.10.0/plugins/sixamobot.nb000066400000000000000000000031451371026053200170360ustar00rootroot00000000000000#-*-ruby; coding: euc-jp -*- # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # Sixamo bot # # add config like this: # =begin BotConfig = [ { :name => :SixamoBot, :dir => '~/sixamo', :ch => '#nadoka', :tm => 10, :id => /(sixamo)|(¤·¤·¤ã¤â)/, :rt => [10, 5, 4, 4, 3, 2], } =end # # $Id$ # require 'sixamo' require 'kconv' class SixamoBot < Nadoka::NDK_Bot def bot_initialize @sixamo_dir = @bot_config[:dir] || '~/sixamo' @sixamo_ch = @bot_config[:ch] || '#nadoka' @sixamo_tm = @bot_config[:tm] || 10 @sixamo_id = @bot_config[:id] || /(sixamo)|(¤·¤·¤ã¤â)/ @sixamo_rt = @bot_config[:rt] || [10, 5, 4, 4, 3, 2] @prev = Time.now make_sixamo end def make_sixamo @sixamo = Sixamo.new(File.expand_path(@sixamo_dir)) end def on_privmsg prefix, ch, msg return unless @sixamo_ch === ch begin msg = Kconv.toeuc(msg) @sixamo.memorize msg unless @sixamo_id === msg rnd = case Time.now - @prev when 0..10; @sixamo_rt[0] when 10..20; @sixamo_rt[1] when 20..30; @sixamo_rt[2] when 30..60; @sixamo_rt[3] when 60..120;@sixamo_rt[4] else ; @sixamo_rt[5] end return if Kernel.rand(rnd) != 1 end @prev = Time.now talk = @sixamo.talk(msg) @sixamo.memorize talk send_notice ch, 'sixamo: ' + talk.tojis rescue make_sixamo end end end nadoka-0.10.0/plugins/tenkibot.nb000066400000000000000000000111461371026053200166500ustar00rootroot00000000000000# -*- coding: utf-8 -*- # -*-ruby-*- # # Copyright (c) 2020 Kazuhiro NISHIYAMA # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # =begin == Abstract Answer weather information using "https://www.jma.go.jp/" == Usage tenki> [AREA] [AREA] should be an area name in Kanji listed on following table. https://www.jma.go.jp/jp/yoho/ == Configuration BotConfig << { :name => :TenkiBot, :ch => //, :timeout => 10, } =end require 'nokogiri' require 'open-uri' require 'tmpdir' module Tenki AREA = {} TMPDIR = Dir.mktmpdir('tenkibot') EN_CACHE_HTML = File.join(TMPDIR, 'en.html~') EN_URI = URI('https://www.jma.go.jp/en/yoho/') JP_CACHE_HTML = File.join(TMPDIR, 'jp.html~') JP_URI = URI('https://www.jma.go.jp/jp/yoho/') def get(uri, cache=nil) raise Errno::ENOENT unless cache File.read(cache) rescue Errno::ENOENT html = uri.read File.write(cache, html) if cache html end def init_tenki jp = get(JP_URI, JP_CACHE_HTML) doc = Nokogiri::HTML(jp) doc.xpath('//noscript//a').each do |e| AREA[e.text] = JP_URI + e[:href] end en = get(EN_URI, EN_CACHE_HTML) doc = Nokogiri::HTML(en) doc.xpath('//select[@name="elfukenlist"]/option').each do |e| AREA[e.text] = JP_URI + "/jp/yoho/#{e[:value]}.html" if e[:value] end end def tenki(area) begin uri = AREA.fetch(area) rescue KeyError => e if e.respond_to?(:corrections) && !e.corrections.empty? raise "ã‚‚ã—ã‹ã—ã¦ï¼š#{e.corrections.join(' or ')}" else raise "例: #{AREA.keys.sample(10).join(', ')}" end end html = get(uri) doc = Nokogiri::HTML(html) forecast = doc.css('table.forecast') tenki = {} area = nil forecast.xpath('./tr').each do |forecast_tr| th_area = forecast_tr.css('.th-area') unless th_area.empty? # 見出ã—行 area = th_area.text tenki[area] = [] next end weather = {} th_weather = forecast_tr.css('th.weather') weather[:when] = th_weather.text.strip weather[:title] = th_weather.xpath('img/@title').to_s.strip forecast_tr.css('table.rain').each do |rain_table| weather[:rain] = [] rain_table.css('tr').map do |tr| time_range = tr.css('td[align=left]').text.strip percent = tr.css('td[align=right]').text.strip if /\d/ =~ percent weather[:rain].push [time_range, percent] end end end forecast_tr.css('table.temp').each do |temp_table| weather[:temp] = [] temp_table.css('tr').map do |tr| city = tr.css('td.city').text.strip min = tr.css('td.min').text.strip max = tr.css('td.max').text.strip if /./ =~ city weather[:temp].push(city: city, min: min, max: max) end end end tenki[area].push weather end textframe = doc.css('pre.textframe') tenki_time = textframe.children[0]&.text&.lines&.[](1)&.strip tenki_text = textframe.children[2]&.text&.strip&.[](/.+/)&.gsub(/\s+/, ' ') if $DEBUG p tenki, tenki_time, tenki_text end texts = [] tenki.each do |area, weathers| text = "#{area}: " text += weathers.map do |weather| if weather[:rain]&.any? rain_text = weather[:rain]&.map{|time_range,percent|"#{time_range}:#{percent}"}.join(';') rain_text = "(#{rain_text})" end [ "#{weather[:when]}:#{weather[:title]}", weather[:temp]&.map{|temp|"(#{temp[:city]}:#{temp[:min]}-#{temp[:max]})"}&.join(''), rain_text, ].compact.join('') end.join(', ') texts << text end texts << "#{tenki_text} (#{tenki_time})" return texts end end if __FILE__ == $0 include Tenki if ARGV.empty? puts "#$0 area" else init_tenki ARGV.each do |area| puts tenki(area) end end exit end class TenkiBot < Nadoka::NDK_Bot include Tenki def bot_initialize bot_init_utils init_tenki @nkf = @bot_config.fetch(:nkf, "-Wj") end def on_privmsg prefix, ch, msg return unless @available_channel === ch return if same_bot?(ch) msg = NKF.nkf('-w', msg) if @nkf if /\Atenki>/ =~ msg area = $'.strip.toutf8 begin results = tenki(area) rescue => e results = ["#{e}"] end results.each do |result| msg = "tenki bot: #{result}".gsub(/\s+/, ' ') msg = NKF.nkf(@nkf, msg) if @nkf send_notice ch, msg end end end end nadoka-0.10.0/plugins/timestampbot.nb000066400000000000000000000023341371026053200175400ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # =begin == Abstract Add time stamp to each log. == Configuration BotConfig = [ { :name => :TimeStampBot, :interval => 60 * 60, # sec :stampformat => '== %y/%m/%d-%H:%M:%S ==========================================', :client => false, } ] =end class TimeStampBot < Nadoka::NDK_Bot def bot_initialize @interval = @bot_config.fetch(:interval, 60 * 60) # default: 1 hour @stampformat = @bot_config.fetch(:stampformat, '== %y/%m/%d-%H:%M:%S ==========================================') @client = @bot_config.fetch(:client, false) @nexttime = nexttime end def nexttime t = (Time.now.to_i + @interval) Time.at(t - (t % @interval)) end def on_timer tm if tm >= @nexttime stamp_log @nexttime = nexttime end end def stamp_log msg = @nexttime.strftime(@stampformat) @state.channels.each{|ch| @logger.clog(ch, msg, true) } @logger.slog(msg, true) if @client end end nadoka-0.10.0/plugins/titlebot.nb000077500000000000000000000245661371026053200166740ustar00rootroot00000000000000# -*-ruby; coding: utf-8 -*- # vim:set ft=ruby: # # Copyright (c) 2009, 2011, 2012 Kazuhiro NISHIYAMA # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # =begin == Abstract Reply title of URL. == Configuration BotConfig << { :name => :TitleBot, :ch => //, :timeout => 10, :headers => { #"User-Agent" => "Ruby/#{RUBY_VERSION}", } } =end begin require 'iconv' rescue LoadError end require 'nkf' require 'open-uri' require 'tempfile' require 'timeout' require 'tmpdir' begin require 'cgi/util' rescue LoadError require 'cgi' end begin require 'rubygems' require 'nokogiri' rescue LoadError end unless OpenURI.redirectable?(URI("http://example.com"), URI("https://example.com")) def OpenURI.redirectable?(uri1, uri2) # :nodoc: uri1.scheme.downcase == uri2.scheme.downcase || (/\A(?:http|ftp)\z/i =~ uri1.scheme && /\A(?:https?|ftp)\z/i =~ uri2.scheme) end end module URL2Title module_function BLACKLIST_HOST = [ /\blocalhost\b/, /\A127\./, /\A192\.168\./, /\A10\./, /\A169\.254\./, ] if defined?(::Nadoka::VERSION) DEFAULT_USER_AGENT = "Ruby/#{RUBY_VERSION} NadokaTitleBot/#{Nadoka::VERSION}" else DEFAULT_USER_AGENT = "Ruby/#{RUBY_VERSION}" end def get_title(url, headers) uri = URI(url) info = { :uri => uri } info[:errors] = [] case uri.host when *BLACKLIST_HOST info[:title] = "(ignored)" return info when /\A(?:twitter\.com)\z/ # Twitter response depends on User-Agent headers = headers.merge!('User-Agent' => DEFAULT_USER_AGENT) end headers['User-Agent'] ||= DEFAULT_USER_AGENT headers = { :content_length_proc => proc{|x| raise Errno::EFBIG if x && x > 1048576}, }.merge!(headers) uri.open(headers) do |f| info[:uri] = f.base_uri body = f.read if /\.blog\d+\.fc2\.com\z/ =~ uri.host # set last content-type only f.meta_add_field("content-type", f.meta["content-type"].split(/, /)[-1]) end case f.content_type when /\Atext\// charset = f.charset{} # without block, returns "iso-8859-1" # Content-Encoding case when f.content_encoding.empty? # ignore when f.content_encoding.any?{|c_e| /deflate/ =~ c_e } require "zlib" body = Zlib::Inflate.inflate(body) when f.content_encoding.any?{|c_e| /gzip/ =~ c_e } require "zlib" body = Zlib::GzipReader.new(StringIO.new(body)).read || '' end # encoding if charset # ok elsif /charset=[\'\"]?([^;\'\">]+)/ni =~ body charset = $1 end case charset when /shift[_-]jis/i charset = "cp932" when /euc-jp/i # euc-jp, x-euc-jp charset = "eucjp-ms" end if /\A(?:utf-8|eucjp-ms)\z/i =~ charset # avoid # # or Iconv::IllegalSequence body = NKF.nkf("-wm0x --ic=#{charset}", body) elsif charset charset.sub!(/\Ax-?/i, '') if defined?(Iconv) begin body = Iconv.conv("utf-8", charset, body) rescue Iconv::IllegalSequence => e info[:errors] << e body = NKF.nkf("-wm0x", body) rescue Iconv::InvalidEncoding => e info[:errors] << e body = NKF.nkf("-wm0x", body) end else body = body.encode("utf-8", charset, :invalid => :replace, :undef => :replace) end else body = NKF.nkf("-wm0x", body) end title = nil if %r"[^<>]*)>(.*?)[^<>]*)>"miu =~ body title = $1 end case f.base_uri.host when /\A(?:twitter\.com)\z/ if defined?(::Nokogiri) doc = Nokogiri::HTML(body, uri.to_s, 'utf-8') title = doc.xpath("//meta[@property='og:description'][1]/@content").text end when /\A(?:mobile\.twitter\.com)\z/ if defined?(::Nokogiri) doc = Nokogiri::HTML(body, uri.to_s, 'utf-8') tweet, = doc.css('.main-tweet .tweet-text') if tweet tweet = tweet.inner_html tweet.gsub!(/<.*?>/, '') tweet.strip! title = tweet unless tweet.empty? end end when /\A(?:github\.com)\z/ # ignore og:title when /\A(?:www\.youtube\.com)\z/ if %r'"title":"(.+?)"' =~ body title = $1 end else if defined?(::Nokogiri) doc ||= Nokogiri::HTML(body, uri.to_s, 'utf-8') og_title = doc.xpath("//meta[@property='og:title'][1]/@content").text unless og_title.empty? # title is escaped string when get by regexp title = CGI.escapeHTML(og_title) end elsif // =~ body title = $1 end if uri.fragment && defined?(::Nokogiri) begin doc ||= Nokogiri::HTML(body, uri.to_s, 'utf-8') xpath = "//*[@id='#{uri.fragment}' or @name='#{uri.fragment}']" fragment_element = doc.xpath(xpath) # tDiary style unless fragment_element.xpath("span[@class='sanchor']").empty? fragment_element = fragment_element.xpath("..") end info[:fragment_text] = truncate(fragment_element.text) rescue Exception => e info[:errors] << e end end end if defined?(::Nokogiri) doc ||= Nokogiri::HTML(body, uri.to_s, 'utf-8') canonical_uri = doc.xpath("//link[@rel='canonical'][1]/@href") unless canonical_uri.empty? info[:uri] = shorten_url(canonical_uri.text) end elsif / FULLWIDTH TILDE end info[:title] = title || body return info when /\Aimage\// if f.respond_to?(:path) && f.path info[:title] = `identify '#{f.path}'`.sub(/\A#{Regexp.quote(f.path)}/, '').strip return info else info[:title] = "(unknown image format)" temp = Tempfile.new("titlebot", :encoding => 'ascii-8bit') begin temp.write(body) temp.close info[:title] = `identify '#{temp.path}'`.sub(/\A#{Regexp.quote(temp.path)}/, '').strip ensure temp.unlink end return info end else info[:title] = "#{f.content_type} #{f.size} bytes" return info end end rescue Errno::EFBIG info[:title] = "(too big)" return info end def truncate s if /\A(?>(.{197})....)/mu =~ s return $1+'...' else return s end end def prepare_url(url) url.sub(/\/\#!\//, '/') end def shorten_url(url) case url when %r!\Ahttps?://www\.amazon\.co\.jp/.*(/dp/[^/]+)! URI("https://amazon.jp#{$1}") else # default: do nothing URI(url) end rescue URI::InvalidURIError url end def cleanup(title) title.gsub(/(\s| | | |\u{A0})+/i, ' ') end def url2title(url, headers) url = prepare_url(url) info = get_title(url, headers) info[:title] = truncate(cleanup(info[:title])) info end end if __FILE__ == $0 require File.expand_path('../../ndk/version', __FILE__) def u2t(url) firefox = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0" ua = "NadokaTitleBot/#{Nadoka::VERSION}" headers = { "User-Agent" => "#{firefox} Ruby/#{RUBY_VERSION} #{ua}", } URL2Title.url2title(url, headers) rescue puts $!.backtrace info = { :uri => url } info[:title] = $!.inspect info end URL2Title::BLACKLIST_HOST << /\.test\z/ if ARGV.empty? # TODO: test else ARGV.each do |url| info = u2t(url) p info puts info[:uri] puts info[:title] end end exit end class TitleBot < Nadoka::NDK_Bot include URL2Title def bot_initialize if @bot_config.key?(:channels) channels = '\A(?:' + @bot_config[:channels].collect{|ch| Regexp.quote(ch) }.join('|') + ')\z' @available_channel = Regexp.compile(channels) else @available_channel = @bot_config.fetch(:ch, //) end @same_bot = @bot_config.fetch(:same_bot, /(?!)/) @nkf_options = @bot_config.fetch(:nkf, "--oc=CP50221 --ic=UTF-8 --fb-xml") @timeout = @bot_config.fetch(:timeout, 60) @skip_nkf_msg = @bot_config.fetch(:skip_nkf_msg, false) @fragment_size_range = @bot_config.fetch(:fragment_size_range, 5..100) @headers = @bot_config.fetch(:headers, {}) end def send_notice(ch, msg) msg = msg.tr("\r\n", " ") if @nkf_options msg = NKF.nkf(@nkf_options, msg) end super(ch, msg) end def on_privmsg prefix, ch, msg return unless @available_channel === ch msg = NKF.nkf('-w', msg) unless @skip_nkf_msg case msg when /^[^<>]+>/ # ignore messages to other bots when /https?:/ return if @state.channel_users(ccn(ch)).find{|x| @same_bot =~ x } msg.scan(%r<(?\()?(?https?://(?()(?[!-~&&[^()]]+|\(\g\))+|[!-~]+))>) do url = $~[:url] info = Timeout.timeout(@timeout) do url2title(url, @headers) end return unless info[:title] if url != info[:uri].to_s send_notice(ch, "title bot: #{info[:title]} - #{info[:uri]}") else send_notice(ch, "title bot: #{info[:title]}") end if info[:fragment_text] # ignore when fragment_text is too long or too short if @fragment_size_range === info[:fragment_text].size send_notice(ch, "title bot:: #{info[:fragment_text]}") end end info[:errors].each do |e| @manager.ndk_error e send_notice(ch, "title bot error: #{e}") end end end rescue Exception => e send_notice(ch, "title bot error! #{e}") @manager.ndk_error e end end nadoka-0.10.0/plugins/twitterbot.nb000077500000000000000000000123671371026053200172510ustar00rootroot00000000000000# -*-ruby-*- # nadoka-twit # # = Usage # # == Get consumer key # # 1. access https://twitter.com/apps/new # 2. register it # 3. memo 'Consumer key' and 'Consumer secret' # # == Get access token # # 1. run this script with consumer key and consumer secret like: # ruby twitterbot.nb # 2. memo access_token and access_token_secret # # == Setting nadokarc # # 1. set :consumer_key, :consumer_secret, :access_token, # and :acccess_token_secret # # = Configuration # # == :ch # # target channel # # == :pattern # # pattern for messages to send twitter # # == :nkf_encoding # # the encoding of messages # # == :consumer_key, :consumer_secret # # Consumer key and consumer secret # # == :access_token, :acccess_token_secret # # Access token and access token secret # require 'time' require 'rubygems' require 'user_stream' require 'rubytter' require 'json' if __FILE__ == $0 key = ARGV.shift secret = ARGV.shift unless key && secret puts "Usage: #$0 " end oauth = Rubytter::OAuth.new(key, secret) request_token = oauth.get_request_token system('open', request_token.authorize_url) || puts("Access here: #{request_token.authorize_url}\nand...") print "Enter PIN: " pin = gets.strip access_token = request_token.get_access_token( :oauth_token => request_token.token, :oauth_verifier => pin ) puts ":access_token => '#{access_token.token}'," puts ":access_token_secret => '#{access_token.secret}'," exit end class TwitterBot < Nadoka::NDK_Bot def bot_initialize @ch = @bot_config.fetch(:ch, nil) @pattern = @bot_config.fetch(:pattern, />tw$/) @nkf_encoding = @bot_config.fetch(:nkf_encoding, nil) consumer = OAuth::Consumer.new( @bot_config.fetch(:consumer_key, nil), @bot_config.fetch(:consumer_secret, nil), :site => 'https://api.twitter.com') access_token = OAuth::AccessToken.new(consumer, @bot_config.fetch(:access_token, nil), @bot_config.fetch(:access_token_secret, nil)) @rt = OAuthRubytter.new(access_token) @current_id = -1 UserStream.configure do |config| config.consumer_key = @bot_config.fetch(:consumer_key, nil) config.consumer_secret = @bot_config.fetch(:consumer_secret, nil) config.oauth_token = @bot_config.fetch(:access_token, nil) config.oauth_token_secret = @bot_config.fetch(:access_token_secret, nil) end @streamer = Thread.new do loop do UserStream.client.user do |status| begin case # https://dev.twitter.com/docs/streaming-apis/messages #when status[:delete] #when status[:scrub_geo] when status[:limit] when status[:status_withheld] when status[:user_withheld] when status[:friends] when status[:event] when status[:for_user] when status[:control] when status[:warning] screen_name = status[:code] time = Time.parse(status[:created_at]) send_notice @ch, "#{time.strftime('%H:%M')} #{screen_name}: #{status.text}" when status[:user] screen_name = status[:user][:screen_name] if status[:retweeted_status] status = status[:retweeted_status] screen_name << ":" screen_name << status[:user][:screen_name] end time = Time.parse(status[:created_at]) text = status.text text.tr!("\r\n", ' ') text.gsub!(/</, '<') text.gsub!(/>/, '>') text.gsub!(/"/, '"') text = NKF.nkf('--numchar-input --ic=UTF-8 --oc=' + @nkf_encoding, text) if @nkf_encoding text.gsub!(/&/, '&') send_notice @ch, "#{time.strftime('%H:%M')} #{screen_name}: #{text}" else send_notice @ch, status.inspect end rescue => e @logger.slog e.inspect slog e.backtrace slog e.inspect slog status.inspect end end @logger.slog "user stream finished...and restart" end end end def bot_destruct @streamer.kill @streamer.join rescue Timeout::Error end def on_client_privmsg(client, ch, message) return unless @ch.nil? or @ch.upcase == ch.upcase unless @pattern =~ message slog 'pattern unmatch, ignored' return end text = message.sub(@pattern, '') text = NKF.nkf('--oc=UTF-8 --ic=' + @nkf_encoding, text) if @nkf_encoding slog((@rt.update(text) ? 'sent to twitter: ' : 'twitter send faild: ') + message) rescue Exception => err puts_error_message(err) end def slog(msg, nostamp = false) current_method = caller.first[/:in \`(.*?)\'/, 1].to_s msg.each do |line| @logger.slog "#{self.class.to_s}##{current_method} #{line}", nostamp end end private def puts_error_message(err) if err.is_a?(Rubytter::APIError) || err.is_a?(JSON::ParserError) @logger.slog "%s: %s (%s) %s" % [err.backtrace[0], err.message, err.class, err.response] else @logger.slog "%s: %s (%s)" % [err.backtrace[0], err.message, err.class] end @logger.slog err.backtrace.select{|l|/\A\/home/=~l} end end nadoka-0.10.0/plugins/weba.nb000066400000000000000000000122501371026053200157440ustar00rootroot00000000000000# -*-ruby-*- # # Copyright (c) 2004-2005 SASADA Koichi # # This program is free software with ABSOLUTELY NO WARRANTY. # You can re-distribute and/or modify this program under # the same terms of the Ruby's license. # # # $Id$ # =begin == Abstract WebA: Web Accessor http interface for irc You can access IRC via (()) (by default). == Configuration BotConfig = [ { :name => :WebA, :passwd => 'WebAPassWord', # if passwd is specified, use Basic Access Authentication with id :id => 'weba', :port => 12345, :entry => 'weba', # :message_format => ... (see source) } ] =end require 'webrick' require 'lib/tagparts' class WebA < Nadoka::NDK_Bot class WebAlet < WEBrick::HTTPServlet::AbstractServlet def initialize server, bot, authorizer super @bot = bot @auth = authorizer end def do_GET req, res @auth.authenticate(req, res) if @auth begin res.body = @bot.htmlpage(req.query).to_s res['content-type'] = 'text/html; charset=Shift_JIS' rescue WebARedirect => e res.set_redirect(WEBrick::HTTPStatus::Found, "#{req.path}?ch=#{URI.encode(e.ch.tosjis)}") res.body = 'moved' end end end class WebARedirect < Exception attr_reader :ch def initialize ch @ch = ch end end include HTMLParts def htmlpage query rch = (query['ch'] || '').tojis ch = ccn(rch) ch = !ch.empty? && (@state.channels.include?(ch) || ch == 'all') && ch ttl = ch ? " - #{rch.tosjis}" : '' if ch && (msg = query['message']) && !msg.empty? msg = msg.tojis + ' (from WebA)' send_privmsg(ch, msg) raise WebARedirect.new(ch) end _html( _head(_title("WebA: IRC Web Accessor#{ttl}")), _body( _h1("WebA#{ttl}"), _p( _a({:href => "./#{@entry}?ch="+URI.encode((rch || '').tosjis)}, 'reload'), _a({:href => "./#{@entry}"}, 'top') ), case ch when 'command' command_ch else view_ch(rch, ch) select_ch(rch, ch) end )) end def select_ch rch, ch _p({:class => 'channel-list'}, (@state.channel_raw_names.sort + ['all']).map{|e| e = e.tosjis [_a({:href => "./#{@entry}?ch="+ URI.encode(e)}, e), ' '] } ) end def view_ch rch, ch return unless ch chs = ch.tosjis if ch == 'all' msgs = [] @stores.pools.each{|_, store| msgs.concat store.pool } msgs = msgs.sort_by{|msg| msg[:time]} else msgs = (@stores.pools[ch] && @stores.pools[ch].pool) || [] end _div({:class => 'irc-accessor'}, if(ch != 'all') _form({:method => 'get', :action => "./#{@entry}", :class => 'sayform'}, "msg: ", _input({:type => 'text', :name => 'message', :class => 'msgbox'}), _input({:type => 'submit', :name => 'say', :value => 'say'}), _input({:type => 'hidden', :name => 'ch', :value => ch}) ) end, _h2("channel #{ch.tosjis}"), _div({:class => 'messages'}, msgs.map{|m| if ch == 'all' && m[:ch] chn = _a({:href => "./#{@entry}?ch=#{URI.encode(m[:ch])}"}, m[:ch].tosjis) msg = @config.log_format_message(@all_message_format, m) elsif m[:type] == 'PRIVMSG' chn = _a({:href => "./#{@entry}?user=#{URI.encode(m[:user])}"}, "<#{m[:user].tosjis}>") msg = @config.log_format_message(@message_format, m) else msg = @config.log_format_message(@message_format, m) chn = '' end _div({:class=>'msg'}, "#{m[:time].strftime('%H:%M')} ", chn, "#{msg}".tosjis) }.reverse ) ) end def bot_initialize @stores = @logger.message_stores @server = WEBrick::HTTPServer.new({ :Port => @bot_config.fetch(:port, 12345), }) @entry = @bot_config.fetch(:entry, 'weba') auth = nil if passwd = @bot_config.fetch(:passwd, 'WebAPassWord') id = @bot_config.fetch(:id, 'weba') userdb = Hash.new userdb.extend(WEBrick::HTTPAuth::UserDB) userdb.auth_type = WEBrick::HTTPAuth::BasicAuth userdb.set_passwd("WebA Authentication", id, passwd) auth = WEBrick::HTTPAuth::BasicAuth.new({ :Realm => "WebA Authentication", :UserDB => userdb, :Algorithm => 'MD5-sess', :Qop => [ 'auth' ], :UseOpaque => true, :NonceExpirePeriod => 60, :NonceExpireDelta => 5, }) end @server.mount("/#{@entry}", WebAlet, self, auth) @server_thread = Thread.new{ begin @server.start rescue Exception => e @manager.ndk_error e end } @message_format = @config.default_log[:message_format].merge( @bot_config.fetch(:message_format, { 'PRIVMSG' => '{msg}', 'NOTICE' => '{{user}} {msg}', })) @all_message_format = @config.default_log[:message_format].merge( @bot_config.fetch(:all_message_format, {})) end def bot_destruct @server_thread.kill @server.shutdown sleep 1 end end nadoka-0.10.0/plugins/xibot.nb000066400000000000000000000067041371026053200161620ustar00rootroot00000000000000# coding: UTF-8 # # Xi Bot # # No rights reserved. # # Synopsis: # xi> 2d10 (two dice of ten) # [2d10] 13 = 7 + 6 # xi> 5d # [5d6] 14 = 3 + 1 + 3 + 1 + 6 # xi>100 # [1d100] 26 # class XiBot < Nadoka::NDK_Bot def bot_initialize @available_channel = @bot_config[:ch] || /.*/ end def dice(count=1, max=6) count.times{ count += rand(max) } count end def on_privmsg prefix, ch, msg return unless @available_channel === ch return unless /\Axi\s*>\s*/ =~ msg case $~.post_match.downcase when /character/ %w/STR DEX CON INT WIS CHA/.each do |name| values = (1..3).map{|i|rand(6)+1} sum = values.inject(0){|s, i|s += i} send_notice(ch, '%s: %2d = %s' % [name, sum, values.join(' + ')]) end when /char/ values = %w/STR DEX CON INT WIS CHA/.map do |name| '%s: %2d' % [name, (1..4).map{|i|rand(6)+1}.sort.last(3).inject(0){|s, i|s += i}] end send_notice(ch, "#{prefix.nick}: #{values.join(', ')}") when /san/ int = dice(2, 6) + 6 pow = dice(3, 6) san0 = pow * 5 current = san0 result = 'int:%d pow:%d san0:%d' % [int, pow, san0] case rand(10) when 9 result <<= ' you saw Great Cthulhu.' losts = [dice(1, 10), dice(1, 100)] when 7, 8 result <<= ' you saw a living dead.' losts = [1, dice(1, 10)] when 4, 5, 6 result <<= ' you saw a Dimension-Shambler.' losts = [0, dice(1, 10)] when 2, 3, 4 result <<= ' you woke up in the grave.' losts = [0, dice(1, 6)] else result <<= ' you find a dead body.' losts = [0, dice(1, 3)] end check = dice(1, 100) result << " check:#{check}" lost = losts[check > current ? 1 : 0] insane = false if lost > 0 result << " you lost #{lost} SAN point." if lost >= current # eternal insanity result << ' you went mad. (eternal)' insane = true elsif lost * 5 > current # indefinite insanity r = %w/緊張症・痴呆症 記憶喪失 サンãƒãƒ§ãƒ»ãƒ‘ンザ症ã€ãƒ‰ãƒ³ã‚­ãƒ›ãƒ¼ãƒ†ç—‡ å執症 ææ€–ç—‡ã€ãƒ•ェティッシュ 強迫観念ã€ä¸­æ¯’ã€ã‘ã„れん発作 誇大妄想 精神分裂症 犯罪性精神異常 多é‡äººæ ¼/[rand(10)] result << ' you went mad. (indefinite %s)' % NKF.nkf('-jW', r) insane = true elsif lost >= 5 idearoll = dice(1, 100) result << " idearoll:#{idearoll}" if idearoll <= int * 5 # temporary insanity result << ' you went mad. (temporary)' insane = true end end end result << ' you kept sanity.' unless insane #message = '%s: current: %d check: %d result: %s' % [prefix.nick, current, check, result] message = "#{prefix.nick}: #{result}" send_notice(ch, message) when /(?:(\d+)d)?(\d+)?(?:\*([1-9]))?/ count = $1.to_i count = 1 unless (1..100).include? count max = $2.to_i max = 6 unless (1..1_000_000_000).include? max ($3 ? $3.to_i : 1).times do values = (1..count).map{|i|rand(max)+1} sum = values.inject(0){|s, i|s += i} if count == 1 send_notice(ch, '%s: [%dd%d] %d' % [prefix.nick,count, max, sum]) else send_notice(ch, '%s: [%dd%d] %d = %s' % [prefix.nick,count, max, sum, values.join(' + ')]) end end end end end nadoka-0.10.0/rice/000077500000000000000000000000001371026053200137465ustar00rootroot00000000000000nadoka-0.10.0/rice/irc.rb000066400000000000000000000472141371026053200150600ustar00rootroot00000000000000=begin = rice - Ruby Irc interfaCE Original Credit: Original Id: irc.rb,v 1.9 2001/06/13 10:22:24 akira Exp Copyright (c) 2001 akira yamada You can redistribute it and/or modify it under the same term as Ruby. == Modified Modified by K.Sasada. $Id$ =end require 'socket' require 'thread' require 'monitor' begin require "openssl" rescue LoadError end module RICE class Error < StandardError; end class InvalidMessage < Error; end class UnknownCommand < Error; end =begin == RICE::Connection =end class Connection class Error < StandardError; end class Closed < Error; end =begin --- RICE::Connection::new =end def initialize(server, port, eol = "\r\n", ssl_params = nil) @conn = [] @conn.extend(MonitorMixin) @main_th = nil self.server = server self.port = port self.eol = eol self.ssl_params = ssl_params @read_q = Queue.new @read_th = Thread.new(@read_q, @eol) do |read_q, eol_| read_thread(read_q, eol_) end @threads = {} @threads.extend(MonitorMixin) @dispatcher = Thread.new(@read_q) do |read_q| loop do x = read_q.pop ths = @threads.synchronize do @threads.keys end ths.each do |th| if th.status @threads[th].q.push(x) else @threads.delete(th) end end end # loop end @delay = 0.3 @prev_send_time = Time.now end attr_accessor :delay attr_reader :server, :port, :ssl_params =begin --- RICE::Connection#server=(server) =end def server=(server) raise RuntimeError, "Already connected to #{@server}:#{@port}" unless @conn.empty? @server = server end =begin --- RICE::Connection#port=(port) =end def port=(port) raise RuntimeError, "Already connected to #{@server}:#{@port}" unless @conn.empty? @port = port end =begin --- RICE::Connection#eol=(eol) =end def eol=(eol) raise RuntimeError, "Already connected to #{@server}:#{@port}" unless @conn.empty? @eol = eol end =begin --- RICE::Connection#ssl_params=(ssl_params) =end def ssl_params=(ssl_params) raise RuntimeError, "Already connected to #{@server}:#{@port}" unless @conn.empty? unless ssl_params @ssl_params = false return end raise 'openssl library not installed' unless defined?(OpenSSL) ssl_params = ssl_params.to_hash ssl_params[:verify_mode] ||= OpenSSL::SSL::VERIFY_PEER store = OpenSSL::X509::Store.new if ssl_params.key?(:ca_cert) ca_cert = ssl_params.delete(:ca_cert) if ca_cert # auto setting ca_path or ca_file like open-uri.rb if File.directory? ca_cert store.add_path ca_cert else store.add_file ca_cert end else # use ssl_params={:ca_cert=>nil} if you want to disable auto setting store = nil end else # use default of openssl store.set_default_paths end if store ssl_params[:cert_store] = store end @ssl_params = ssl_params end =begin --- RICE::Connection#start(max_retry = 3, retry_wait = 30) =end def start(max_retry = 3, retry_wait = 30) @client_th = Thread.current # caller thread if alive? #sleep retry_wait return nil end @main_th = Thread.new do begin Thread.stop ensure yield(self) if block_given? @read_th.raise(Closed) if @read_th.status close(true) @client_th.raise(Closed) end end begin open_conn rescue SystemCallError max_retry -= 1 if max_retry == 0 @main_th.kill raise end sleep retry_wait retry rescue Exception @main_th.kill raise else @main_th.join end end def open_conn @conn.synchronize do conn = TCPSocket.new(@server, @port) if ssl_params context = OpenSSL::SSL::SSLContext.new context.set_params(ssl_params) conn = OpenSSL::SSL::SSLSocket.new(conn, context) conn.sync_close = true conn.hostname = @server if conn.respond_to? :hostname= conn.connect if context.verify_mode != OpenSSL::SSL::VERIFY_NONE conn.post_connection_check(@server) end end @conn[0] = conn end @conn[0].extend(MonitorMixin) @read_th.run ths = @threads.synchronize do @threads.keys end ths.each do |th| th.run if th.status && th.stop? end end private :open_conn =begin --- RICE::Connection#regist(raise_on_close, *args) {...} =end USER_THREAD = Struct.new('User_Thread', :q, :raise_on_close) def regist(raise_on_close = false, *args) read_q = Queue.new th = Thread.new(read_q, self, *args) do |read_q_, conn, *args_| yield(read_q_, conn, *args_) end @threads.synchronize do @threads[th] = USER_THREAD.new(read_q, raise_on_close) end th end =begin --- RICE::Connection#unregist(thread) =end def unregist(thread) th = nil @threads.synchronize do th = @threads.delete(th) end th.exit th end def read_thread(read_q, eol) begin read_q.clear Thread.stop begin conn = @conn[0] while l = conn.gets(eol) begin read_q.push(Message.parse(l)) rescue UnknownCommand $stderr.print l.inspect if $DEBUG rescue InvalidMessage begin read_q.push(Message.parse(l.sub(/\s*#{eol}\z/o, eol))) rescue $stderr.print l.inspect if $DEBUG end end end rescue IOError#, SystemCallError $stderr.print "#{self.inspect}: read_th get error #{$!}" if $DEBUG ensure raise Closed end rescue Closed begin @main_th.run if alive? rescue Closed end retry end end private :read_thread =begin --- RICE::Connection#close(restart = false) =end def close(restart = false) begin unless restart @main_th.exit if alive? @read_th.exit if @read_th.alive? end conn = nil @conn.synchronize do conn = @conn.shift end conn.close if conn @threads.synchronize do @threads.each_key do |th| if restart if @threads[th].raise_on_close if @threads[th].raise_on_close.kind_of?(Exception) th.raise(@threads[th].raise_on_close) else th.raise(Closed) end end else th.exit end end end end end =begin --- RICE::Connection#alive? =end def alive? @main_th && @main_th.alive? end =begin --- RICE::Connection#push(message) =end def push(message) conn = @conn[0] if conn conn.synchronize do cmd = message.command if cmd == 'PRIVMSG' || cmd == 'NOTICE' # flood control t = Time.now if t.to_i <= @prev_send_time.to_i + 2 sleep 1 end @prev_send_time = t end conn.print message.to_s unless conn.closed? end else nil end end alias << push end # Connection =begin == RICE::Message =end class Message module PATTERN # letter = %x41-5A / %x61-7A ; A-Z / a-z # digit = %x30-39 ; 0-9 # hexdigit = digit / "A" / "B" / "C" / "D" / "E" / "F" # special = %x5B-60 / %x7B-7D # ; "[", "]", "\", "`", "_", "^", "{", "|", "}" LETTER = 'A-Za-z' DIGIT = '\d' HEXDIGIT = "#{DIGIT}A-Fa-f" SPECIAL = '\x5B-\x60\x7B-\x7D' # shortname = ( letter / digit ) *( letter / digit / "-" ) # *( letter / digit ) # ; as specified in RFC 1123 [HNAME] # hostname = shortname *( "." shortname ) SHORTNAME = "[#{LETTER}#{DIGIT}](?:[-#{LETTER}#{DIGIT}\/]*[#{LETTER}#{DIGIT}])?" HOSTNAME = "#{SHORTNAME}(?:\\.#{SHORTNAME})*\\.?" # servername = hostname SERVERNAME = HOSTNAME # nickname = ( letter / special ) *8( letter / digit / special / "-" ) # (nickname starts with DIGIT may occur in split mode) NICKNAME = "[#{LETTER}#{DIGIT}#{SPECIAL}][\-#{LETTER}#{DIGIT}#{SPECIAL}]*" # user = 1*( %x01-09 / %x0B-0C / %x0E-1F / %x21-3F / %x41-FF ) # ; any octet except NUL, CR, LF, " " and "@" USER = '[\x01-\x09\x0B-\x0C\x0E-\x1F\x21-\x3F\x41-\xFF]+' # ip4addr = 1*3digit "." 1*3digit "." 1*3digit "." 1*3digit IP4ADDR = "[#{DIGIT}]{1,3}(?:\\.[#{DIGIT}]{1,3}){3}" H16 = "[#{HEXDIGIT}]{1,4}" # :nodoc: LS32 = "(?:#{H16}:#{H16})|#{IP4ADDR}" # :nodoc: # ip6addr from RFC3986 IP6ADDR = "(?:#{H16}:){6}#{LS32}|" \ "::(?:#{H16}:){5}#{LS32}|" \ "(?:#{H16})?::(?:#{H16}:){4}#{LS32}|" \ "(?:(?:#{H16}:)?#{H16})?::(?:#{H16}:){3}#{LS32}|" \ "(?:(?:#{H16}:){0,2}#{H16})?::(?:#{H16}:){2}#{LS32}|" \ "(?:(?:#{H16}:){0,3}#{H16})?::#{H16}:#{LS32}|" \ "(?:(?:#{H16}:){0,4}#{H16})?::#{LS32}|" \ "(?:(?:#{H16}:){0,5}#{H16})?::#{H16}|" \ "(?:(?:#{H16}:){0,6}#{H16})?::" # hostaddr = ip4addr / ip6addr HOSTADDR = "(?:#{IP4ADDR}|#{IP6ADDR})" # host = hostname / hostaddr HOST = "(?:#{HOSTNAME}|#{HOSTADDR})" # prefix = servername / ( nickname [ [ "!" user ] "@" host ] ) PREFIX = "(?:#{NICKNAME}(?:(?:!#{USER})?@#{HOST})?|#{SERVERNAME})" # nospcrlfcl = %x01-09 / %x0B-0C / %x0E-1F / %x21-39 / %x3B-FF # ; any octet except NUL, CR, LF, " " and ":" NOSPCRLFCL = '\x01-\x09\x0B-\x0C\x0E-\x1F\x21-\x39\x3B-\xFF' # command = 1*letter / 3digit COMMAND = "(?:[#{LETTER}]+|[#{DIGIT}]{3})" # SPACE = %x20 ; space character # middle = nospcrlfcl *( ":" / nospcrlfcl ) # trailing = *( ":" / " " / nospcrlfcl ) # params = *14( SPACE middle ) [ SPACE ":" trailing ] # =/ 14( SPACE middle ) [ SPACE [ ":" ] trailing ] MIDDLE = "[#{NOSPCRLFCL}][:#{NOSPCRLFCL}]*" TRAILING = "[: #{NOSPCRLFCL}]*" PARAMS = "(?:((?: +#{MIDDLE}){0,14})(?: +:(#{TRAILING}))?|((?: +#{MIDDLE}){14}):?(#{TRAILING}))" # crlf = %x0D %x0A ; "carriage return" "linefeed" # message = [ ":" prefix SPACE ] command [ params ] crlf CRLF = '\x0D\x0A' MESSAGE = "(?::(#{PREFIX}) +)?(#{COMMAND})#{PARAMS}\s*(#{CRLF}|\n|\r)" CLIENT_PATTERN = /\A#{NICKNAME}(?:(?:!#{USER})?@#{HOST})\z/on MESSAGE_PATTERN = /\A#{MESSAGE}\z/on end # PATTERN =begin --- RICE::Message::parse(str) =end def self.parse(str) unless PATTERN::MESSAGE_PATTERN =~ str raise InvalidMessage, "Invalid message: #{str.inspect}" else prefix = $1 command = $2 if $3 && $3.size > 0 middle = $3 trailer = $4 elsif $5 && $5.size > 0 middle = $5 trailer = $6 elsif $4 params = [] trailer = $4 elsif $6 params = [] trailer = $6 else params = [] end end params ||= middle.split(/ /)[1..-1] params << trailer if trailer self.build(prefix, command.upcase, params) end =begin --- RICE::Message::build(prefix, command, params) =end def self.build(prefix, command, params) if Command::Commands.include?(command) Command::Commands[command].new(prefix, command, params) elsif Reply::Replies.include?(command) Reply::Replies[command].new(prefix, command, params) else raise UnknownCommand, "unknown command: #{command}" end end =begin --- RICE::Message#prefix --- RICE::Message#command --- RICE::Message#params =end def initialize(prefix, command, params) @prefix = prefix @command = command @params = params end attr_accessor :prefix attr_reader :command, :params =begin --- RICE::Message::#to_s =end def to_s str = '' if @prefix str << ':' str << @prefix str << ' ' end str << @command if @params @params.each do |param| str << ' ' param_s = param.to_s if param_s.respond_to?(:force_encoding) param_s = param_s.dup.force_encoding(Encoding::ASCII_8BIT) end if (param == @params[-1]) && (param_s.size == 0 || /\A:|\s/ =~ param_s) str << ':' str << param_s else str << param_s end end end str << "\x0D\x0A" str end =begin --- RICE::Message::#to_a =end def to_a [@prefix, @command, @params] end def inspect sprintf('#<%s:0x%x prefix:%s command:%s params:%s>', self.class, self.object_id, @prefix, @command, @params.inspect) end end # Message =begin == RICE::Command =end module Command class Command < Message end # Command def self.regist_command cmd eval <