diff -urN tiarra-20080510/.svnversion tiarra-20090206/.svnversion --- tiarra-20080510/.svnversion 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/.svnversion 2009-02-09 22:30:09.000000000 +0900 @@ -1 +1 @@ -11365 \ No newline at end of file +29652 \ No newline at end of file diff -urN tiarra-20080510/ChangeLog tiarra-20090206/ChangeLog --- tiarra-20080510/ChangeLog 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/ChangeLog 2009-02-09 22:30:09.000000000 +0900 @@ -428,7 +428,7 @@ * main/IrcIO/Server.pm (new): $this->{channels}のキーを、小文字に変換しておく。 - + 大文字小文字に一貫性の無いチャンネル名をクライアントに送る、 EFnetやFreenet(IRC)のようなircdに接続していると、しばしば Tiarraは混乱する。この問題を回避するため、チャンネル名同士の @@ -438,7 +438,7 @@ 大文字小文字を無視してチャンネルを探索するように変更。 $server->channel($name)のようにしてChannelInfoを得ている 場合には、何ら変更は必要無い。 - + チャンネル名 => ChannelInfoのハッシュを返す、$server->channels を使っている場合は、そのキーが小文字に変換されている事に注意しなければ ならない。 @@ -446,7 +446,7 @@ * main/Multicast.pm (lc, uc): 追加 IRC方式で大文字と小文字の変換を行う。IRC方式とは、[]\がそれぞれ{}|の 大文字であると定義されている方式である。 - + * main/Mask.pm (compile): 追加 マスクからコンパイル済み正規表現を生成する部分を、独立した関数にした。 大量のマッチングを高速に行う場合は、Mask::の関数を何度も呼ぶ代わりに @@ -901,9 +901,9 @@ ニューメリックリプライをシンボルとして定義するクラス。 useで全シンボルをエクスポート。 - * main/IrcIO.pm: + * main/IrcIO.pm: CRが無く、LFだけで終わっているメッセージも受け入れる。 - + 2004-01-23 Topia * tiarra: $0 自体が symlink だったときに、@INC に symlink 先の @@ -915,7 +915,7 @@ * tiarra: 起動時オプション --make-password 追加。 make-passwordの機能をtiarra本体に移した。 - + * make-password: 削除 2004-01-20 phonohawk @@ -1348,7 +1348,7 @@ * module/Channel/Freeze.pm (freeze): カンマで区切られた複数のチャンネル名を認識。 - + (defrost): 凍結していないチャンネルをdefrostするとエラーが起こる問題を解決。 チャンネル名をマスクとして扱うように変更。 @@ -1367,13 +1367,13 @@ * main/Timer.pm (interval): 明示的にundefを渡す事で、リピート終了可能に。 uninstallすれば一緒なので大した意味は無い。 - - * main/IrcIO/Server.pm (_TOPIC): + + * main/IrcIO/Server.pm (_TOPIC): TOPICメッセージを受信した時、古いトピックを'old-topic'として註釈を付ける。 2003-07-17 phonohawk - * tiarra-conf.el (tiarra-conf-jump-to-block): + * tiarra-conf.el (tiarra-conf-jump-to-block): ブロック名を入力し、その位置へジャンプするコマンド。 デフォルトでは C-c C-. 及び C-c . に割当てられている。 @@ -1491,7 +1491,7 @@ * IO/Socket/INET6.pm: 追加。 IO::Socket::INETをIPv6に移植。Socket6.pmが必要。 - + * RunLoop.pm, IrcIO/Server.pm: IPv6対応。 general/tiarra-ip-versionに'v6'を指定する事で、IPv6でのリスニングを行なう。 また、サーバーには最初にIPv6での接続を試みてからIPv4にフォールバックする。 @@ -1526,7 +1526,7 @@ * Auto/Utils.pm: sendto_channel_closure 関数追加。 NOTICE/PRIVMSG の処理は面倒なので、これを自動的に処理する。 sendto_channel_closure, generate_reply_closures に使用方法のコメントを追加。 - + 2003-05-15 phonohawk * conf: @@ -1576,7 +1576,7 @@ * tiarra: SIGHUPを受信した時の動作を変更。 これまではシャットダウンしていたが、以後は設定をリロードする。 - + * ChannelInfo.pm (fullname): 追加 * RunLoop.pm: 各ネットワークの設定を変更した後リロードすると、 @@ -1588,7 +1588,7 @@ * IrcIO.pm (server_p,client_p): 追加。 それぞれIrcIO::Serverであれば1を返すメソッドと IrcIO::Clientであれば1を返すメソッド。 - + 2003-04-13 phonohawk * User/Vanish.pm: 追加 @@ -1610,7 +1610,7 @@ * LinedINETSocket.pm: メソッドconnectの動作をconnectとattachの二つに分けた。 これにより予め開かれたIO::Socket::INETに対してLinedINETSocketの機能を適用可能。 - + 2003-04-05 phonohawk * Auto/Joined.pm: 追加。 @@ -1647,7 +1647,7 @@ 2003-03-23 Topia - * CTCP/Version.pm: add perl version infomation. + * CTCP/Version.pm: add perl version information. * CTCP/ClientInfo.pm: separate ' ' instead of '/'. (ref. TAGGED DATA) @@ -1663,7 +1663,7 @@ * Channel/Join/Invite.pm: add. * Auto/Utils.pm (get_ch_name): add. - (generate_reply_closures): + (generate_reply_closures): add param ch_place(6th). default:0. place of channel name in msg->params. $get_ch_name closure return static string. @@ -1672,7 +1672,7 @@ generate_reply_closures's 3rd param(use_alias) to undef.(use default) * Auto/AliasDB/CallbackUtils.pm: add register_extcallbacks. - (for regist insecure callbacks) + (for register insecure callbacks) 2003-03-19 Topia @@ -1804,7 +1804,7 @@ 2003-03-03 phonohawk * Configuration/Block.pm: ブロック内ブロックを扱えるように。 - + * Configuration/LexicalAnalyzer.pm: 新規追加。confの字句解析器。 * Configuration/Parser.pm: 新規追加。confの構文解析器。 @@ -1890,7 +1890,7 @@ ソケットの監視にはExternalSocketを用いる。使い方はExternalSocket.pmにある。 * Timer.pm: uninstallした時はrunloopにundefを代入する。 - + 2003-02-20 phonohawk * Module.pm (notification_of_message_io) : 新規追加。 diff -urN tiarra-20080510/ChangeLog.svn tiarra-20090206/ChangeLog.svn --- tiarra-20080510/ChangeLog.svn 2008-05-11 00:25:46.000000000 +0900 +++ tiarra-20090206/ChangeLog.svn 2009-02-09 22:30:19.000000000 +0900 @@ -1,4 +1,944 @@ ------------------------------------------------------------------------ +r29652 | topia | 2009-02-06 22:55:13 +0900 (Fri, 06 Feb 2009) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/Configuration.pm + +fix nick-fix-mode default value handling. + +------------------------------------------------------------------------ +r29651 | topia | 2009-02-06 22:44:53 +0900 (Fri, 06 Feb 2009) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/User/Nick/Detached.pm + +use BulletinBoard to work with single-server-mode. + +------------------------------------------------------------------------ +r29425 | drry | 2009-02-02 15:17:32 +0900 (Mon, 02 Feb 2009) | 4 lines +Changed paths: + M /lang/perl/tiarra/trunk/ChangeLog + M /lang/perl/tiarra/trunk/doc/module/UNCLASSIFIED.html + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle.pm + M /lang/perl/tiarra/trunk/module/Skelton.pm + + * added the `svn:mime-type` property. + * fixed typos. + * et cetera. + +------------------------------------------------------------------------ +r29317 | topia | 2009-01-31 01:02:07 +0900 (Sat, 31 Jan 2009) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/Tiarra/Encoding.pm + +cache encoding driver load failure result. + +------------------------------------------------------------------------ +r28633 | takano32 | 2009-01-19 04:49:53 +0900 (Mon, 19 Jan 2009) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/Im.pm + +Im.pm: UTF-8 BOM => UTF-8N +------------------------------------------------------------------------ +r28632 | takano32 | 2009-01-19 03:59:58 +0900 (Mon, 19 Jan 2009) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/Im.pm + +Im.pm: UTF-8N => UTF-8 BOM +------------------------------------------------------------------------ +r28573 | topia | 2009-01-18 03:01:46 +0900 (Sun, 18 Jan 2009) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/IrcIO/Client.pm + +remove unnecessary warning. + +------------------------------------------------------------------------ +r28572 | topia | 2009-01-18 03:00:30 +0900 (Sun, 18 Jan 2009) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/tiarra + +SIGPIPE must be ignored. + +------------------------------------------------------------------------ +r24729 | hio | 2008-11-24 16:01:17 +0900 (Mon, 24 Nov 2008) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/doc/module/Auto.html + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/Mixi.pm + +fetchtitle.mixi, ログインエラーの検出. エラーメッセージを日本語に&設定で変えれるように. +------------------------------------------------------------------------ +r24519 | topia | 2008-11-21 00:49:39 +0900 (Fri, 21 Nov 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/Outputz.pm + +fix config reloading issue. + +------------------------------------------------------------------------ +r24357 | topia | 2008-11-20 00:22:57 +0900 (Thu, 20 Nov 2008) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/doc/module/Auto.html + +update documentation. +------------------------------------------------------------------------ +r24356 | topia | 2008-11-20 00:22:08 +0900 (Thu, 20 Nov 2008) | 4 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/Outputz.pm + +explain command configuration. +small style fix. +change sample 'channel'. + +------------------------------------------------------------------------ +r24347 | topia | 2008-11-19 23:45:12 +0900 (Wed, 19 Nov 2008) | 3 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/Configuration/Preprocessor.pm + +added support to @elsifdef/@elsifndef. +fix drop condition of nested @if. + +------------------------------------------------------------------------ +r24337 | topia | 2008-11-19 22:31:45 +0900 (Wed, 19 Nov 2008) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/doc/module/Auto.html + M /lang/perl/tiarra/trunk/doc/module-toc.html + M /lang/perl/tiarra/trunk/sample.conf + +commit documentation. +------------------------------------------------------------------------ +r24336 | topia | 2008-11-19 22:31:28 +0900 (Wed, 19 Nov 2008) | 1 line +Changed paths: + A /lang/perl/tiarra/trunk/module/Auto/Outputz.pm + +initial version of Auto::Outputz. +------------------------------------------------------------------------ +r22785 | hio | 2008-11-05 22:45:32 +0900 (Wed, 05 Nov 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/ExtractHeading.pm + +zakzakの見出し位置が変わっていたのに対応. + +------------------------------------------------------------------------ +r20015 | hio | 2008-09-27 11:45:04 +0900 (Sat, 27 Sep 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/ExtractHeading.pm + +血液型ゲノムの見出し抽出を追加. + +------------------------------------------------------------------------ +r20013 | hio | 2008-09-27 11:24:28 +0900 (Sat, 27 Sep 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/TouhouReplay.pm + +FetchTitle/TouhouReply, 取得サイズの制限を緩和. + +------------------------------------------------------------------------ +r20011 | hio | 2008-09-27 11:23:06 +0900 (Sat, 27 Sep 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/Mixi.pm + +mixiログイン微調整. + +------------------------------------------------------------------------ +r20009 | hio | 2008-09-27 11:20:10 +0900 (Sat, 27 Sep 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/doc/module/System.html + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +ドキュメント調整. + +------------------------------------------------------------------------ +r17280 | hio | 2008-08-09 12:43:15 +0900 (Sat, 09 Aug 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/ExtractHeading.pm + +FetchTitle, スベシ診断とゴディバの抽出. + +------------------------------------------------------------------------ +r16813 | hio | 2008-07-29 23:28:13 +0900 (Tue, 29 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/ExtractHeading.pm + +FetchTitle, 見出し抽出追加(nintendo) + +------------------------------------------------------------------------ +r16779 | hio | 2008-07-28 22:53:40 +0900 (Mon, 28 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/ExtractHeading.pm + +FetchTitle, 見出し抽出追加. + +------------------------------------------------------------------------ +r16714 | hio | 2008-07-27 23:52:47 +0900 (Sun, 27 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/ExtractHeading.pm + +FetchTitle, 見出し抽出の追加. + +------------------------------------------------------------------------ +r16148 | drry | 2008-07-24 01:29:52 +0900 (Thu, 24 Jul 2008) | 3 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle.pm + + * 正規表現を修正しました。 + * HTTP を優先するように変更しました。 + +------------------------------------------------------------------------ +r15771 | drry | 2008-07-14 08:55:21 +0900 (Mon, 14 Jul 2008) | 4 lines +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/doc/module/CTCP.html + M /lang/perl/tiarra/trunk/doc/module/Channel.html + M /lang/perl/tiarra/trunk/doc-src/README + M /lang/perl/tiarra/trunk/doc-src/conf-main.tdoc + M /lang/perl/tiarra/trunk/doc-src/sample.conf.in + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle.pm + M /lang/perl/tiarra/trunk/module/CTCP/DCC/RewriteAddress.pm + M /lang/perl/tiarra/trunk/module/Channel/Ignore.pm + M /lang/perl/tiarra/trunk/sample.conf + + * Auto::FetchTitle: fixed regexps. + * whitespace changes. + * et cetera. + +------------------------------------------------------------------------ +r15736 | hio | 2008-07-13 15:15:20 +0900 (Sun, 13 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle.pm + +FetchTitle, コメント内は無視するように. + +------------------------------------------------------------------------ +r15720 | hio | 2008-07-13 00:56:44 +0900 (Sun, 13 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +WebClient, バージョン番号更新. + +------------------------------------------------------------------------ +r15715 | hio | 2008-07-13 00:38:40 +0900 (Sun, 13 Jul 2008) | 3 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +WebClient, ログ表示の微調整(リンクを時刻に埋め込み). +DEBUGオプションいれたままだったのを解除. + +------------------------------------------------------------------------ +r15713 | hio | 2008-07-13 00:27:46 +0900 (Sun, 13 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +WebClient, 携帯ブラウザの対応. + +------------------------------------------------------------------------ +r15706 | hio | 2008-07-12 21:35:54 +0900 (Sat, 12 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/doc/module/System.html + M /lang/perl/tiarra/trunk/mini.conf + +ドキュメント更新. + +------------------------------------------------------------------------ +r15705 | hio | 2008-07-12 21:33:43 +0900 (Sat, 12 Jul 2008) | 3 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle.pm + +Auto::FetchTitle, ID3tag対応追加, icecast対応が外れていたので修正. + + +------------------------------------------------------------------------ +r15704 | hio | 2008-07-12 21:31:42 +0900 (Sat, 12 Jul 2008) | 2 lines +Changed paths: + A /lang/perl/tiarra/trunk/module/Tools/ID3Tag.pm + +Tools::ID3Tag, MP3のID3タグの簡易抽出. + +------------------------------------------------------------------------ +r15703 | hio | 2008-07-12 21:29:11 +0900 (Sat, 12 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/ExtractHeading.pm + +FetchTitle(ExtractHeading), 見出し抽出追加. + +------------------------------------------------------------------------ +r15702 | hio | 2008-07-12 21:26:42 +0900 (Sat, 12 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +初期設定調整. + +------------------------------------------------------------------------ +r15585 | topia | 2008-07-10 02:01:45 +0900 (Thu, 10 Jul 2008) | 3 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Tools/HTTPClient/SSL.pm + +メッセージのチェックから!をはずしてみた。 +これで動くといいなあ。 + +------------------------------------------------------------------------ +r15581 | hio | 2008-07-10 00:24:33 +0900 (Thu, 10 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +WebClient, /style/style.css は認証通ってればみれるように. + +------------------------------------------------------------------------ +r15575 | hio | 2008-07-09 22:01:32 +0900 (Wed, 09 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +WebClient, 認証情報のログが減っていたので補完. + +------------------------------------------------------------------------ +r15519 | hio | 2008-07-09 00:49:47 +0900 (Wed, 09 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +WebClient, method="post" は対応できなかったりするぽい. + +------------------------------------------------------------------------ +r15518 | hio | 2008-07-09 00:48:44 +0900 (Wed, 09 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/ExtractHeading.pm + +FetchTitle, 見出し取得対応追加. + +------------------------------------------------------------------------ +r15324 | hio | 2008-07-07 00:58:55 +0900 (Mon, 07 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +sidにランダムいれわすれてた. + +------------------------------------------------------------------------ +r15318 | hio | 2008-07-07 00:34:51 +0900 (Mon, 07 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Channel/Mode/Oper/Grant.pm + +Channel/Mode/Oper/Grant, なんかうごかないことがあるぽい + +------------------------------------------------------------------------ +r15317 | hio | 2008-07-07 00:34:12 +0900 (Mon, 07 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle.pm + +FetchTitle, タイトル取得の微調整. + +------------------------------------------------------------------------ +r15316 | hio | 2008-07-07 00:33:41 +0900 (Mon, 07 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/ExtractHeading.pm + +FetchTitle, タイトル取得箇所こまごま調整 + +------------------------------------------------------------------------ +r15315 | hio | 2008-07-07 00:33:07 +0900 (Mon, 07 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/Mixi.pm + +取得サイズ調整. + +------------------------------------------------------------------------ +r15314 | hio | 2008-07-07 00:32:46 +0900 (Mon, 07 Jul 2008) | 2 lines +Changed paths: + A /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/2ch.pm + +FetchTitle, 2chをdatから読むプラグイン. + +------------------------------------------------------------------------ +r15313 | hio | 2008-07-07 00:31:48 +0900 (Mon, 07 Jul 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +WebClient, shared時の名前を固定できるように. + +------------------------------------------------------------------------ +r14529 | topia | 2008-06-24 20:57:19 +0900 (Tue, 24 Jun 2008) | 3 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/Im.pm + +fix mask checking bug. +* mask の実装ミスでデフォルト値が壊れていたのを修正。 + +------------------------------------------------------------------------ +r14151 | topia | 2008-06-17 00:47:01 +0900 (Tue, 17 Jun 2008) | 3 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/Configuration/Block.pm + M /lang/perl/tiarra/trunk/main/Configuration/Parser.pm + A /lang/perl/tiarra/trunk/t/010_main_Configuration + A /lang/perl/tiarra/trunk/t/010_main_Configuration/001_basic_test.t + A /lang/perl/tiarra/trunk/t/010_main_Configuration/testdata1.conf + +support multiple nested block. +* add tiny test for Configuration::Parser. +* support multiple nested block. +------------------------------------------------------------------------ +r14132 | topia | 2008-06-16 22:33:59 +0900 (Mon, 16 Jun 2008) | 3 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/Tiarra/IRC/Message.pm + M /lang/perl/tiarra/trunk/main/Tiarra/IRC/Prefix.pm + +documentation fixes. +* add POD for Tiarra::IRC::Prefix. +* change AUTHOR section in Tiarra::IRC::Message. +------------------------------------------------------------------------ +r14049 | hio | 2008-06-15 21:45:29 +0900 (Sun, 15 Jun 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +WebClient, 接続ログに受理した認証の情報を表示するように. + +------------------------------------------------------------------------ +r13832 | topia | 2008-06-13 23:04:52 +0900 (Fri, 13 Jun 2008) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/main/IrcIO/Server.pm + +* change server_host/server_port disposal timing. +------------------------------------------------------------------------ +r13831 | topia | 2008-06-13 23:01:33 +0900 (Fri, 13 Jun 2008) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk + M /lang/perl/tiarra/trunk/doc/module-reload.txt + M /lang/perl/tiarra/trunk/main/ExternalSocket.pm + M /lang/perl/tiarra/trunk/main/Hook.pm + M /lang/perl/tiarra/trunk/main/Module.pm + M /lang/perl/tiarra/trunk/main/ModuleManager.pm + M /lang/perl/tiarra/trunk/main/ReloadTrigger.pm + M /lang/perl/tiarra/trunk/main/RunLoop.pm + M /lang/perl/tiarra/trunk/main/Tiarra/Socket.pm + M /lang/perl/tiarra/trunk/main/Tiarra/WrapMainLoop.pm + M /lang/perl/tiarra/trunk/main/Timer.pm + M /lang/perl/tiarra/trunk/module/Auto/Im.pm + M /lang/perl/tiarra/trunk/module/Skelton.pm + +* landing module-reload on trunk. +------------------------------------------------------------------------ +r13830 | topia | 2008-06-13 22:45:55 +0900 (Fri, 13 Jun 2008) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/main/BulletinBoard.pm + +* support value deletion. +------------------------------------------------------------------------ +r13829 | topia | 2008-06-13 22:43:03 +0900 (Fri, 13 Jun 2008) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/main/Tiarra/OptionalModules.pm + +* fix code style. +------------------------------------------------------------------------ +r13084 | hio | 2008-06-02 22:56:48 +0900 (Mon, 02 Jun 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/Multicast.pm + +network-separatorは後ろから取るように. + +------------------------------------------------------------------------ +r13025 | hio | 2008-06-01 23:50:38 +0900 (Sun, 01 Jun 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +System::WebClient, 未読管理の調整. + +------------------------------------------------------------------------ +r12996 | hio | 2008-06-01 21:16:13 +0900 (Sun, 01 Jun 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/mini.conf + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/ExtractHeading.pm + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +System::WebClient, 発言時は最新のログに飛ぶように. + +------------------------------------------------------------------------ +r12926 | hio | 2008-05-31 23:42:57 +0900 (Sat, 31 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/Tiarra/Resolver.pm + +perl -c の時はスレッド無効に. + +------------------------------------------------------------------------ +r12925 | hio | 2008-05-31 23:38:21 +0900 (Sat, 31 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +System::WebClient, CRYPTとSMD5を追加. + +------------------------------------------------------------------------ +r12856 | hio | 2008-05-30 23:37:23 +0900 (Fri, 30 May 2008) | 3 lines +Changed paths: + M /lang/perl/tiarra/trunk/doc/module-reload.txt + A /lang/perl/tiarra/trunk/module/Tools/Reload.pm + +reload時に値を引き継ぐサンプルとして Tools::Reload をかいてみた. +動作未確認. + +------------------------------------------------------------------------ +r12854 | hio | 2008-05-30 23:23:17 +0900 (Fri, 30 May 2008) | 2 lines +Changed paths: + A /lang/perl/tiarra/trunk/doc/auto-reply-comparison.txt + +Auto系の違いの説明を書いてみた. + +------------------------------------------------------------------------ +r12851 | hio | 2008-05-30 22:50:00 +0900 (Fri, 30 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/doc/module/Auto.html + M /lang/perl/tiarra/trunk/module/Auto/Reply.pm + +Auto::Reply, 間違えてたところもう一箇所修正. + +------------------------------------------------------------------------ +r12850 | hio | 2008-05-30 22:46:17 +0900 (Fri, 30 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/doc/module/Auto.html + M /lang/perl/tiarra/trunk/module/Auto/Reply.pm + +Auto::Reply 一部間違ってたのを修正と加筆. + +------------------------------------------------------------------------ +r12848 | hio | 2008-05-30 22:28:01 +0900 (Fri, 30 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/doc-src/conf-main.tdoc + M /lang/perl/tiarra/trunk/sample.conf + +なんかドキュメントにutf8修正の漏れ? + +------------------------------------------------------------------------ +r12847 | hio | 2008-05-30 22:15:27 +0900 (Fri, 30 May 2008) | 3 lines +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/doc/module/Auto.html + M /lang/perl/tiarra/trunk/module/Auto/Reply.pm + +Auto::Reply, 同じ応答マスクが複数個書かれていたときの処理を実装. +あとドキュメント加筆. + +------------------------------------------------------------------------ +r12579 | hio | 2008-05-28 01:39:46 +0900 (Wed, 28 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +警告抑制. + +------------------------------------------------------------------------ +r12578 | hio | 2008-05-28 01:13:30 +0900 (Wed, 28 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +debug時に認証情報を標準出力に出すように. + +------------------------------------------------------------------------ +r12486 | topia | 2008-05-27 01:55:33 +0900 (Tue, 27 May 2008) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/tiarra + +5.8 より前のことはわすれました。 +------------------------------------------------------------------------ +r12479 | drry | 2008-05-27 00:42:00 +0900 (Tue, 27 May 2008) | 3 lines +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle.pm + M /lang/perl/tiarra/trunk/module/Tools/HTTPClient.pm + + * documentation. + * regexp. + +------------------------------------------------------------------------ +r12473 | hio | 2008-05-26 23:50:36 +0900 (Mon, 26 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/doc/module/Client.html + M /lang/perl/tiarra/trunk/doc/module/System.html + M /lang/perl/tiarra/trunk/doc/module-toc.html + A /lang/perl/tiarra/trunk/module/Client/List.pm (from /lang/perl/tiarra/trunk/module/System/ClientList.pm:12470) + D /lang/perl/tiarra/trunk/module/System/ClientList.pm + +System::ClientList から Client::List にリネーム. + +------------------------------------------------------------------------ +r12470 | hio | 2008-05-26 23:44:00 +0900 (Mon, 26 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/doc/module/System.html + M /lang/perl/tiarra/trunk/doc/module-toc.html + A /lang/perl/tiarra/trunk/module/System/ClientList.pm + +クライアント一覧をリストアップする System::ClientList モジュール作成. + +------------------------------------------------------------------------ +r12340 | topia | 2008-05-25 19:40:16 +0900 (Sun, 25 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/doc/macro.txt + A /lang/perl/tiarra/trunk/t + A /lang/perl/tiarra/trunk/t/010_module_Tools_HashTools_macro.t + +* add macro EBNF. +* add macro expansion test. +------------------------------------------------------------------------ +r12337 | topia | 2008-05-25 19:04:44 +0900 (Sun, 25 May 2008) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/tiarra + +* tiarra: cleanup correctly. +------------------------------------------------------------------------ +r12336 | topia | 2008-05-25 19:03:57 +0900 (Sun, 25 May 2008) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/HACKING + M /lang/perl/tiarra/trunk/main/RunLoop.pm + M /lang/perl/tiarra/trunk/main/TiarraDoc.pm + M /lang/perl/tiarra/trunk/module/Auto/Im.pm + M /lang/perl/tiarra/trunk/module/Auto/MesMail.pm + M /lang/perl/tiarra/trunk/tiarra + +* revert recently wrong commit. +------------------------------------------------------------------------ +r12335 | topia | 2008-05-25 19:02:56 +0900 (Sun, 25 May 2008) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/HACKING + M /lang/perl/tiarra/trunk/main/RunLoop.pm + M /lang/perl/tiarra/trunk/main/TiarraDoc.pm + M /lang/perl/tiarra/trunk/module/Auto/Im.pm + M /lang/perl/tiarra/trunk/module/Auto/MesMail.pm + M /lang/perl/tiarra/trunk/tiarra + +* tiarra: cleanup correctly. +------------------------------------------------------------------------ +r12325 | hio | 2008-05-25 16:43:30 +0900 (Sun, 25 May 2008) | 4 lines +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/doc/module/System.html + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +WebClient, 未読管理をひとまず実装. +SoftBank,AUの端末認証を実装. +別のTiarraさんからのログを見やすく. + +------------------------------------------------------------------------ +r12299 | hio | 2008-05-25 00:40:03 +0900 (Sun, 25 May 2008) | 2 lines +Changed paths: + A /lang/perl/tiarra/trunk/doc/module-reload.txt + +Tiarra モジュールのリロードに関する改善案メモ. + +------------------------------------------------------------------------ +r12298 | hio | 2008-05-25 00:38:04 +0900 (Sun, 25 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/doc/module/Auto.html + +ドキュメント系更新. + +------------------------------------------------------------------------ +r12297 | hio | 2008-05-25 00:37:28 +0900 (Sun, 25 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle.pm + +FetchTitle, 設定変更時のリフレッシュ周りを調整. + +------------------------------------------------------------------------ +r12295 | hio | 2008-05-25 00:27:51 +0900 (Sun, 25 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/doc/module/System.html + M /lang/perl/tiarra/trunk/module/System/LivePatch.pm + +LivePatch, Configuration::Block#equals のバグ修正を反映. + +------------------------------------------------------------------------ +r12284 | hio | 2008-05-24 22:47:34 +0900 (Sat, 24 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/ExtractHeading.pm + +release.nikkei.co.jpを追加. + +------------------------------------------------------------------------ +r12214 | topia | 2008-05-22 20:34:11 +0900 (Thu, 22 May 2008) | 3 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Log/Channel.pm + +* use Tools::HashTools (instead of Auto::AliasDB). +* kill once warnings for S_* and C_* aliases. + +------------------------------------------------------------------------ +r12198 | topia | 2008-05-22 15:38:54 +0900 (Thu, 22 May 2008) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/module/Log/Channel.pm + +* Log::Channel: require Auto::AliasDB. +------------------------------------------------------------------------ +r12141 | hio | 2008-05-21 22:28:32 +0900 (Wed, 21 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/System/WebClient.pm + +!なチャンネルの調整. + +------------------------------------------------------------------------ +r12095 | hio | 2008-05-21 09:32:31 +0900 (Wed, 21 May 2008) | 2 lines +Changed paths: + A /lang/perl/tiarra/trunk/doc/macro.txt + +マクロ展開関係のメモ. + +------------------------------------------------------------------------ +r12073 | hio | 2008-05-20 23:12:59 +0900 (Tue, 20 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/ExtractHeading.pm + +警告がでてたのを修正. + +------------------------------------------------------------------------ +r12072 | hio | 2008-05-20 22:58:17 +0900 (Tue, 20 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/Mixi.pm + +コメント加筆. + +------------------------------------------------------------------------ +r12068 | hio | 2008-05-20 22:13:47 +0900 (Tue, 20 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/doc/module/Log.html + M /lang/perl/tiarra/trunk/module/Log/Channel.pm + M /lang/perl/tiarra/trunk/sample.conf + +Log::Channel, 自動でチャンネル別にログを保存できるように. + +------------------------------------------------------------------------ +r11872 | topia | 2008-05-18 14:25:48 +0900 (Sun, 18 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/Tiarra/IRC/Message.pm + +* Tiarra::IRC::Message: parse 時の max param exceeded 警告がちゃんと出るようにした。 + +------------------------------------------------------------------------ +r11869 | topia | 2008-05-18 14:01:08 +0900 (Sun, 18 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/CTCP/DCC/RewriteAddress.pm + +* warn 関係を printmsg ではなく runloop->notify_warn に流すように変更。 + +------------------------------------------------------------------------ +r11868 | topia | 2008-05-18 14:00:32 +0900 (Sun, 18 May 2008) | 3 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/Configuration/Block.pm + +* Configuration::Block->equals: + 設定にブロックを使ったモジュール同士の評価に失敗することがあったのを修正 + +------------------------------------------------------------------------ +r11732 | hio | 2008-05-17 14:08:14 +0900 (Sat, 17 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle.pm + +fix on extracting urls. + +------------------------------------------------------------------------ +r11731 | hio | 2008-05-17 14:07:28 +0900 (Sat, 17 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/ExtractHeading.pm + +add gourmet.oricon.co.jp. + +------------------------------------------------------------------------ +r11595 | topia | 2008-05-14 18:53:05 +0900 (Wed, 14 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/Im.pm + +* add raw_channel alias key. + +------------------------------------------------------------------------ +r11540 | topia | 2008-05-14 00:02:23 +0900 (Wed, 14 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/IrcIO/Server.pm + + * set server_host on connection accepted. + +------------------------------------------------------------------------ +r11480 | topia | 2008-05-13 02:42:42 +0900 (Tue, 13 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/IrcIO/Server.pm + +* サーバ接続時には progress がないとわかりにくいので表示するように。 + +------------------------------------------------------------------------ +r11479 | topia | 2008-05-13 02:41:48 +0900 (Tue, 13 May 2008) | 5 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/Tiarra/Socket/Connect.pm + +* try_count がちゃんと試行回数を返すようにした。 + (前までは再試行の回数を返していた。) +* progress コールバックの追加。 +* メソッド名を _notify_skip, _notify_progress にした。 + +------------------------------------------------------------------------ +r11477 | topia | 2008-05-13 02:29:51 +0900 (Tue, 13 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/LinedINETSocket.pm + M /lang/perl/tiarra/trunk/main/Tiarra/Socket/Buffered.pm + +* パラメータの順番が間違ってたので修正。 + (ただし外部仕様には影響がなかった。) +------------------------------------------------------------------------ +r11475 | topia | 2008-05-13 01:39:36 +0900 (Tue, 13 May 2008) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/doc-src/conf-main.tdoc + M /lang/perl/tiarra/trunk/main/Configuration.pm + M /lang/perl/tiarra/trunk/main/IrcIO/Server.pm + +* IrcIO::Server: support multiple host/port configuration. +------------------------------------------------------------------------ +r11470 | topia | 2008-05-13 00:06:02 +0900 (Tue, 13 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/System/NotifyIcon/Win32.pm + +* tooltip の変更に Win32::GUI の正式 API を使うように書き換えた。 + +------------------------------------------------------------------------ +r11418 | topia | 2008-05-12 02:31:59 +0900 (Mon, 12 May 2008) | 6 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/IrcIO/Server.pm + +* Tiarra::Socket::Connector をまじめに使うようにした。 +* 内部的に複数ホスト/複数ポートに接続を試行できるようになりましたが、 + 設定をどう記述させようか悩んでいるので設定の方はまだです。 +* 多分大丈夫だと思いますが、結構コードを書き換えたので十分に + テストしてから入れ換えてください。 + +------------------------------------------------------------------------ +r11417 | topia | 2008-05-12 02:16:49 +0900 (Mon, 12 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/Tiarra/Socket/Connect.pm + +* メッセージをすこし変更。 + +------------------------------------------------------------------------ +r11416 | topia | 2008-05-12 01:59:31 +0900 (Mon, 12 May 2008) | 4 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/Tiarra/Socket/Connect.pm + +* 複数ポートを指定できるように。 +* _connect_stage を _connect_after_resolve にリネーム。 +* ソケットエラー処理を一部変更。 + +------------------------------------------------------------------------ +r11415 | topia | 2008-05-12 01:38:06 +0900 (Mon, 12 May 2008) | 4 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/Tiarra/Socket/Connect.pm + +* name resolution error に host をつけるようにした。 +* $sock not defined を warn & try next にした。 +* unix domain socket で before_connect が呼ばれるようにした。 + +------------------------------------------------------------------------ +r11414 | topia | 2008-05-12 01:36:31 +0900 (Mon, 12 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/IrcIO/Server.pm + +* 全然使ってないインスタンス変数を捨てる。 + +------------------------------------------------------------------------ +r11408 | topia | 2008-05-11 23:01:10 +0900 (Sun, 11 May 2008) | 3 lines +Changed paths: + M /lang/perl/tiarra/trunk/tiarra + +* --debug --show-env の時にはモジュールロードの + エラーメッセージが出るように。 + +------------------------------------------------------------------------ +r11407 | topia | 2008-05-11 23:00:20 +0900 (Sun, 11 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/Tiarra/Resolver.pm + +* Thread::Queue のロード順を最後にした。気分の問題。 + +------------------------------------------------------------------------ +r11406 | topia | 2008-05-11 22:59:27 +0900 (Sun, 11 May 2008) | 8 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/Tiarra/Socket/Connect.pm + +* 動作フローの見直し。主に error から warn and try next への変更。 +* hooks->{before_connect} を追加(したけど未テスト)。 + * 特定のaddr/port/type をみて skip したり、書き換えたりできるはず。 +* bind_addr をコネクション毎にオーバーライドできるように(同じく未テスト)。 +* callback の optional genre として skip を追加した。 +* ドキュメントをちょっと書き直した。 + * 重要なのは callback の 'error' genre は接続の試行が終わるというところ。 + +------------------------------------------------------------------------ +r11394 | topia | 2008-05-11 15:57:19 +0900 (Sun, 11 May 2008) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/doc/module/Auto.html + M /lang/perl/tiarra/trunk/doc/module/CTCP.html + M /lang/perl/tiarra/trunk/doc/module/Channel.html + M /lang/perl/tiarra/trunk/doc/module/Client.html + M /lang/perl/tiarra/trunk/doc/module/Debug.html + M /lang/perl/tiarra/trunk/doc/module/Log.html + M /lang/perl/tiarra/trunk/doc/module/System.html + M /lang/perl/tiarra/trunk/doc/module/UNCLASSIFIED.html + M /lang/perl/tiarra/trunk/doc/module/User.html + M /lang/perl/tiarra/trunk/doc/module-toc.html + +* regen documentation.* delete unnecessary property. +------------------------------------------------------------------------ +r11393 | topia | 2008-05-11 15:56:12 +0900 (Sun, 11 May 2008) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/doc-src/contents.html + M /lang/perl/tiarra/trunk/doc-src/module-toc.html + +* XML 宣言の文字コードの存在を忘れてた。これも UTF-8 に。 +------------------------------------------------------------------------ +r11392 | topia | 2008-05-11 15:46:50 +0900 (Sun, 11 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/main/Tiarra/OptionalModules.pm + +* add note of required modules. + +------------------------------------------------------------------------ +r11381 | topia | 2008-05-11 05:54:37 +0900 (Sun, 11 May 2008) | 1 line +Changed paths: + M /lang/perl/tiarra/trunk/bundle/Unicode/Japanese.pm + +* upgrade Unicode::Japanese to 0.45. +------------------------------------------------------------------------ +r11372 | hio | 2008-05-11 01:10:37 +0900 (Sun, 11 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/all.conf + M /lang/perl/tiarra/trunk/doc/module/Auto.html + M /lang/perl/tiarra/trunk/doc/module-toc.html + +Auto::FetchTitle::Plugin::Mixi, ExtractHeading の tiarra-doc をマージ. + +------------------------------------------------------------------------ +r11371 | hio | 2008-05-11 00:58:25 +0900 (Sun, 11 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/ExtractHeading.pm + +Auto::FetchTitle::Plugin::ExtractHeading, tiarra.conf でも抽出パターンを記述できるように. + +------------------------------------------------------------------------ +r11370 | hio | 2008-05-11 00:57:15 +0900 (Sun, 11 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/Mixi.pm + +Auto::FetchTitle::Plugin::Mixi, tiarra-doc 追加. + +------------------------------------------------------------------------ +r11369 | hio | 2008-05-11 00:56:26 +0900 (Sun, 11 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin.pm + +Auto::FetchTitle::Plugin, ユーティリティメソッドを追加. + +------------------------------------------------------------------------ +r11368 | hio | 2008-05-11 00:55:38 +0900 (Sun, 11 May 2008) | 2 lines +Changed paths: + M /lang/perl/tiarra/trunk/module/Auto/FetchTitle.pm + +ワイルドカードクッキーの対応. + +------------------------------------------------------------------------ r11365 | topia | 2008-05-10 23:58:28 +0900 (Sat, 10 May 2008) | 1 line Changed paths: M /lang/perl/tiarra/trunk diff -urN tiarra-20080510/all.conf tiarra-20090206/all.conf --- tiarra-20080510/all.conf 2008-05-11 00:25:27.000000000 +0900 +++ tiarra-20090206/all.conf 2009-02-09 22:30:12.000000000 +0900 @@ -52,7 +52,7 @@ # crypt は ./tiarra --make-password で行えます。 tiarra-password: xl7cflIcH9AwE - # 外部プログラムからtiarraをコントロールする為のUNIXドメインソケットの名前。 + # 外部プログラムからTiarraをコントロールする為のUNIXドメインソケットの名前。 # 例えば"foo"を指定した場合、ソケット/tmp/tiarra-control/fooが作られる。 # 省略された場合はこの機能を無効とする。 # また、非UNIX環境ではそもそもUNIXドメインソケットが利用可能でないため、 @@ -69,7 +69,7 @@ client-in-encoding: jis client-out-encoding: jis - # Tiarraは標準出力に様々なメッセージを出力するが、その文字コードを指定する。省略時にはeucとなる。 + # Tiarraは標準出力に様々なメッセージを出力するが、その文字コードを指定する。省略時にはutf8となる。 # ただしtiarra.confのパースが完了するまでは文字コードの変換は行なわれない(つまりこの設定が有効にならない)ことに注意して下さい。 stdout-encoding: utf8 @@ -105,7 +105,7 @@ #ipv4-bind-addr: 0.0.0.0 #ipv6-bind-addr: ::0 - # tiarra が、 001 や 002 や、 recent log を送信するときなどに使う prefix + # Tiarra が、 001 や 002 や、 recent log を送信するときなどに使う prefix # を指定します。 hostname や fqdn っぽいものを指定すると良いかもしれません。 # デフォルトは tiarra です。普通変える必要はありません。 #sysmsg-prefix: tiarra @@ -224,9 +224,15 @@ # そのアドレス、ポート、(必要なら)パスワードを定義します。 # ----------------------------------------------------------------------------- ircnet { - # サーバーのホストとポート。省略不可。 - host: irc.nara.wide.ad.jp - port: 6663 + # サーバーのホストとポートを指定。複数行可。(host/port が指定されていない場合は)省略不可。 + # 同じサーバの複数のポート指定は順番に試すので、大量に書かない方がよい。 + server: irc.nara.wide.ad.jp 6662 6663 + server: irc.fujisawa.wide.ad.jp 6661 6664 + + # サーバーのホストとポート。(server が指定されていない場合は)省略不可。 + # server を指定した場合は server が優先されます。 + #host: irc.nara.wide.ad.jp + #port: 6663 # general/userで設定したユーザ名を使わずに、各ネットワークで独自のユーザ名を使用する事も可能。 # 省略されたら当然、general/userで設定したものが使われる。 @@ -479,12 +485,73 @@ #debug-dumpfile: fetchtitle.log # NOTE: - # 利用するにはcodereposから + # 利用するにはCodeReposから # module/Tools/HTTPClient.pm rev.8220 # main/Tiarra/Socket/Buffered.pm rev.8219 # 以降が必要です. } +- Auto::FetchTitle::Plugin::ExtractHeading { + # 本文から見出しを抽出するFetchTitleプラグイン. + + # Auto::FetchTitle { ... } での設定. + # + Auto::FetchTitle { + # plugins { + # ExtractHeading { + # extra: name1 name2 ... + # extra-name1 { + # url: http://www.example.com/* + # recv_limit: 10*1024 + # extract: re:
(.*?)
+ # } + # } + # } + # } +} + +- Auto::FetchTitle::Plugin::Mixi { + # Mixiにログインして見出し抽出出来るようにするFetchTitleプラグイン. + + # Auto::FetchTitle { ... } での設定. + # + # + Auto::FetchTitle { + # mask: #* &mixi http://* + # plugins { + # Mixi { + # mixi-user: xxx + # mixi-pass: yyy + # } + # } + # conf-mixi { + # filter-mixi { + # url: http://mixi.jp/* + # url: http://news.mixi.jp/* + # type: mixi + # timeout: 10 + # #閲覧可能なコミュニティの指定. + # #mixi-community: 0 + # #閲覧可能なユーザの指定. + # #指定したユーザには足跡踏んで見に行きます. + # #mixi-friend: 0 + # #閲覧可能にしていないページを表示したときのメッセージ. + # #要求されたページを #(url) で展開できます. + # #mixi-noperm-msg: not permitted #(url). + # } + # } + # } + # + # アカウント情報は plugins Mixi に記述. + # mixi-pass には {B}bbbb でBASE64エンコード値も可能. + # + # newsだけしか使わない場合でも, ログイン処理が必要なので + # mixi.jp 内のいくつかのURLはこのプラグインで処理する必要があります. + # url: http://news.mixi.jp/* + # url: http://mixi.jp/issue_ticket.pl?* + # url: http://mixi.jp/login.pl + # url: http://mixi.jp/check.pl?* + # (それぞれ, ニュースページ, ログイン処理, エラー検出, 途中経路になります.) +} + - Auto::Im { # 名前が呼ばれると、その発言をim.kayac.comに送信する @@ -502,6 +569,7 @@ # im.kayac.com に送るメッセージのフォーマットを指定します。 # デフォルト値: [tiarra][#(channel):#(nick.now)] #(text) + # #(channel) のかわりに #(raw_channel) を利用するとネットワーク名がつきません。 format: [tiarra][#(channel):#(nick.now)] #(text) # im.kayac.comで登録したユーザ名を入力します。 @@ -699,6 +767,28 @@ out: チャンネルに入っていない } +- Auto::Outputz { + # チャンネルの発言文字数を outputz に送信する + + # 復活の呪文。 + key: some secret + + # 送信対象にするコマンドの設定。 + # 省略された場合は PRIVMSG 。 + # パラメータ1が送信先、パラメータ2が本文でなければ動作しないので、 + # 動作するコマンドは PRIVMSG/NOTICE/TOPIC/PART 程度。 + #command: PRIVMSG + + # 各チャンネルのURIの設定。 + # 記述された順序で検索されるので、全てのチャンネルにマッチする"*"などは最後に書かなければならない。 + # フォーマットは次の通り。 + # channel: (<チャンネル名> / 'priv')@<ネットワーク名> + # #(channel) はチャンネル名に、 #(channel_short) はネットワークなしの + # チャンネル名に、 #(network) はネットワーク名にそれぞれ置き換えられる。 + # また、危険な文字は自動的にエスケープされる。 + channel: http://#(network).irc.example.com/#(channel_short) * +} + - Auto::Random { # 特定の発言に反応してランダムな発言をします。 @@ -765,58 +855,110 @@ # Auto::Aliasを有効にしていれば、エイリアス置換を行ないます。 # 使用するブロックの定義。 + # 省略すると std を使用. + # 複数個の blocks の指定も可能. blocks: std std { + # 1つの応答ブロックの定義. + # 一応全ての項目が省略可能ではあるけれど, + # 通常は最低限 file と file-encoding を使用する. + # IRCで応答の追加削除等を行いたいときにはそれに更に設定を追加する形. + # (IRC上で応答の追加削除は行うが保存はしない時に限ってfileを省略可能.) + + # 機能: + # - 通常応答(mask) + # - 登録数確認(count-query/mask) + # - 反応確認(request/modifier) + # - 反応追加(add/modifier) + # - 反応削除(remove/modifier) + # 通常応答以外は設定を省略することで機能を無効にできます。 + # データファイルと文字コードを指定します。 - # ファイルの中では一行に一つの"反応:メッセージ"を書いて下さい。 + # ファイルの中では一行に一つの"反応マスク:メッセージ"を書いて下さい。 file: reply.txt file-encoding: euc - # 反応チェックを行うキーワードを指定します。 - # 実際の指定方法は、「 <チェックしたい発言>」です。 - request: 反応チェック + # 1つの発言で複数の反応マスクにマッチする場合, + # どれにマッチするかは未定義です. + # ただ, どちらか1つにのみマッチします. + + # 同じ反応マスクに複数個のメッセージが記述してあった場合の処理. + # multivalue: random #==> ランダムに1つ選択. + # multivalue: all #==> 全て返す. + # multivalue: seq #==> 順番に1つずつ返す. + # 省略時及び認識できなかったときは random. + #multivalue: random + # 返す最大行数. + # multivalue: all の時のみ有効. + # (それ以外の時は1行しか返さない) + # デフォルトは 5 行まで. + #multivalue-limit: 5 - # request に反応するときのフォーマットを指定します。 - # #(key) がキーワード、 #(message) が発言に置換されます。 - reply-format: 「#(key)」という発言に「#(message)」と反応します。 + # 反応する人のマスク。 + # 通常応答と登録数の返答時にチェックされる。 + mask: * *!*@* + # plum: mask: *!*@* - # request に反応する最大個数を指定します。 - # あまり大きな値を指定すると、アタックが可能になったり、ログが流れて邪魔なので注意してください。 - max-reply: 5 + # マッチした1つの反応マスクが実際に発言に反応する確率を指定します。 + # 百分率です。省略された場合は100と見做されます。 + rate: 100 # メッセージの登録数を返答するキーワードを指定します。 + # 省略するとこの機能は無効になります。 + # 指定したときだけこの機能が有効になります。 + # mask で許可された人(通常応答を返す人)が使えます。 count-query: 反応登録数 # メッセージの登録数を返答するときの反応を指定します。 # formatで指定できるものと同じです。#(count)は登録数になります。 + # count-query を指定したときのみ必要。 count-format: 反応は#(count)件登録されています。 - # 反応する人のマスク。 - mask: * *!*@* - # plum: mask: *!*@* + # メッセージを追加するキーワードを指定します。 + # ここで指定したキーワードを発言すると、新しいメッセージを追加します。 + # 実際の追加方法は「 <追加するメッセージ>」です。 + # 省略するとこの機能は無効になります。 + # 指定したときだけこの機能が有効になります。 + # modifier で許可された人だけ使えます。 + #add: 反応追加 # 反応が追加されたときの反応を指定します。 # formatで指定できるものと同じです。#(message)は追加されたメッセージになります。 added-format: #(name|nick.now): #(key) に対する反応 #(message) を追加しました。 + # メッセージを削除するキーワードを指定します。 + # 実際の削除方法は「 <削除するキーワード>」です。 + # 省略するとこの機能は無効になります。 + # 指定したときだけこの機能が有効になります。 + # modifier で許可された人だけ使えます。 + #remove: 反応削除 + # メッセージが削除されたときの反応を指定します。 # formatで指定できるものと同じです。#(message)は削除されたメッセージになります。 removed-format: #(name|nick.now): #(key) #(message;に対する反応 %s|;) を #(count) 件削除しました。 - # 発言に反応する確率を指定します。百分率です。省略された場合は100と見做されます。 - rate: 100 + # 反応の確認を行うためのキーワードを指定します。 + # 通常応答と違って, multivalue-limit の制限を受けずに全てのマッチした応答を返します。 + # 実際の指定方法は、「 <チェックしたい発言>」です。 + # 省略するとこの機能は無効になります。 + # 指定したときだけこの機能が有効になります。 + # modifier で許可された人だけ使えます。 + request: 反応チェック - # メッセージを追加するキーワードを指定します。 - # ここで指定したキーワードを発言すると、新しいメッセージを追加します。 - # 実際の追加方法は「 <追加するメッセージ>」です。 - add: 反応追加 + # request に反応するときのフォーマットを指定します。 + # #(key) がキーワード、 #(message) が発言に置換されます。 + # request を指定したときのみ必要。 + reply-format: 「#(key)」という発言に「#(message)」と反応します。 - # メッセージを削除するキーワードを指定します。 - # 実際の削除方法は「 <削除するキーワード>」です。 - remove: 反応削除 + # request に反応する最大個数(反応マスクの数)を指定します。 + # (1つの反応マスクに対応するメッセージの数は制限されません。) + # あまり大きな値を指定すると、アタックが可能になったり、ログが流れて邪魔なので注意してください。 + # 通常の反応には関与しません。また、応答の行数ではありません。 + max-reply: 5 - # addとremoveを許可する人。省略された場合は「* *!*@*」と見做します。 + # 編集系コマンド, add とremove と request を許可する人。 + # 省略された場合は「* *!*@*」(全員許可)と見做します。 modifier: * *!*@* # 正規表現拡張を許可するか。省略された場合は禁止します。 @@ -865,7 +1007,7 @@ - CTCP::DCC::RewriteAddress { # クライアントが送信した CTCP DCC のアドレスを変換する。 - # CTCP DCC に指定されているアドレスを、 tiarra で取得したものに + # CTCP DCC に指定されているアドレスを、 Tiarra で取得したものに # 書き換えます。(EXPERIMENTAL) # # IPv4 のみサポートしています。 @@ -892,7 +1034,7 @@ client-socket { # クライアントソケットのリモートアドレスを取ります。 - # client [this address]<-> tiarra <-> server + # client [this address]<-> Tiarra <-> server } dns { @@ -912,16 +1054,16 @@ # リゾルバの選び方 # - # * tiarra を動作させているサーバとインターネットの間にルータ等があり、 + # * Tiarra を動作させているサーバとインターネットの間にルータ等があり、 # グローバルアドレスがない場合 # *-socket は役に立ちません。 http を利用してください。 # 適当な DDNS を持っていればdns も良いでしょう。 # - # * tiarra がレンタルサーバなどLAN上にないサーバで動作している場合 + # * Tiarra がレンタルサーバなどLAN上にないサーバで動作している場合 # server-socket, http は役に立ちません。 # client-socket がお勧めです。 # - # * tiarra がLAN上にあり、グローバルアドレスのついているホストで + # * Tiarra がLAN上にあり、グローバルアドレスのついているホストで # 動作している場合 # client-socket は役に立ちません。 # server-socket がお勧めです。 @@ -993,7 +1135,7 @@ # 注意点 # - この機能はまだ実装途中です。いろいろな不具合があるかもしれません。むしろきっとあります。 # - サーバがわとの通信に割り込みますのでログにもとられません。 - # - この機能を使っている tiarra より上流に multi-server-mode な tiarra を置かないでください。 + # - この機能を使っている Tiarra より上流に multi-server-mode な Tiarra を置かないでください。 # チャンネルの定義。 # また、privの場合は「priv@ネットワーク名」という文字列をチャンネル名の代わりとしてマッチングを行なう。 @@ -1140,6 +1282,21 @@ # Client::Guess から使用されます。) } +- Client::List { + # Clientの一覧を取得. + + # 接続しているクライアントを一覧. + # /clientlist を投げると, その時に接続しているクライアントの一覧を返す. + # 出力例: + # clientlist + # *** 1 client + # *** [1] 127.0.0.1:23695 + + + # 一覧を返すトリガーとするコマンド. + #command: clientlist +} + - Client::PatchworkMessage { # IRC メッセージにちょっと変更を加えて、クライアントのバグを抑制する @@ -1336,8 +1493,17 @@ # channel: others * # この例では、#IRC談話室@ircnetのログはIRCDanwasitu/%Y.%m.%d.txtに、 # それ以外(privも含む)のログはothers/%Y.%m.%d.txtに保存される。 + # #(channel) はチャンネル名に展開される。 + # (古いバージョンだと展開されずにそのままディレクトリ名になってしまいます。) channel: priv priv - channel: others * + channel: #(channel) * + #channel: others * + + # ファイル名のエンコーディング. + # 指定可能な値は, utf8, sjis, euc, jis, ascii. + # ascii は実際には utf8 と同等で8bit部分が全てquoted-printableされる. + # デフォルトはWindowsではsjis, それ以外では utf8. + #filename-encoding: utf8 } - Log::ChannelList { @@ -1461,7 +1627,7 @@ } - Skelton { - # Skelton for tiarra-module. + # Skeleton for tiarra-module. # モジュールの説明をこのあたりに書く. # 詳細はこのソースみれば分かると思われ. @@ -1492,6 +1658,8 @@ # 対応している箇所. # ModuleManager / reload_modules_if_modified / r3004 => r8809 + # LinedINETSocket / connect / r3004 => r8930 + # Configuration::Block / equals / r3004 => r11868 # /livepatch check で確認. # /livepatch apply で適用. @@ -1623,9 +1791,9 @@ # WebClient を起動させる場所の指定. bind-addr: 127.0.0.1 - bind-port: 8668 + bind-port: 8667 path: /irc - css: /style/irc-style.css + css: /irc/style/style.css # 上の設定をapacheでReverseProxyさせる場合, httpd.conf には次のように設定. # ProxyPass /irc/ http://localhost:8667/irc/ # ProxyPassReverse /irc/ http://localhost:8667/irc/ @@ -1635,6 +1803,7 @@ # ReverseProxy 利用時の追加設定. # 接続元が全部プロキシサーバになっちゃうのでその対応. + # ReverseProxy 使わず直接公開の場合は不要. #extract-forwarded-for: 127.0.0.1 # 利用する接続設定の一覧. @@ -1655,7 +1824,14 @@ # 接続元IPアドレスの制限. host: 127.0.0.1 # 認証設定. - auth: user pass + # auth: + # auth: :basic + # auth: :softbank <端末ID> + # auth: :softbank + # auth: :au + # 各値(等)には {MD5}xxxx や {B}xxx や {CRYPT}xxx を利用可能. + # そのままべた書きも出来るけれど. + auth: :basic user pass # 公開するチャンネルの指定. mask: #*@* mask: *@* @@ -1680,9 +1856,20 @@ # asc (旧->新) か desc (新->旧). # sort-order: asc - # 発言BOXで名前指定しなかったときのデフォルトの名前. - # mode: shared の時に使われる. - #name-default: (noname) + # name-default 設定は VERSION 0.05 で廃止されました. + # # 発言BOXで名前指定しなかったときのデフォルトの名前. + # # mode: shared の時に使われる. + # -name-default: (noname) + + # 外部にTiarraさんを使っているときに, そこのネットワークを切り出して表示する. + # exteact-network: + # ::= このTiarraさんから見たときの外部Tiarraさんのネットワーク名. + # (このtiarra.confで指定しているネットワーク名) + # ::= 外部Tiarraさんで使っているセパレータ. + # (こっちはこのtiarra.confのではないです) + # 省略すると @ と仮定. + #exteact-network: tiarra + #exteact-network: tiarra @ } - User::Away::Client { diff -urN tiarra-20080510/bundle/Unicode/Japanese.pm tiarra-20090206/bundle/Unicode/Japanese.pm --- tiarra-20080510/bundle/Unicode/Japanese.pm 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/bundle/Unicode/Japanese.pm 2009-02-09 22:30:09.000000000 +0900 @@ -2,16 +2,16 @@ # Unicode::Japanese # Unicode::Japanese::PurePerl # ----------------------------------------------------------------------------- -# $Id: Japanese.pm,v 1.24 2007/09/14 05:28:43 hio Exp $ +# $Id: Japanese_stub.pm 5278 2008-01-21 08:37:47Z hio $ # ----------------------------------------------------------------------------- package Unicode::Japanese::PurePerl; package Unicode::Japanese; use strict; use vars qw($VERSION $PurePerl $xs_loaderror); -$VERSION = '0.44'; +$VERSION = '0.45'; -# `use bytes' and `use Encode' if on perl-5.8.0 or later. +# `use bytes' and `use Encode' if we are on perl-5.8.0 or later. if( $] >= 5.008 ) { my $evalerr; @@ -100,7 +100,7 @@ local($SIG{__DIE__}) = 'DEFAULT'; Unicode::Japanese->bootstrap($VERSION); }; - #print STDERR "* * try done.\n"; + #print STDERR "* * the trial has been done.\n"; #undef @ISA; if( $@ ) { @@ -115,15 +115,15 @@ $use_xs = 1; eval q { - #print STDERR "over riding _s2u,_u2s\n"; + #print STDERR "overriding _s2u,_u2s\n"; do_memmap(); #print STDERR "memmap done\n"; END{ do_memunmap(); } - #print STDERR "binding xsubs done.\n"; + #print STDERR "binding xsubs has been done.\n"; }; if( $@ ) { - #print STDERR "error on last part of load XS.\n"; + #print STDERR "error in the last part of operation to load XS.\n"; $xs_loaderror = $@; CORE::die($@); } @@ -134,7 +134,7 @@ if( $@ ) { $xs_loaderror = $@; - CORE::die("Cannot Load Unicode::Japanese either XS nor PurePerl\n$@"); + CORE::die("Cannot load Unicode::Japanese of neither XS nor PurePerl side\n$@"); } if( !$use_xs ) { @@ -151,7 +151,7 @@ # ----------------------------------------------------------------------------- # Unicode::Japanese->new(); -# cache for char convert. +# cache for char conversion. # 2bytes. # JIS C 6226-1979 \e$@ # JIS X 0208-1983 \e$B @@ -193,7 +193,7 @@ sub _got_undefined_subroutine { my $subname = pop; - CORE::die "Undefined subroutine \&$subname called.\n"; + CORE::die "Undefined subroutine \&$subname got called.\n"; } # ----------------------------------------------------------------------------- @@ -205,9 +205,9 @@ { # load pure perl subs. use vars qw($AUTOLOAD); - my ($pkg,$subname) = $AUTOLOAD =~ /^(.*)::(\w+)$/ - or got_undefined_subroutine($AUTOLOAD); - no strict 'refs'; + + #print "AUTOLOAD... $AUTOLOAD\n"; + if(!defined($Unicode::Japanese::xs_loaderror) ) { Unicode::Japanese::PurePerl::_init_table(); @@ -216,9 +216,28 @@ return &$AUTOLOAD; } } - my $ppsubname = "$pkg\:\:PurePerl\:\:$subname"; + + my ($pkg, $subname) = do{ + local($1, $2); + $AUTOLOAD =~ /^(.*)::(\w+)$/ + } or got_undefined_subroutine($AUTOLOAD); + + my $pppkg = $pkg . '::PurePerl'; + my $ppsubname = $pkg . '::PurePerl::' . $subname; + if( !defined(&$ppsubname) ) + { + my $save = $@; + my @BAK = @_; + $pppkg->_loadsub($ppsubname); + $@ = $save; + @_ = @BAK; + } + my $sub = \&$ppsubname; - *$AUTOLOAD = $sub; # copy. + { + no strict 'refs'; + *$AUTOLOAD = $sub; # copy. + } goto &$sub; } @@ -229,7 +248,7 @@ use strict; -use vars qw(%CHARCODE %ESC %RE); +use vars qw(%CHARCODE %ESC %RE @CHARSET_LIST); use vars qw(@J2S @S2J @S2E @E2S @U2T %T2U %S2U %U2S %SA2U1 %U2SA1 %SA2U2 %U2SA2); @@ -282,10 +301,11 @@ E_ICON_AU_END => '">', E_JSKY_START => quotemeta($ESC{E_JSKY_START}), E_JSKY_END => '(?:'.quotemeta($ESC{E_JSKY_END}).'|\z)', - E_JSKYv1_UTF8 => qr/\xee(?:\x80[\x81-\xbf]|\x81[\x80-\x9a]|\x84[\x81-\xbf]|\x85[\x80-\x9a]|\x88[\x81-\xbf]|\x89[\x80-\x9a])/, - E_JSKYv2_UTF8 => qr/\xee(?:\x8c[\x81-\xbf]|\x8d[\x80-\x8d]|\x90[\x81-\xbf]|\x91[\x80-\x8c]|\x94[\x81-\xb7])/, + E_JSKYv1_UTF8 => '\xee(?:\x80[\x81-\xbf]|\x81[\x80-\x9a]|\x84[\x81-\xbf]|\x85[\x80-\x9a]|\x88[\x81-\xbf]|\x89[\x80-\x9a])', + E_JSKYv2_UTF8 => '\xee(?:\x8c[\x81-\xbf]|\x8d[\x80-\x8d]|\x90[\x81-\xbf]|\x91[\x80-\x8c]|\x94[\x81-\xb7])', ); +$]<5.005 and $RE{E_JSKY_END} =~ s/\\z/\$/; $RE{E_JSKY} = $RE{E_JSKY_START} . $RE{E_JSKY1} . $RE{E_JSKY2} . '+' . $RE{E_JSKY_END}; @@ -296,7 +316,7 @@ . $RE{E_JSKY1v2} . $RE{E_JSKY2} . '+' . $RE{E_JSKY_END}; -our @CHARSET_LIST = qw( +@CHARSET_LIST = qw( utf8 ucs2 ucs4 @@ -320,6 +340,7 @@ utf8-jsky utf8-icon-au + utf8-imode ); use vars qw($s2u_table $u2s_table); @@ -335,53 +356,70 @@ # ----------------------------------------------------------------------------- # AUTOLOAD # AUTOLOAD of Unicode::Japanese::PurePerl. -# load PurePerl methods from embeded data. +# load PurePerl methods from embedded data. # AUTOLOAD { use strict; use vars qw($AUTOLOAD); - #print STDERR "AUTOLOAD... $AUTOLOAD\n"; + #print "AUTOLOAD... $AUTOLOAD\n"; my $save = $@; my @BAK = @_; - my $subname = $AUTOLOAD; - $subname =~ s/^Unicode\:\:Japanese\:\:(?:PurePerl\:\:)?//; + my ($pkg, $subname) = do{ + local($1, $2); + $AUTOLOAD =~ /^(.*)::(\w+)$/ + } or got_undefined_subroutine($AUTOLOAD); + $pkg->_loadsub($AUTOLOAD); + + $@ = $save; + @_ = @BAK; + goto &$AUTOLOAD; +} + +sub _loadsub +{ + my $pkg = shift; + my $fullsubname = shift; #print "subs..\n",join("\n",keys %$TABLE,''); - + use vars qw($AUTOLOAD); + + local($1, $2); + my ($subpkg,$subname) = $fullsubname =~ /^(.*)::(\w+)$/ + or got_undefined_subroutine($fullsubname); + # check if(!defined($TABLE->{$subname}{offset})) { _init_table(); if( !defined($TABLE->{$subname}{offset}) ) { - if( substr($AUTOLOAD,-9) eq '::DESTROY' ) + if( $subname eq 'DESTROY' ) { + my $sub = sub{}; { - no strict; - *$AUTOLOAD = sub {}; + no strict 'refs'; + *$fullsubname = $sub; } - $@ = $save; - @_ = @BAK; - goto &$AUTOLOAD; + return $sub; } - CORE::die "Undefined subroutine \&$AUTOLOAD called.\n"; + CORE::die "Undefined subroutine \&$fullsubname got called.\n"; } } if($TABLE->{$subname}{offset} == -1) { - CORE::die "Double loaded \&$AUTOLOAD. It has some error.\n"; + CORE::die "\&$fullsubname is getting loaded twice. There must be a problem in AUTOLOAD.\n"; } seek($FH, $PROGLEN + $HEADLEN + $TABLE->{$subname}{offset}, 0) or die "Can't seek $subname. [$!]\n"; my $sub; - read($FH, $sub, $TABLE->{$subname}{length}) + read($FH, $sub, $TABLE->{$subname}{'length'}) or die "Can't read $subname. [$!]\n"; if( $]>=5.008 ) @@ -394,14 +432,11 @@ { CORE::die $@; } - $DB::sub = $AUTOLOAD; # Now debugger know where we are. + $DB::sub = $fullsubname; # Now debugger knows where we are. # evaled $TABLE->{$subname}{offset} = -1; - $@ = $save; - @_ = @BAK; - goto &$AUTOLOAD; } # ----------------------------------------------------------------------------- @@ -466,17 +501,17 @@ local($/) = "\n"; my $line; - while($line = <$FH>) + while(defined($line = <$FH>)) { last if($line =~ m/^__DATA__/); } $PROGLEN = tell($FH); read($FH, $HEADLEN, 4) - or die "Can't read table. [$!]\n"; + or die "Can't read the table. [$!]\n"; $HEADLEN = unpack('N', $HEADLEN); read($FH, $TABLE, $HEADLEN) - or die "Can't seek table. [$!]\n"; + or die "Can't seek the table. [$!]\n"; $TABLE =~ /(.*)/s; $TABLE = eval(($TABLE=~/(.*)/s)[0]); if($@) @@ -496,7 +531,7 @@ # ----------------------------------------------------------------------------- # _getFile -# load embeded file data. +# load embedded file data. # sub _getFile { my $this = shift; @@ -507,12 +542,12 @@ or die "no such file [$file]\n"; #my $offset16 = $TABLE->{$file}{offset} % 16; - #print STDERR "_getFile($file, $TABLE->{$file}{offset}, $TABLE->{$file}{length}, $offset16)\n"; + #print STDERR "_getFile($file, $TABLE->{$file}{offset}, $TABLE->{$file}{'length'}, $offset16)\n"; seek($FH, $PROGLEN + $HEADLEN + $TABLE->{$file}{offset}, 0) or die "Can't seek $file. [$!]\n"; my $data; - read($FH, $data, $TABLE->{$file}{length}) + read($FH, $data, $TABLE->{$file}{'length'}) or die "Can't read $file. [$!]\n"; $data; @@ -520,7 +555,7 @@ # ----------------------------------------------------------------------------- # use_I18N_Japanese -# copy from I18N::Japanese in jperl-5.5.3 +# copied from I18N::Japanese in jperl-5.5.3 # sub use_I18N_Japanese { @@ -544,7 +579,7 @@ # ----------------------------------------------------------------------------- # no_I18N_Japanese -# copy from I18N::Japanese in jperl-5.5.3 +# copied from I18N::Japanese in jperl-5.5.3 # sub no_I18N_Japanese { @@ -572,7 +607,7 @@ =head1 NAME -Unicode::Japanese - Japanese Character Encoding Handler +Unicode::Japanese - Convert encoding of japanese text =head1 SYNOPSIS @@ -583,7 +618,7 @@ # convert utf8 -> sjis print Unicode::Japanese->new($str)->sjis; - print unijp($str)->sjis; # same as avobe. + print unijp($str)->sjis; # same as above. # convert sjis -> utf8 @@ -593,13 +628,14 @@ print Unicode::Japanese->new($str,'sjis-imode')->get; - # convert ZENKAKU (utf8) -> HANKAKU (utf8) + # convert zenkaku (utf8) -> hankaku (utf8) print Unicode::Japanese->new($str)->z2h->get; =head1 DESCRIPTION -Module for conversion among Japanese character encodings. +The Unicode::Japanese module converts encoding of japanese text from one +encoding to another. =head2 FEATURES @@ -610,93 +646,139 @@ -The instance stores internal strings in UTF-8. +An instance of Unicode::Japanese internally holds a string in UTF-8. + + +=item * + + + +This module is implemented in two ways: XS and pure perl. If efficiency is +important for you, you should build and install the XS module. If you don't want +to, or if you can't build the XS module, you may use the pure perl module +instead. In that case, only you have to do is to copy Japanese.pm into somewhere +in @INC. + + +=item * + + + +This module can convert characters from zenkaku (full-width) form to hankaku +(half-width) form, and vice versa. Conversion between hiragana (one of two sets +of japanese phonetical alphabet) and katakana (another set of japanese +phonetical alphabet) is also supported. =item * -Supports both XS and Non-XS. -Use XS for high performance, -or No-XS for ease to use (only by copying Japanese.pm). +This module has mapping tables for emoji (graphic characters) defined by various +japanese mobile phones; DoCoMo i-mode, ASTEL dot-i and J-PHONE J-Sky. Those +letters are mapped on Unicode Private Use Area so unicode strings it outputs are +still valid even if they contain emoji, and you can safely pass them to other +softwares that can handle Unicode. =item * -Supports conversion between ZENKAKU and HANKAKU. +This module can map some emoji from one set to another. Different mobile phones +define different sets of emoji, so mapping each other is not always +possible. But since some emoji exist in two or more sets with similar +appearance, this module considers those emoji to be the same. =item * -Safely handles "EMOJI" of the mobile phones (DoCoMo i-mode, ASTEL dot-i -and J-PHONE J-Sky) by mapping them on Unicode Private Use Area. +This module uses the mapping table for MS-CP932 instead of the standard +Shift_JIS. The Shift_JIS encoding used by MS-Windows (MS-SJIS/MS-CP932) slightly +differs from the standard. =item * -Supports conversion of the same image of EMOJI -between different mobile phone's standard mutually. +When the module converts strings from Unicode to Shift_JIS, EUC-JP or +ISO-2022-JP, unicode letters which can't be represented in those encodings will +be encoded in "&#dddd;" form (decimal character reference). Note, however, that +letters in Unicode Private Use Area will be replaced with '?' mark ('QUESTION +MARK'; U+003F) instead of being encoded. In addition, encoding to character sets +for mobile phones makes every unrepresentable letters being '?' mark. =item * -Considers Shift_JIS(SJIS) as MS-CP932. -(Shift_JIS on MS-Windows (MS-SJIS/MS-CP932) differ from -generic Shift_JIS encodings.) +On perl-5.8.0 or later, this module handles the UTF-8 flag: the method utf8() +returns UTF-8 I string, and the method getu() returns UTF-8 I +string. + + +Currently the method get() returns UTF-8 I string but this behavior may be +changed in the future. + +Methods like sjis(), jis(), utf8(), and such like return I string. new(), +set(), getcode() methods just ignore the UTF-8 flag of strings they take. + + +=back + +=head1 REQUIREMENT + +=over 4 =item * -On converting Unicode to SJIS (and EUC-JP/JIS), those encodings that cannot -be converted to SJIS (except "EMOJI") are escaped in "&#dddd;" format. -"EMOJI" on Unicode Private Use Area is going to be '?'. -When converting strings from Unicode to SJIS of mobile phones, -any characters not up to their standard is going to be '?' +perl 5.10.x, 5.8.x, etc. (5.004 and later) =item * -On perl-5.8.0 and later, setting of utf-8 flag is performed properly. -utf8() method returns utf-8 `bytes' string and -getu() method returns utf-8 `char' string. +(optional) +C Compiler. +This module supports both XS and Pure Perl. +If you have no C Compilers, +Unicode::Japanese will be installed as Pure Perl module. + +=item * -get() method returns utf-8 `bytes' string in current release. -in future, the behavior of get() maybe change. -sjis(), jis(), utf8(), etc.. methods return bytes string. -The input of new, set, and a getcode method is not asked about utf8-flaged/bytes. +(optional) +Test.pm and Test::More for testing. =back +No other modules are required at run time. + + =head1 METHODS =over 4 =item $s = Unicode::Japanese->new($str [, $icode [, $encode]]) -Creates a new instance of Unicode::Japanese. +Create a new instance of Unicode::Japanese. -If arguments are specified, passes through to set method. +Any given parameters will be internally passed to the method set(). -=item unijp($str [, $icode [, $encode]]) +=item $s = unijp($str [, $icode [, $encode]]) Same as Unicode::Janaese->new(...). @@ -707,50 +789,69 @@ =item $str: string -=item $icode: character encodings, may be omitted (default = 'utf8') +=item $icode: optional character encoding (default: 'utf8') -=item $encode: ASCII encoding, may be omitted. +=item $encode: optional binary encoding (default: no binary encodings are assumed) =back -Set a string in the instance. -If '$icode' is omitted, string is considered as UTF-8. +Store a string into the instance. + +Possible character encodings are: -To specify a encodings, choose from the following; -'auto', 'utf8', 'ucs2', 'ucs4', 'utf16-be', 'utf16-le', 'utf16', -'utf32-be', 'utf32-le', 'utf32', 'jis', 'euc', 'euc-jp', -'sjis', 'cp932', 'sjis-imode', 'sjis-imode1', 'sjis-imode2', -'sjis-doti', 'sjis-doti1', 'sjis-jsky', 'sjis-jsky1', 'sjis-jsky2', -'jis-jsky', 'jis-jsky1', 'jis-jsky2', 'jis-au', 'jis-au1', 'jis-au2', -'sjis-au', 'sjis-au1', 'sjis-au2', 'sjis-icon-au', 'sjis-icon-au1', 'sjis-icon-au2', -'euc-icon-au', 'euc-icon-au1', 'euc-icon-au2', 'jis-icon-au', 'jis-icon-au1', 'jis-icon-au2', -'utf8-icon-au', 'utf8-icon-au1', 'utf8-icon-au2', 'ascii', 'binary' + auto + utf8 ucs2 ucs4 + utf16-be utf16-le utf16 + utf32-be utf32-le utf32 + sjis cp932 euc euc-jp jis + sjis-imode sjis-imode1 sjis-imode2 + utf8-imode utf8-imode1 utf8-imode2 + sjis-doti sjis-doti1 + sjis-jsky sjis-jsky1 sjis-jsky2 + jis-jsky jis-jsky1 jis-jsky2 + utf8-jsky utf8-jsky1 utf8-jsky2 + sjis-au sjis-au1 sjis-au2 + jis-au jis-au1 jis-au2 + sjis-icon-au sjis-icon-au1 sjis-icon-au2 + euc-icon-au euc-icon-au1 euc-icon-au2 + jis-icon-au jis-icon-au1 jis-icon-au2 + utf8-icon-au utf8-icon-au1 utf8-icon-au2 + ascii binary -For auto encoding detection, you MUST specify 'auto' -so as to call getcode() method automatically. +(see also L.) -For ASCII encoding, only 'base64' may be specified. -With it, the string will be decoded before storing. +If you want the Unicode::Japanese detect the character encoding of string, you +must explicitly specify 'auto' as the second argument. In that case, the given +string will be passed to the method getcode() to guess the encoding. -To decode binary, specify 'binary' as the encoding. +For binary encodings, only 'base64' is currently supported. If you specify +'base64' as the third argument, the given string will be decoded using Base64 +decoder. -'&#dddd' will be converted to "EMOJI", when specified 'sjis-imode' -or 'sjis-doti'. +Specify 'binary' as the second argument if you want your string to be stored +without modification. -In some cases, character encoding detection is -misleaded because more than one encodings have -same code points. +When you specify 'sjis-imode' or 'sjis-doti' as the character encoding, any +occurences of '&#dddd;' (decimal character reference) in the string will be +interpreted and decoded as code point of emoji, just like emoji implanted into +the string in binary form. -sjis is returned if a string is valid for both sjis and utf8. -And sjis-au is return if a string is valid for both -sjis-au and sjis-doti. +Since encoded forms of strings in various encodings are not clearly distinctive +to each other, it is not always certainly possible to detect what encoding is +used for a given string. + + +When a given string is possibly interpreted as both Shift_JIS and UTF-8 string, +this module considers such a string to be encoded in Shift_JIS. And if the +encoding is not distinguishable between 'sjis-au' and 'sjis-doti', this module +considers it 'sjis-au'. =item $str = $s->get @@ -761,15 +862,16 @@ =back -Gets a string with UTF-8. +Get the internal string in UTF-8. -return `bytes' string in current release, -this behavior will be changed. +This method currently returns a byte string (whose UTF-8 flag is turned off), +but this behavior may be changed in the future. -utf8() method for `character' string or -getu() method for `bytes' string seems better. +If you absolutely want a byte string, you should use the method utf8() +instead. And if you want a character string (whose UTF-8 flag is turned on), you +have to use the method getu(). =item $str = $s->getu @@ -780,10 +882,11 @@ =back -Gets a string with UTF-8. +Get the internal string in UTF-8. -On perl-5.8.0 and later, return value is with utf-8 flag. +On perl-5.8.0 or later, this method returns a character string with its UTF-8 +flag turned on. =item $code = $s->getcode($str) @@ -792,21 +895,21 @@ =item $str: string -=item $code: character encoding name +=item $code: name of character encoding =back -Detects the character encodings of I<$str>. +Detect the character encoding of given string. -Notice: This method detects B encoding of the string in the instance -but I<$str>. +Note that this method, exceptionaly, doesn't deal with the internal string of an +instance. -Character encodings are distinguished by the following algorithm: +To guess the encoding, the following algorithm is used: -(In case of PurePerl) +(For pure perl implementation) =over 4 @@ -815,100 +918,108 @@ -If BOM of UTF-32 is found, the encoding is utf32. +If the string has an UTF-32 BOM, its encoding is 'utf32'. =item 2 -If BOM of UTF-16 is found, the encoding is utf16. +If it has an UTF-16 BOM, its encoding is 'utf16'. =item 3 -If it is in proper UTF-32BE, the encoding is utf32-be. +If it is valid for UTF-32BE, its encoding is 'utf32-be'. =item 4 -If it is in proper UTF-32LE, the encoding is utf32-le. +If it is valid for UTF-32LE, its encoding is 'utf32-le'. =item 5 -Without NON-ASCII characters, the encoding is ascii. -(control codes except escape sequences has been included in ASCII) +If it contains no ESC characters or bytes whose eighth bit is on, its encoding +is 'ascii'. Every ASCII control characters (0x00-0x1F and 0x7F) except ESC +(0x1B) are considered to be in the range of 'ascii'. =item 6 -If it includes ISO-2022-JP(JIS) escape sequences, the encoding is jis. +If it contains escape sequences of ISO-2022-JP, its encoding is 'jis'. =item 7 -If it includes "J-PHONE EMOJI", the encoding is sjis-sky. +If it contains any emoji defined for J-PHONE, its encoding is 'sjis-jsky'. =item 8 -If it is in proper EUC-JP, the encoding is euc. +If it is valid for EUC-JP, its encoding is 'euc'. =item 9 -If it is in proper SJIS, the encoding is sjis. - - -If it is in proper SJIS and "EMOJI" of au, the encoding is sjis-au. +If it is valid for Shift_JIS, its encoding is 'sjis'. =item 10 -If it is in proper SJIS and "EMOJI" of i-mode, the encoding is sjis-imode. +If it contains any emoji defined for au, and everything else is valid for +Shift_JIS, its encoding is 'sjis-au'. =item 11 -If it is in proper SJIS and "EMOJI" of dot-i,the encoding is sjis-doti. +If it contains any emoji defined for i-mode, and everything else is valid for +Shift_JIS, its encoding is 'sjis-imode'. =item 12 -If it is in proper UTF-8, the encoding is utf8. +If it contains any emoji defined for dot-i, and everything else is valid for +Shift_JIS, its encoding is 'sjis-doti'. =item 13 -If none above is true, the encoding is unknown. +If it is valid for UTF-8, its encoding is 'utf8'. + + +=item 14 + + + +If no conditions above are fulfilled, its encoding is 'unknown'. =back -(In case of XS) +(For XS implementation) =over 4 @@ -917,22 +1028,22 @@ -If BOM of UTF-32 is found, the encoding is utf32. +If the string has an UTF-32 BOM, its encoding is 'utf32'. =item 2 -If BOM of UTF-16 is found, the encoding is utf16. +If it has an UTF-16 BOM, its encoding is 'utf16'. =item 3 -String is checked by State Transition if it is applicable -for any listed encodings below. +Find all possible encodings that might have been applied to the string from the +following: ascii / euc-jp / sjis / jis / utf8 / utf32-be / utf32-le / sjis-jsky / @@ -943,7 +1054,8 @@ -The listed order below is applied for a final determination. +If any encodings have been found possible, this module picks out one encoding +having the highest priority among them. The priority order is as follows: utf32-be / utf32-le / ascii / jis / euc-jp / sjis / sjis-jsky / sjis-imode / @@ -954,12 +1066,12 @@ -If none above is true, the encoding is unknown. +If no conditions above are fulfilled, its encoding is 'unknown'. =back -Regarding the algorithm, pay attention to the following: +Pay attention to the following pitfalls in the above algorithm: =over 2 @@ -968,41 +1080,42 @@ -UTF-8 is occasionally detected as SJIS. +UTF-8 strings might be accidentally considered to be encoded in Shift_JIS. =item * -Can NOT detect UCS2 automatically. +UCS-2 strings (sequence of raw UCS-2 letters in big-endian; each letters has +always 2 bytes) can't be detected because they look like nothing but sequences +of random bytes whose length is an even number. =item * -Can detect UTF-16 only when the string has BOM. +UTF-16 strings must have BOM to be detected. =item * -Can detect "EMOJI" when it is stored in binary, not in "&#dddd;" -format. (If only stored in "&#dddd;" format, getcode() will -return incorrect result. In that case, "EMOJI" will be crashed.) +Emoji are only be recognized if they are implanted into the string in binary +form. If they are described in '&#dddd;' form, they aren't considered to be +emoji. =back -Because each of XS and PurePerl has a different algorithm, A result of -the detection would be possibly different. In case that the string is -SJIS with escape characters, it would be considered as SJIS on -PurePerl. However, it can't be detected as S-JIS on XS. This is -because by using Algorithm, the string can't be distinguished between -SJIS and SJIS-Jsky. This exclusion of escape characters on XS from -the detection is suppose to be the same for EUC-JP. +Since the XS and pure perl implementations use different algorithms to guess +encoding, they may guess differently for the same string. Especially, the pure +perl implementation finds Shift_JIS strings containing ESC character (0x1B) to +be actually encoded in Shift_JIS but XS implementation doesn't. This is because +such strings can hardly be distinguished from 'sjis-jsky'. In addition, EUC-JP +strings containing ESC character are also rejected for the same reason. =item $code = $s->getcodelist($str) @@ -1011,324 +1124,420 @@ =item $str: string -=item $code: character encoding name +=item $code: name of character encodings =back -Detects the character encodings of I<$str>. +Detect the character encoding of given string. -This function returns all acceptable character encodings. +Unlike the method getcode(), getcodelist() returns a list of possible encodings. =item $str = $s->conv($ocode, $encode) -This function returns copy of contained string in $ocode encoding. - - =over 2 -=item $ocode: output character encoding (Choose from 'utf8', 'euc', 'euc-jp', 'jis', 'sjis', 'cp932', -'sjis-imode', 'sjis-imode1', 'sjis-imode2', 'sjis-doti', 'sjis-doti1', 'sjis-jsky', 'sjis-jsky1', 'sjis-jsky2', -'jis-jsky', 'jis-jsky1', 'jis-jsky2', 'jis-au', 'jis-au1', 'jis-au2', 'sjis-au', 'sjis-au1', 'sjis-au2', -'sjis-icon-au', 'sjis-icon-au1', 'sjis-icon-au2', 'euc-icon-au', 'euc-icon-au1', 'euc-icon-au2', -'jis-icon-au', 'jis-icon-au1', 'jis-icon-au2', 'utf8-icon-au', 'utf8-icon-au1', 'utf8-icon-au2', -'ucs2', 'ucs4', 'utf16', 'binary') - -Number at end of encoding names means emoji set version. -Larger number is newer set. -No number is same as newest set. -Generally you may use without digits. +=item $ocode: character encoding (possible encodings are:) + utf8 ucs2 ucs4 utf16 + sjis cp932 euc euc-jp jis + sjis-imode sjis-imode1 sjis-imode2 + utf8-imode utf8-imode1 utf8-imode2 + sjis-doti sjis-doti1 + sjis-jsky sjis-jsky1 sjis-jsky2 + jis-jsky jis-jsky1 jis-jsky2 + utf8-jsky utf8-jsky1 utf8-jsky2 + sjis-au sjis-au1 sjis-au2 + jis-au jis-au1 jis-au2 + sjis-icon-au sjis-icon-au1 sjis-icon-au2 + euc-icon-au euc-icon-au1 euc-icon-au2 + jis-icon-au jis-icon-au1 jis-icon-au2 + utf8-icon-au utf8-icon-au1 utf8-icon-au2 + binary + +(see also L.) + + +Some encodings for mobile phones have a trailing digit like 'sjis-au2'. Those +digits represent the version number of encodings. Such encodings have a variant +with no trailing digits, like 'sjis-au', which is the same as the latest version +among its variants. -=item $encode: encoding, may be omitted. + +=item $encode: optional binary encoding =item $str: string =back -Gets a string converted to I<$ocode>. +Get the internal string of instance with encoding it using a given character +encoding method. -For ASCII encoding, only 'base64' may be specified. With it, the string -encoded in base64 will be returned. +If you want the resulting string to be encoded in Base64, specify 'base64' as +the second argument. -On perl-5.8.0 and later, return value is not with utf-8 flag, and is -bytes string. +On perl-5.8.0 or later, the UTF-8 flag of resulting string is turned off even if +you specify 'utf8' to the first argument. =item $s->tag2bin -Replaces the substrings "&#dddd;" in the string with the binary entity -they mean. +Interpret decimal character references (&#dddd;) in the instance, and replaces +them with single characters they represent. =item $s->z2h -Converts ZENKAKU to HANKAKU. +Replace zenkaku (full-width) letters in the instance with hankaku (half-width) +letters. =item $s->h2z -Converts HANKAKU to ZENKAKU. +Replace hankaku (half-width) letters in the instance with zenkaku (full-width) +letters. =item $s->hira2kata -Converts HIRAGANA to KATAKANA. +Replace any hiragana in the instance with katakana. =item $s->kata2hira -Converts KATAKANA to HIRAGANA. +Replace any katakana in the instance with hiragana. =item $str = $s->jis -$str: string (JIS) +$str: byte string in ISO-2022-JP -Gets the string converted to ISO-2022-JP(JIS). +Get the internal string of instance with encoding it in ISO-2022-JP. =item $str = $s->euc -$str: string (EUC-JP) +$str: byte string in EUC-JP -Gets the string converted to EUC-JP. +Get the internal string of instance with encoding it in EUC-JP. =item $str = $s->utf8 -$str: `bytes' string (UTF-8) +$str: byte string in UTF-8 -Gets the string converted to UTF-8. +Get the internal UTF-8 string of instance. -On perl-5.8.0 and later, return value is not with utf-8 flag, and is -bytes string. +On perl-5.8.0 or later, the UTF-8 flag of resulting string is turned off. =item $str = $s->ucs2 -$str: string (UCS2) +$str: byte string in UCS-2 -Gets the string converted to UCS2. +Get the internal string of instance as a sequence of raw UCS-2 letters in +big-endian. Note that this is different from UTF-16BE as raw UCS-2 sequence has +no concept of surrogate pair. =item $str = $s->ucs4 -$str: string (UCS4) +$str: byte string in UCS-4 -Gets the string converted to UCS4. +Get the internal string of instance as a sequence of raw UCS-4 letters in +big-endian. This is practically the same as UTF-32BE. =item $str = $s->utf16 -$str: string (UTF-16) +$str: byte string in UTF-16 -Gets the string converted to UTF-16(big-endian). -BOM is not added. +Get the insternal string of instance with encoding it in UTF-16 in big-endian +with no BOM prepended. =item $str = $s->sjis -$str: string (SJIS) +$str: byte string in Shift_JIS -Gets the string converted to Shift_JIS(MS-SJIS/MS-CP932). +Get the internal string of instance with encoding it in Shift_JIS (MS-SJIS / +MS-CP932). =item $str = $s->sjis_imode -$str: string (SJIS/imode_EMOJI) +$str: byte string in 'sjis-imode' -Gets the string converted to SJIS for i-mode. -This method is alias of sjis_imode2. +Get the internal string of instance with encoding it in 'sjis-imode'. =item $str = $s->sjis_imode1 -$str: string (SJIS/imode_EMOJI) +$str: byte string in 'sjis-imode1' -Gets the string converted to SJIS for i-mode. -$str includes only basic pictgraphs, and is without extended pictgraphs. +Get the internal string of instance with encoding it in 'sjis-imode1'. =item $str = $s->sjis_imode2 -$str: string (SJIS/imode_EMOJI) +$str: byte string in 'sjis-imode2' -Gets the string converted to SJIS for i-mode. -$str includes both basic pictgraphs, and extended ones. +Get the internal string of instance with encoding it in 'sjis-imode2'. =item $str = $s->sjis_doti -$str: string (SJIS/dot-i_EMOJI) +$str: byte string in 'sjis-doti' -Gets the string converted to SJIS for dot-i. +Get the internal string of instance with encoding it in 'sjis-doti'. =item $str = $s->sjis_jsky -$str: string (SJIS/J-SKY_EMOJI) +$str: byte string in 'sjis-jsky' -Gets the string converted to SJIS for j-sky. -This method is alias of sjis_jsky2 on VERSION 0.15. +Get the internal string of instance with encoding it in 'sjis-jsky'. =item $str = $s->sjis_jsky1 -$str: string (SJIS/J-SKY_EMOJI) +$str: byte string in 'sjis-jsky1' -Gets the string converted to SJIS for j-sky. -$str includes from Page 1 to Page 3. +Get the internal string of instance with encoding it in 'sjis-jsky1'. =item $str = $s->sjis_jsky -$str: string (SJIS/J-SKY_EMOJI) +$str: byte string in 'sjis-jsky' -Gets the string converted to SJIS for j-sky. -$str includes from Page 1 to Page 6. +Get the internal string of instance with encoding it in 'sjis-jsky'. =item $str = $s->sjis_icon_au -$str: string (SJIS/AU-ICON-TAG) +$str: byte string in 'sjis-icon-au' -Gets the string converted to SJIS for au. +Get the internal string of instance with encoding it in 'sjis-icon-au'. -=item @str = $s->strcut($len) +=item $str_arrayref = $s->strcut($len) =over 2 -=item $len: number of characters +=item $len: maximum length of each chunks (in number of +full-width characters) -=item @str: strings +=item $str_arrayref: reference to array of strings =back -Splits the string by length(I<$len>). +Split the internal string of instance into chunks of a given length. -On perl-5.8.0 and later, each element in return array -is with utf-8 flag. +On perl-5.8.0 or later, UTF-8 flags of each chunks are turned on. =item $len = $s->strlen -$len: `visual width' of the string +$len: character width of the internal string -Gets the length of the string. This method has been offered to -substitute for perl build-in length(). ZENKAKU characters are -assumed to have lengths of 2, regardless of the coding being -SJIS or UTF-8. +Calculate the character width of the internal string. Half-width characters have +width of one unit, and full-width characters have width of two units. =item $s->join_csv(@values); -@values: data array +@values: array of strings -Converts the array to a string in CSV format, then stores into the instance. -In the meantime, adds a newline("\n") at the end of string. +Build a line of CSV from the arguments, and store it into the instance. The +resulting line has a trailing line break ("\n"). =item @values = $s->split_csv; -@values: data array +@values: array of strings -Splits the string, accounting it is in CSV format. -Each newline("\n") is removed before split. +Parse a line of CSV in the instance and return each columns. The line will be +chomp()ed before getting parsed. -on perl-5.8.0 and later, utf-8 flag of return value depends on -icode of set method. if $s contains binary, return value is bytes -too. if $s contains any string, return value is with utf-8 flag. +If the internal string was decoded from 'binary' encoding (see methods new() and +set()), the UTF-8 flags of the resulting array of strings are turned +off. Otherwise the flags are turned on. =back +=head1 SUPPORTED ENCODINGS + + +---------------+----+-----+-------+ + |encoding | in | out | guess | + +---------------+----+-----+-------+ + |auto : OK : -- | ----- | + +---------------+----+-----+-------+ + |utf8 : OK : OK | OK | + |ucs2 : OK : OK | ----- | + |ucs4 : OK : OK | ----- | + |utf16-be : OK : -- | ----- | + |utf16-le : OK : -- | ----- | + |utf16 : OK : OK | OK(#) | + |utf32-be : OK : -- | OK | + |utf32-le : OK : -- | OK | + |utf32 : OK : -- | OK(#) | + +---------------+----+-----+-------+ + |sjis : OK : OK | OK | + |cp932 : OK : OK | ----- | + |euc : OK : OK | OK | + |euc-jp : OK : OK | ----- | + |jis : OK : OK | OK | + +---------------+----+-----+-------+ + |sjis-imode : OK : OK | OK | + |sjis-imode1 : OK : OK | ----- | + |sjis-imode2 : OK : OK | ----- | + |utf8-imode : OK : OK | ----- | + |utf8-imode1 : OK : OK | ----- | + |utf8-imode2 : OK : OK | ----- | + +---------------+----+-----+-------+ + |sjis-doti : OK : OK | OK | + |sjis-doti1 : OK : OK | ----- | + +---------------+----+-----+-------+ + |sjis-jsky : OK : OK | OK | + |sjis-jsky1 : OK : OK | ----- | + |sjis-jsky2 : OK : OK | ----- | + |jis-jsky : OK : OK | ----- | + |jis-jsky1 : OK : OK | ----- | + |jis-jsky2 : OK : OK | ----- | + |utf8-jsky : OK : OK | ----- | + |utf8-jsky1 : OK : OK | ----- | + |utf8-jsky2 : OK : OK | ----- | + +---------------+----+-----+-------+ + |sjis-au : OK : OK | OK | + |sjis-au1 : OK : OK | ----- | + |sjis-au2 : OK : OK | ----- | + |jis-au : OK : OK | ----- | + |jis-au1 : OK : OK | ----- | + |jis-au2 : OK : OK | ----- | + |sjis-icon-au : OK : OK | ----- | + |sjis-icon-au1 : OK : OK | ----- | + |sjis-icon-au2 : OK : OK | ----- | + |euc-icon-au : OK : OK | ----- | + |euc-icon-au1 : OK : OK | ----- | + |euc-icon-au2 : OK : OK | ----- | + |jis-icon-au : OK : OK | ----- | + |jis-icon-au1 : OK : OK | ----- | + |jis-icon-au2 : OK : OK | ----- | + |utf8-icon-au : OK : OK | ----- | + |utf8-icon-au1 : OK : OK | ----- | + |utf8-icon-au2 : OK : OK | ----- | + +---------------+----+-----+-------+ + |ascii : OK : -- | OK | + |binary : OK : OK | ----- | + +---------------+----+-----+-------+ + (#): guessed when it has bom. + +=head2 GUESSING ORDER + + 1. utf32 (#) + 2. utf16 (#) + 3. utf32-be + 4. utf32-le + 5. ascii + 6. jis + 7. sjis-jsky (pp) + 8. euc + 9. sjis + 10. sjis-jsky (xs) + 11. sjis-au + 12. sjis-imode + 13. sjis-doti + 14. utf8 + 15. unknown + =head1 DESCRIPTION OF UNICODE MAPPING -Translation is proceedede as follows. +Transcoding between Unicode encodings and other ones is performed as below: =over 2 -=item SJIS +=item Shift_JIS -Mapped as MS-CP932. Mapping table in the following URL is used. +This module uses the mapping table of MS-CP932. -ftp://ftp.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP932.TXT +L<< ftp://ftp.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP932.TXT >> -If a character cannot be mapped to SJIS from Unicode, -it will be converted to &#dddd; format. -Pictgraphs are converted to "?"; +When the module tries to convert Unicode string to Shift_JIS, it represents most +letters which isn't available in Shift_JIS as decimal character reference +('&#dddd;'). There is one exception to this: every graphic characters for mobile +phones are replaced with '?' mark. -Also, any unmapped character will be converted into "?" when converting -to SJIS for mobile phones. +For variants of Shift_JIS defined for mobile phones, every unrepresentable +characters are replaced with '?' mark unlike the plain Shift_JIS. -=item EUC-JP/JIS +=item EUC-JP/ISO-2022-JP -Converted to SJIS and then mapped to Unicode. Any non-SJIS character -in the string will not be mapped correctly. +This module doesn't directly convert Unicode string from/to EUC-JP or +ISO-2022-JP: it once converts from/to Shift_JIS and then do the rest +translation. So characters which aren't available in the Shift_JIS can not be +properly translated. =item DoCoMo i-mode -Portion of involving "EMOJI" in F800 - F9FF is maapped - to U+0FF800 - U+0FF9FF. +This module maps emoji in the range of F800 - F9FF to U+0FF800 - U+0FF9FF. =item ASTEL dot-i -Portion of involving "EMOJI" in F000 - F4FF is mapped - to U+0FF000 - U+0FF4FF. +This module maps emoji in the range of F000 - F4FF to U+0FF000 - U+0FF4FF. =item J-PHONE J-SKY -"J-SKY EMOJI" are mapped down as follows: "\e\$"(\x1b\x24) escape -sequences, the first byte, the second byte and "\x0f". -With sequential "EMOJI"s of identical first bytes, -it may be compressed by arranging only the second bytes. +The encoding method defined by J-SKY is as follows: first an escape sequence +"\e\$" comes to indicate the beginning of emoji, then the first byte of an emoji +comes next, then the second bytes of at least one emoji comes next, then "\x0f" +comes last to indicate the end of emoji. If a string contains a series of emoji +whose first bytes are identical, such sequence can be compressed by cascading +second bytes of them to the single first byte. -4500 - 47FF is mapped to U+0FFB00 - U+0FFDFF, accounting the first -and the second bytes make one EMOJI character. +This module considers a pair of those first and second bytes to be one letter, +and map them from 4500 - 47FF to U+0FFB00 - U+0FFDFF. -Unicode::Japanese will compress "J-SKY_EMOJI" automatically when -the first bytes of a sequence of "EMOJI" are identical. +When the module encodes J-SKY emoji, it performs the compression automatically. =item AU -Portion of involving "EMOJI" is mapped to U+0FF500 - U+0FF6FF. +This module maps AU emoji to U+0FF500 - U+0FF6FF. =back @@ -1337,66 +1546,48 @@ use Unicode::Japanese qw(PurePerl); -If module was loaded with 'PurePerl' keyword, -it works on Non-XS mode. +If you want to explicitly take the pure perl implementation, pass +C<'PurePerl'> to the argument of the C statement. =head1 BUGS +Please report bugs and requests to C or +L. If you +report them to the web interface, any progress to your report will be +automatically sent back to you. + + =over 2 =item * -EUC-JP, JIS strings cannot be converted correctly when they include -non-SJIS characters because they are converted to SJIS before -being converted to UTF-8. +This module doesn't directly convert Unicode string from/to EUC-JP or +ISO-2022-JP: it once converts from/to Shift_JIS and then do the rest +translation. So characters which aren't available in the Shift_JIS can not be +properly translated. =item * -When using XS, character encoding detection of EUC-JP and -SJIS(included all EMOJI) strings when they include "\e" will -fail. Also, getcode() and all convert method will not work. +The XS implementation of getcode() fails to detect the encoding when the given +string contains \e while its encoding is EUC-JP or Shift_JIS. =item * -The Japanese.pm file will collapse if sent via ASCII mode of FTP, -as it has a trailing binary data. +Japanese.pm is composed of textual perl script and binary character conversion +table. If you transfer it on FTP using ASCII mode, the file will collapse. =back -=head1 AUTHOR INFORMATION - -Copyright 2001-2007 -SANO Taku (SAWATARI Mikage) and YAMASHINA Hio. -All right reserved. - - -This library is free software; you can redistribute it -and/or modify it under the same terms as Perl itself. - - -=head1 BUGS - -Bug reports and comments to: mikage@cpan.org. -Thank you. - - -Or, report any bugs or feature requests to -C, or through the web interface at -L. -I will be notified, and then you'll automatically be notified of progress on -your bug as I make changes. - - =head1 SUPPORT You can find documentation for this module with the perldoc command. @@ -1404,7 +1595,7 @@ perldoc Unicode::Japanese -You can also look for information at: +You can find more information at: =over 4 @@ -1444,7 +1635,7 @@ =head1 COPYRIGHT & LICENSE -Copyright 2001-2007 +Copyright 2001-2008 SANO Taku (SAWATARI Mikage) and YAMASHINA Hio, all rights reserved. @@ -1459,7 +1650,7 @@ __DATA__ -{'joinCsv'=>{'length'=>939,'offset'=>0},'_decodeBase64'=>{'length'=>609,'offset'=>939},'z2hNum'=>{'length'=>284,'offset'=>1548},'_utf16le_utf16'=>{'length'=>179,'offset'=>3074},'kata2hira'=>{'length'=>1242,'offset'=>1832},'jcode/emoji2/ea2u.dat'=>{'length'=>1320,'offset'=>372976},'_u2ai2'=>{'length'=>1062,'offset'=>3253},'z2hAlpha'=>{'length'=>836,'offset'=>4315},'_ucs4_utf8'=>{'length'=>936,'offset'=>5151},'h2zSym'=>{'length'=>316,'offset'=>6087},'utf8_icon_au1'=>{'length'=>73,'offset'=>6403},'h2z'=>{'length'=>114,'offset'=>6476},'jcode/emoji2/ea2u2s.dat'=>{'length'=>4096,'offset'=>430848},'sjis'=>{'length'=>177,'offset'=>6590},'euc_icon_au2'=>{'length'=>98,'offset'=>6767},'_u2si1'=>{'length'=>1619,'offset'=>6865},'_sj2u1'=>{'length'=>1144,'offset'=>8484},'euc_icon_au'=>{'length'=>97,'offset'=>9956},'tag2bin'=>{'length'=>328,'offset'=>9628},'z2hSym'=>{'length'=>596,'offset'=>10053},'ucs2'=>{'length'=>183,'offset'=>10649},'jis_au2'=>{'length'=>80,'offset'=>10832},'jcode/emoji2/ei2u2.dat'=>{'length'=>2048,'offset'=>244976},'_si2u1'=>{'length'=>1228,'offset'=>10912},'_utf8_utf16'=>{'length'=>950,'offset'=>12140},'jis_icon_au1'=>{'length'=>98,'offset'=>13090},'sjis_icon_au1'=>{'length'=>86,'offset'=>13188},'sjis_jsky2'=>{'length'=>70,'offset'=>13274},'jcode/emoji2/ei2u.dat'=>{'length'=>2048,'offset'=>226544},'getcode'=>{'length'=>2026,'offset'=>13344},'_j2s2'=>{'length'=>469,'offset'=>15370},'jcode/emoji2/ea2us.dat'=>{'length'=>4096,'offset'=>410368},'sjis_au2'=>{'length'=>95,'offset'=>15839},'h2zKanaD'=>{'length'=>810,'offset'=>15934},'sjis_imode1'=>{'length'=>71,'offset'=>16744},'eucjp'=>{'length'=>32,'offset'=>16815},'utf8'=>{'length'=>187,'offset'=>16847},'_s2e'=>{'length'=>244,'offset'=>17034},'jcode/emoji2/ea2u2.dat'=>{'length'=>3288,'offset'=>390688},'utf8_jsky'=>{'length'=>189,'offset'=>17278},'_uj2u2'=>{'length'=>874,'offset'=>17467},'utf8_jsky1'=>{'length'=>70,'offset'=>18341},'jcode/emoji2/eu2a2.dat'=>{'length'=>16384,'offset'=>393984},'jcode/s2u.dat'=>{'length'=>48573,'offset'=>177968},'conv'=>{'length'=>3663,'offset'=>18411},'_utf16be_utf16'=>{'length'=>71,'offset'=>22074},'jcode/emoji2/eu2j.dat'=>{'length'=>40960,'offset'=>266480},'hira2kata'=>{'length'=>1242,'offset'=>22145},'splitCsvu'=>{'length'=>197,'offset'=>23387},'sjis_doti1'=>{'length'=>69,'offset'=>23584},'_s2j'=>{'length'=>272,'offset'=>23653},'_sa2j2'=>{'length'=>384,'offset'=>23925},'_j2sa'=>{'length'=>179,'offset'=>24309},'sjis_au1'=>{'length'=>95,'offset'=>24488},'join_csv'=>{'length'=>29,'offset'=>24583},'_ai2u1'=>{'length'=>458,'offset'=>24612},'jcode/emoji2/eu2as.dat'=>{'length'=>16384,'offset'=>414464},'_s2u'=>{'length'=>988,'offset'=>25070},'jis_jsky1'=>{'length'=>82,'offset'=>26058},'jis_icon_au2'=>{'length'=>98,'offset'=>26140},'_j2sa3'=>{'length'=>434,'offset'=>26238},'sjis_jsky'=>{'length'=>189,'offset'=>26672},'_u2uj2'=>{'length'=>788,'offset'=>26861},'jis'=>{'length'=>179,'offset'=>27649},'jis_au1'=>{'length'=>80,'offset'=>27828},'_utf8_ucs4'=>{'length'=>1149,'offset'=>27908},'get'=>{'length'=>162,'offset'=>29057},'z2h'=>{'length'=>114,'offset'=>29219},'getu'=>{'length'=>266,'offset'=>29333},'_loadConvTable'=>{'length'=>18009,'offset'=>29599},'unijp'=>{'length'=>137,'offset'=>47608},'_u2uj1'=>{'length'=>806,'offset'=>47745},'jcode/emoji2/eu2a2s.dat'=>{'length'=>16384,'offset'=>434944},'_u2ja1'=>{'length'=>1639,'offset'=>48551},'_j2s'=>{'length'=>177,'offset'=>50190},'utf16'=>{'length'=>187,'offset'=>50367},'utf8_jsky2'=>{'length'=>70,'offset'=>50554},'_u2ai1'=>{'length'=>1203,'offset'=>50624},'sjis_icon_au2'=>{'length'=>86,'offset'=>51827},'_u2si2'=>{'length'=>1620,'offset'=>51913},'jcode/emoji2/eu2i.dat'=>{'length'=>16384,'offset'=>228592},'splitCsv'=>{'length'=>350,'offset'=>53533},'jcode/emoji2/eu2i2.dat'=>{'length'=>16384,'offset'=>247024},'sjis_jsky1'=>{'length'=>70,'offset'=>53883},'_s2j3'=>{'length'=>355,'offset'=>53953},'_sa2u1'=>{'length'=>1137,'offset'=>54308},'_u2s'=>{'length'=>2320,'offset'=>55445},'_sa2j3'=>{'length'=>455,'offset'=>57765},'_utf16_utf8'=>{'length'=>769,'offset'=>58220},'h2zNum'=>{'length'=>174,'offset'=>58989},'h2zKanaK'=>{'length'=>979,'offset'=>59163},'strlen'=>{'length'=>360,'offset'=>60142},'strcutu'=>{'length'=>195,'offset'=>60502},'sjis_imode2'=>{'length'=>71,'offset'=>60697},'_validate_utf8'=>{'length'=>855,'offset'=>60768},'jcode/emoji2/eu2a.dat'=>{'length'=>16384,'offset'=>374304},'set'=>{'length'=>5325,'offset'=>61623},'_ucs2_utf8'=>{'length'=>549,'offset'=>66948},'_utf16_utf16'=>{'length'=>300,'offset'=>67497},'h2zAlpha'=>{'length'=>264,'offset'=>67797},'z2hKanaK'=>{'length'=>979,'offset'=>68061},'getcodelist'=>{'length'=>2241,'offset'=>69040},'_sj2u2'=>{'length'=>1503,'offset'=>71281},'jcode/emoji2/ed2u.dat'=>{'length'=>5120,'offset'=>351472},'jis_icon_au'=>{'length'=>97,'offset'=>72784},'_utf32_ucs4'=>{'length'=>312,'offset'=>72881},'_ai2u2'=>{'length'=>410,'offset'=>73193},'utf8_icon_au2'=>{'length'=>73,'offset'=>73603},'_uj2u1'=>{'length'=>600,'offset'=>73676},'_sa2j'=>{'length'=>174,'offset'=>74276},'h2zKana'=>{'length'=>185,'offset'=>74450},'z2hKana'=>{'length'=>89,'offset'=>74635},'_si2u2'=>{'length'=>1227,'offset'=>74724},'_u2sj1'=>{'length'=>1772,'offset'=>75951},'_u2sj2'=>{'length'=>1794,'offset'=>77723},'utf8_icon_au'=>{'length'=>72,'offset'=>79517},'jis_jsky2'=>{'length'=>82,'offset'=>79589},'sjis_doti'=>{'length'=>188,'offset'=>79671},'_e2s'=>{'length'=>202,'offset'=>79859},'jcode/emoji2/ej2u2.dat'=>{'length'=>3072,'offset'=>307440},'euc'=>{'length'=>175,'offset'=>80061},'_j2s3'=>{'length'=>337,'offset'=>80236},'jcode/emoji2/ej2u.dat'=>{'length'=>3072,'offset'=>263408},'_j2sa2'=>{'length'=>446,'offset'=>80573},'ucs4'=>{'length'=>183,'offset'=>81019},'_sd2u'=>{'length'=>1221,'offset'=>81202},'_u2ja2'=>{'length'=>1640,'offset'=>82423},'_s2e2'=>{'length'=>446,'offset'=>84063},'z2hKanaD'=>{'length'=>498,'offset'=>84509},'_u2sd'=>{'length'=>1615,'offset'=>85007},'sjis_au'=>{'length'=>94,'offset'=>86622},'jcode/emoji2/eu2j2.dat'=>{'length'=>40960,'offset'=>310512},'jcode/emoji2/eu2d.dat'=>{'length'=>16384,'offset'=>356592},'jcode/u2s.dat'=>{'length'=>85504,'offset'=>92464},'_utf8_ucs2'=>{'length'=>755,'offset'=>86716},'euc_icon_au1'=>{'length'=>98,'offset'=>87471},'jis_au'=>{'length'=>195,'offset'=>87569},'_utf32le_ucs4'=>{'length'=>178,'offset'=>87764},'sjis_imode'=>{'length'=>192,'offset'=>87942},'_e2s2'=>{'length'=>535,'offset'=>88134},'_s2j2'=>{'length'=>377,'offset'=>88669},'_encodeBase64'=>{'length'=>741,'offset'=>89046},'validate_utf8'=>{'length'=>129,'offset'=>89787},'sjis_icon_au'=>{'length'=>85,'offset'=>89916},'split_csv'=>{'length'=>131,'offset'=>90001},'_sa2u2'=>{'length'=>1138,'offset'=>90132},'jis_jsky'=>{'length'=>200,'offset'=>91270},'strcut'=>{'length'=>888,'offset'=>91470},'cp932'=>{'length'=>33,'offset'=>92358},'_utf32be_ucs4'=>{'length'=>70,'offset'=>92391}} sub joinCsv { +{'joinCsv'=>{'length'=>947,'offset'=>0},'_decodeBase64'=>{'length'=>610,'offset'=>947},'z2hNum'=>{'length'=>284,'offset'=>1557},'_utf16le_utf16'=>{'length'=>179,'offset'=>3083},'kata2hira'=>{'length'=>1242,'offset'=>1841},'jcode/emoji2/ea2u.dat'=>{'length'=>1320,'offset'=>376896},'_u2ai2'=>{'length'=>1062,'offset'=>3262},'z2hAlpha'=>{'length'=>836,'offset'=>4324},'_u2ui2'=>{'length'=>721,'offset'=>5160},'_ui2u2'=>{'length'=>785,'offset'=>5881},'_ucs4_utf8'=>{'length'=>936,'offset'=>6666},'h2zSym'=>{'length'=>316,'offset'=>7602},'utf8_icon_au1'=>{'length'=>73,'offset'=>7918},'h2z'=>{'length'=>114,'offset'=>7991},'jcode/emoji2/ea2u2s.dat'=>{'length'=>4096,'offset'=>434768},'sjis'=>{'length'=>177,'offset'=>8105},'euc_icon_au2'=>{'length'=>98,'offset'=>8282},'_u2si1'=>{'length'=>1619,'offset'=>8380},'_sj2u1'=>{'length'=>1144,'offset'=>9999},'euc_icon_au'=>{'length'=>97,'offset'=>11471},'tag2bin'=>{'length'=>328,'offset'=>11143},'z2hSym'=>{'length'=>596,'offset'=>11568},'ucs2'=>{'length'=>183,'offset'=>12164},'jis_au2'=>{'length'=>80,'offset'=>12347},'jcode/emoji2/ei2u2.dat'=>{'length'=>2048,'offset'=>248896},'_si2u1'=>{'length'=>1228,'offset'=>12427},'_utf8_utf16'=>{'length'=>950,'offset'=>13655},'jis_icon_au1'=>{'length'=>98,'offset'=>14605},'sjis_icon_au1'=>{'length'=>86,'offset'=>14703},'sjis_jsky2'=>{'length'=>70,'offset'=>14789},'jcode/emoji2/ei2u.dat'=>{'length'=>2048,'offset'=>230464},'getcode'=>{'length'=>2111,'offset'=>14859},'_j2s2'=>{'length'=>469,'offset'=>16970},'jcode/emoji2/ea2us.dat'=>{'length'=>4096,'offset'=>414288},'sjis_au2'=>{'length'=>95,'offset'=>17439},'h2zKanaD'=>{'length'=>810,'offset'=>17534},'sjis_imode1'=>{'length'=>71,'offset'=>18344},'eucjp'=>{'length'=>32,'offset'=>18415},'utf8'=>{'length'=>187,'offset'=>18447},'_s2e'=>{'length'=>244,'offset'=>18634},'jcode/emoji2/ea2u2.dat'=>{'length'=>3288,'offset'=>394608},'utf8_jsky'=>{'length'=>189,'offset'=>18878},'_uj2u2'=>{'length'=>874,'offset'=>19067},'utf8_jsky1'=>{'length'=>70,'offset'=>19941},'jcode/emoji2/eu2a2.dat'=>{'length'=>16384,'offset'=>397904},'jcode/s2u.dat'=>{'length'=>48573,'offset'=>181888},'conv'=>{'length'=>3896,'offset'=>20011},'_utf16be_utf16'=>{'length'=>71,'offset'=>23907},'jcode/emoji2/eu2j.dat'=>{'length'=>40960,'offset'=>270400},'hira2kata'=>{'length'=>1242,'offset'=>23978},'splitCsvu'=>{'length'=>197,'offset'=>25220},'_u2ui1'=>{'length'=>744,'offset'=>25417},'sjis_doti1'=>{'length'=>69,'offset'=>26161},'_s2j'=>{'length'=>272,'offset'=>26230},'_sa2j2'=>{'length'=>384,'offset'=>26502},'_j2sa'=>{'length'=>179,'offset'=>26886},'sjis_au1'=>{'length'=>95,'offset'=>27065},'join_csv'=>{'length'=>29,'offset'=>27160},'_ai2u1'=>{'length'=>458,'offset'=>27189},'jcode/emoji2/eu2as.dat'=>{'length'=>16384,'offset'=>418384},'_s2u'=>{'length'=>988,'offset'=>27647},'utf8_imode1'=>{'length'=>71,'offset'=>28635},'_j2sa3'=>{'length'=>434,'offset'=>28706},'jis_jsky1'=>{'length'=>82,'offset'=>29140},'jis_icon_au2'=>{'length'=>98,'offset'=>29222},'sjis_jsky'=>{'length'=>189,'offset'=>29320},'_u2uj2'=>{'length'=>788,'offset'=>29509},'jis'=>{'length'=>179,'offset'=>30297},'jis_au1'=>{'length'=>80,'offset'=>30476},'_utf8_ucs4'=>{'length'=>1149,'offset'=>30556},'get'=>{'length'=>162,'offset'=>31705},'z2h'=>{'length'=>114,'offset'=>31867},'getu'=>{'length'=>266,'offset'=>31981},'_loadConvTable'=>{'length'=>18009,'offset'=>32247},'unijp'=>{'length'=>137,'offset'=>50256},'utf8_imode2'=>{'length'=>71,'offset'=>50393},'_u2uj1'=>{'length'=>806,'offset'=>50464},'jcode/emoji2/eu2a2s.dat'=>{'length'=>16384,'offset'=>438864},'_u2ja1'=>{'length'=>1639,'offset'=>51270},'_j2s'=>{'length'=>177,'offset'=>52909},'utf16'=>{'length'=>187,'offset'=>53086},'utf8_jsky2'=>{'length'=>70,'offset'=>53273},'_u2ai1'=>{'length'=>1203,'offset'=>53343},'sjis_icon_au2'=>{'length'=>86,'offset'=>54546},'_u2si2'=>{'length'=>1620,'offset'=>54632},'jcode/emoji2/eu2i.dat'=>{'length'=>16384,'offset'=>232512},'splitCsv'=>{'length'=>350,'offset'=>56252},'jcode/emoji2/eu2i2.dat'=>{'length'=>16384,'offset'=>250944},'sjis_jsky1'=>{'length'=>70,'offset'=>56602},'_s2j3'=>{'length'=>355,'offset'=>56672},'_sa2u1'=>{'length'=>1137,'offset'=>57027},'_u2s'=>{'length'=>2320,'offset'=>58164},'_sa2j3'=>{'length'=>455,'offset'=>60484},'_utf16_utf8'=>{'length'=>769,'offset'=>60939},'h2zNum'=>{'length'=>174,'offset'=>61708},'h2zKanaK'=>{'length'=>979,'offset'=>61882},'strlen'=>{'length'=>360,'offset'=>62861},'strcutu'=>{'length'=>195,'offset'=>63221},'sjis_imode2'=>{'length'=>71,'offset'=>63416},'_validate_utf8'=>{'length'=>763,'offset'=>63487},'jcode/emoji2/eu2a.dat'=>{'length'=>16384,'offset'=>378224},'z2hKanaK'=>{'length'=>979,'offset'=>64250},'h2zAlpha'=>{'length'=>264,'offset'=>65229},'set'=>{'length'=>5582,'offset'=>65493},'_ucs2_utf8'=>{'length'=>549,'offset'=>71075},'_utf16_utf16'=>{'length'=>300,'offset'=>71624},'getcodelist'=>{'length'=>2241,'offset'=>71924},'_sj2u2'=>{'length'=>1503,'offset'=>74165},'jcode/emoji2/ed2u.dat'=>{'length'=>5120,'offset'=>355392},'jis_icon_au'=>{'length'=>97,'offset'=>75668},'_utf32_ucs4'=>{'length'=>312,'offset'=>75765},'_ai2u2'=>{'length'=>410,'offset'=>76077},'utf8_icon_au2'=>{'length'=>73,'offset'=>76487},'_uj2u1'=>{'length'=>600,'offset'=>76560},'_sa2j'=>{'length'=>174,'offset'=>77160},'h2zKana'=>{'length'=>185,'offset'=>77334},'z2hKana'=>{'length'=>89,'offset'=>77519},'utf8_imode'=>{'length'=>192,'offset'=>77608},'_si2u2'=>{'length'=>1227,'offset'=>77800},'_u2sj1'=>{'length'=>1772,'offset'=>79027},'_u2sj2'=>{'length'=>1794,'offset'=>80799},'utf8_icon_au'=>{'length'=>72,'offset'=>82593},'jis_jsky2'=>{'length'=>82,'offset'=>82665},'sjis_doti'=>{'length'=>188,'offset'=>82747},'_e2s'=>{'length'=>202,'offset'=>82935},'jcode/emoji2/ej2u2.dat'=>{'length'=>3072,'offset'=>311360},'euc'=>{'length'=>175,'offset'=>83137},'_j2s3'=>{'length'=>337,'offset'=>83312},'jcode/emoji2/ej2u.dat'=>{'length'=>3072,'offset'=>267328},'ucs4'=>{'length'=>183,'offset'=>83649},'_j2sa2'=>{'length'=>446,'offset'=>83832},'_ui2u1'=>{'length'=>803,'offset'=>84278},'_sd2u'=>{'length'=>1221,'offset'=>85081},'_u2ja2'=>{'length'=>1640,'offset'=>86302},'_s2e2'=>{'length'=>446,'offset'=>87942},'z2hKanaD'=>{'length'=>498,'offset'=>88388},'_u2sd'=>{'length'=>1615,'offset'=>88886},'sjis_au'=>{'length'=>94,'offset'=>90501},'jcode/emoji2/eu2j2.dat'=>{'length'=>40960,'offset'=>314432},'jcode/emoji2/eu2d.dat'=>{'length'=>16384,'offset'=>360512},'jcode/u2s.dat'=>{'length'=>85504,'offset'=>96384},'_utf8_ucs2'=>{'length'=>755,'offset'=>90595},'euc_icon_au1'=>{'length'=>98,'offset'=>91350},'jis_au'=>{'length'=>195,'offset'=>91448},'_utf32le_ucs4'=>{'length'=>178,'offset'=>91643},'sjis_imode'=>{'length'=>192,'offset'=>91821},'_e2s2'=>{'length'=>535,'offset'=>92013},'_s2j2'=>{'length'=>377,'offset'=>92548},'_encodeBase64'=>{'length'=>775,'offset'=>92925},'validate_utf8'=>{'length'=>129,'offset'=>93700},'split_csv'=>{'length'=>131,'offset'=>93914},'sjis_icon_au'=>{'length'=>85,'offset'=>93829},'_sa2u2'=>{'length'=>1138,'offset'=>94045},'jis_jsky'=>{'length'=>200,'offset'=>95183},'strcut'=>{'length'=>894,'offset'=>95383},'_utf32be_ucs4'=>{'length'=>70,'offset'=>96310},'cp932'=>{'length'=>33,'offset'=>96277}} sub joinCsv { my $this = shift; my $list; @@ -1489,7 +1680,7 @@ else { my $ref = ref($_[0]); - die "String->joinCsv, Param[1] is not ARRAY/ARRRAY-ref. [$ref]\n"; + die "String#joinCsv: param[1] is neither ARRRAY Ref nor Scalar. [$ref]\n"; } my $text; @@ -1521,7 +1712,7 @@ $str =~ tr|A-Za-z0-9+=/||cd; # remove non-base64 chars if (length($str) % 4) { - warn("Length of base64 data not a multiple of 4"); + warn("Length of Base64 data is not multiple of 4"); } $str =~ s/=+$//; # remove padding $str =~ tr|A-Za-z0-9+/| -_|; # convert to uuencoded format @@ -1630,6 +1821,68 @@ $this; } +sub _u2ui2 +{ + my $this = shift; + my $str = shift; + + if(!defined($str)) + { + return ''; + } + + # imode : F800-F9FF => U+0FF800 - U+0FF9FF + # [BASIC] + # F89F = E63E = ee 98 be = F3BFA29F + # F9B0 = E70B = ee 9c 8b = F3BFA6B0 + # [EXTENSION] + # F9B1 = E70C = ee 9c 8c = F3BFA6B1 + # F9FC = E757 = ee 9d 97 = F3BFA7BC + $str =~ s{\xf3\xbf([\xa2-\xa7][\x80-\xbf])}{ + my ($in1, $in2) = unpack("CC", $1); + my $in = (($in1 - 0xa2) << 6) + $in2; + my $diff = $in <= 0xfc ? 0xfc - 0x9b + : $in <= 0x17e ? 0x17e - 0xda + : 0x1b0 - 0x10b; + my $ucs2offset = $in + 0xe600 - $diff; + pack("C3", 0xee, (($ucs2offset>>6)&63)+128, ($ucs2offset&63)+128); + }xeg; + + $str; +} +sub _ui2u2 +{ + my $this = shift; + my $str = shift; + + if(!defined($str)) + { + return ''; + } + + if(!defined($ei2u2)) + { + $ei2u2 = $this->_getFile('jcode/emoji2/ei2u2.dat'); + } + + $str = $this->_validate_utf8($str); + + # imode : F800-F9FF => U+0FF800 - U+0FF9FF + # E63E - E70B = ee 98 be - ee 9c 8b + # E70C - E757 = ee 9c 8c - ee 9d 97 + $str =~ s{\xee([\x98-\x9e][\x80-\xbf])}{ + my ($in1, $in2) = unpack("CC", $1); + my $in = (($in1 - 0x98) << 6) + ($in2 - 0x80); + my $diff = $in <= 0x9b ? ( 0xfc - 0x9b) + : $in <= 0xda ? (0x17e - 0xda) + : (0x1b0 - 0x10b); + my $sjisoffset = $diff + $in; + my $sjisbin = pack("n", $sjisoffset); + $S2U{$sjisbin} ||= substr($ei2u2, $sjisoffset * 4, 4) || '?'; + }xeg; + + $str; +} sub _ucs4_utf8 { my $this = shift; my $str = shift; @@ -2066,13 +2319,16 @@ { return 'sjis'; } - if($str =~ m/^(?:$RE{E_SJIS_AU})/o) + + my $str3; + $str3 = $str2; + 1 while($str3 =~ s/^(?:$RE{ASCII}|$RE{SJIS_DBCS}|$RE{SJIS_KANA}|$RE{E_SJIS_AU})//o); + if($str3 eq '') { return 'sjis-au'; } - my $str3; $str3 = $str2; 1 while($str3 =~ s/^(?:$RE{ASCII}|$RE{SJIS_DBCS}|$RE{SJIS_KANA}|$RE{E_IMODE})//o); if($str3 eq '') @@ -2244,7 +2500,7 @@ if(!defined($ocode)) { use Carp; - croak(qq(String->conv, Param[1] is undef.)); + croak(qq(String#conv: param[1] is undef.)); } elsif($ocode eq 'utf8') { @@ -2274,6 +2530,18 @@ { $res = $this->sjis_imode2; } + elsif($ocode eq 'utf8-imode') + { + $res = $this->utf8_imode; + } + elsif($ocode eq 'utf8-imode1') + { + $res = $this->utf8_imode1; + } + elsif($ocode eq 'utf8-imode2') + { + $res = $this->utf8_imode2; + } elsif($ocode eq 'sjis-doti') { $res = $this->sjis_doti; @@ -2409,7 +2677,7 @@ else { use Carp; - croak(qq(String->conv, Param[1] "$ocode" is error.)); + croak(qq(String#conv: param[1]: invalid ocode "$ocode")); } if(defined($encode)) @@ -2421,7 +2689,7 @@ else { use Carp; - croak(qq(String->conv, Param[2] "$encode" encode name error.)); + croak(qq(String#conv: param[2]: invalid encoding "$encode")); } } @@ -2460,6 +2728,35 @@ $result; } +sub _u2ui1 +{ + my $this = shift; + my $str = shift; + + if(!defined($str)) + { + return ''; + } + + # imode : F800-F9FF => U+0FF800 - U+0FF9FF + # [BASIC] + # F89F = E63E = ee 98 be = F3BFA29F + # F9B0 = E70B = ee 9c 8b = F3BFA6B0 + # [EXTENSION] + # F9B1 = E70C = ee 9c 8c = F3BFA6B1 + # F9FC = E757 = ee 9d 97 = F3BFA7BC + $str =~ s{\xf3\xbf([\xa2-\xa7][\x80-\xbf])}{ + my ($in1, $in2) = unpack("CC", $1); + my $in = (($in1 - 0xa2) << 6) + $in2; + my $diff = $in <= 0xfc ? 0xfc - 0x9b + : $in <= 0x17e ? 0x17e - 0xda + : 0x1b0 - 0x10b; + my $ucs2offset = $in + 0xe600 - $diff; + $in <= 0x1b0 ? pack("C3", 0xee, (($ucs2offset>>6)&63)+128, ($ucs2offset&63)+128) : '?'; + }xeg; + + $str; +} sub sjis_doti1 { my $this = shift; @@ -2595,15 +2892,10 @@ $str; } -sub jis_jsky1 -{ - my $this = shift; - $this->_s2j($this->_u2sj1($this->{str})); -} -sub jis_icon_au2 +sub utf8_imode1 { my $this = shift; - $this->_s2j($this->_u2s($this->_u2ai2($this->{str}))); + $this->_u2ui1($this->{str}); } sub _j2sa3 { my $this = shift; @@ -2627,6 +2919,16 @@ pack('CC', $c1, $c2); } +sub jis_jsky1 +{ + my $this = shift; + $this->_s2j($this->_u2sj1($this->{str})); +} +sub jis_icon_au2 +{ + my $this = shift; + $this->_s2j($this->_u2s($this->_u2ai2($this->{str}))); +} # ----------------------------------------------------------------------------- # $bytes_jsky = $unijp->sjis_jsky(); # @@ -3101,6 +3403,11 @@ { Unicode::Japanese->new(@_); } +sub utf8_imode2 +{ + my $this = shift; + $this->_u2ui2($this->{str}); +} # utf8 => utf8-jsky2 sub _u2uj1 { @@ -3666,15 +3973,15 @@ # ŬڤǤʤĹ˥󥳡ɤƤ # ʸ ? ֤. defined($str) and $str =~ s{ - # 2 bytes char which is restricted 1 byte. + # 2 bytes char # [\xc0-\xc1] [\x80-\xbf] | - # 3 bytes char which is restricted <= 2 bytes. + # 3 bytes char # \xe0 [\x80-\x9f] [\x80-\xbf] | - # 4 bytes char which is restricted <= 3 bytes. + # 4 bytes char # \xf0 [\x80-\x8f] [\x80-\xbf] [\x80-\xbf] | @@ -3693,6 +4000,30 @@ }{?}xg; $str; } +sub z2hKanaK { + my $this = shift; + + if(!defined(%_z2hKanaK)) + { + $this->_loadConvTable; + } + + $this->{str} =~ s/(\xe3\x80\x81|\xe3\x80\x82|\xe3\x83\xbb|\xe3\x82\x9b|\xe3\x82\x9c|\xe3\x83\xbc|\xe3\x80\x8c|\xe3\x80\x8d|\xe3\x82\xa1|\xe3\x82\xa2|\xe3\x82\xa3|\xe3\x82\xa4|\xe3\x82\xa5|\xe3\x82\xa6|\xe3\x82\xa7|\xe3\x82\xa8|\xe3\x82\xa9|\xe3\x82\xaa|\xe3\x82\xab|\xe3\x82\xad|\xe3\x82\xaf|\xe3\x82\xb1|\xe3\x82\xb3|\xe3\x82\xb5|\xe3\x82\xb7|\xe3\x82\xb9|\xe3\x82\xbb|\xe3\x82\xbd|\xe3\x82\xbf|\xe3\x83\x81|\xe3\x83\x83|\xe3\x83\x84|\xe3\x83\x86|\xe3\x83\x88|\xe3\x83\x8a|\xe3\x83\x8b|\xe3\x83\x8c|\xe3\x83\x8d|\xe3\x83\x8e|\xe3\x83\x8f|\xe3\x83\x92|\xe3\x83\x95|\xe3\x83\x98|\xe3\x83\x9b|\xe3\x83\x9e|\xe3\x83\x9f|\xe3\x83\xa0|\xe3\x83\xa1|\xe3\x83\xa2|\xe3\x83\xa3|\xe3\x83\xa4|\xe3\x83\xa5|\xe3\x83\xa6|\xe3\x83\xa7|\xe3\x83\xa8|\xe3\x83\xa9|\xe3\x83\xaa|\xe3\x83\xab|\xe3\x83\xac|\xe3\x83\xad|\xe3\x83\xaf|\xe3\x83\xb2|\xe3\x83\xb3)/$_z2hKanaK{$1}/eg; + + $this; +} +sub h2zAlpha { + my $this = shift; + + if(!defined(%_h2zAlpha)) + { + $this->_loadConvTable; + } + + $this->{str} =~ s/(A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)/$_h2zAlpha{$1}/eg; + + $this; +} # ----------------------------------------------------------------------------- # $unijp->set($str,[$icode,[$encode]]); # @@ -3705,15 +4036,15 @@ if(ref($str)) { - die "String->set, Param[1] is Ref.\n"; + die "String#set: param[1] is a Ref.\n"; } if(ref($icode)) { - die "String->set, Param[2] is Ref.\n"; + die "String#set: param[2] is a Ref.\n"; } if(ref($encode)) { - die "String->set, Param[3] is Ref.\n"; + die "String#set, Param[3] is a Ref.\n"; } if( $]>=5.008 ) @@ -3729,12 +4060,12 @@ } else { - die "String->set, Param[3] encode name error.\n"; + die "String#set: param[3]: invalid encoding [$encode]\n"; } } if(!defined($icode)) - { # omitted then 'utf8' + { # defaults to 'utf8' $this->{str} = $this->_validate_utf8($str); $this->{icode} = 'utf8'; } @@ -3810,6 +4141,18 @@ { $this->{str} = $this->_si2u2($str); } + elsif($icode eq 'utf8-imode') + { + $this->{str} = $this->_ui2u2($str); + } + elsif($icode eq 'utf8-imode1') + { + $this->{str} = $this->_ui2u1($str); + } + elsif($icode eq 'utf8-imode2') + { + $this->{str} = $this->_ui2u2($str); + } elsif($icode eq 'sjis-doti') { $this->{str} = $this->_sd2u($str); @@ -3937,7 +4280,7 @@ else { use Carp; - croak "icode error [$icode]"; + croak "invalid icode [$icode]"; } $this->{icode} = $icode; } @@ -3987,30 +4330,6 @@ $str; } -sub h2zAlpha { - my $this = shift; - - if(!defined(%_h2zAlpha)) - { - $this->_loadConvTable; - } - - $this->{str} =~ s/(A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)/$_h2zAlpha{$1}/eg; - - $this; -} -sub z2hKanaK { - my $this = shift; - - if(!defined(%_z2hKanaK)) - { - $this->_loadConvTable; - } - - $this->{str} =~ s/(\xe3\x80\x81|\xe3\x80\x82|\xe3\x83\xbb|\xe3\x82\x9b|\xe3\x82\x9c|\xe3\x83\xbc|\xe3\x80\x8c|\xe3\x80\x8d|\xe3\x82\xa1|\xe3\x82\xa2|\xe3\x82\xa3|\xe3\x82\xa4|\xe3\x82\xa5|\xe3\x82\xa6|\xe3\x82\xa7|\xe3\x82\xa8|\xe3\x82\xa9|\xe3\x82\xaa|\xe3\x82\xab|\xe3\x82\xad|\xe3\x82\xaf|\xe3\x82\xb1|\xe3\x82\xb3|\xe3\x82\xb5|\xe3\x82\xb7|\xe3\x82\xb9|\xe3\x82\xbb|\xe3\x82\xbd|\xe3\x82\xbf|\xe3\x83\x81|\xe3\x83\x83|\xe3\x83\x84|\xe3\x83\x86|\xe3\x83\x88|\xe3\x83\x8a|\xe3\x83\x8b|\xe3\x83\x8c|\xe3\x83\x8d|\xe3\x83\x8e|\xe3\x83\x8f|\xe3\x83\x92|\xe3\x83\x95|\xe3\x83\x98|\xe3\x83\x9b|\xe3\x83\x9e|\xe3\x83\x9f|\xe3\x83\xa0|\xe3\x83\xa1|\xe3\x83\xa2|\xe3\x83\xa3|\xe3\x83\xa4|\xe3\x83\xa5|\xe3\x83\xa6|\xe3\x83\xa7|\xe3\x83\xa8|\xe3\x83\xa9|\xe3\x83\xaa|\xe3\x83\xab|\xe3\x83\xac|\xe3\x83\xad|\xe3\x83\xaf|\xe3\x83\xb2|\xe3\x83\xb3)/$_z2hKanaK{$1}/eg; - - $this; -} # ----------------------------------------------------------------------------- # @codelist = Unicode::Japanese->getcodelist($str); # @@ -4324,6 +4643,14 @@ $this; } +# ----------------------------------------------------------------------------- +# $bytes_imode = $unijp->utf8_imode(); +# +sub utf8_imode +{ + my $this = shift; + $this->_u2ui2($this->{str}); +} sub _si2u2 { my $this = shift; my $str = shift; @@ -4596,6 +4923,14 @@ $J2S[unpack('n', $c)] = pack('CC', $c1, $c2); } +# ----------------------------------------------------------------------------- +# $bytes_ucs4 = $unijp->ucs4(); +# +sub ucs4 +{ + my $this = shift; + $this->_utf8_ucs4($this->{str}); +} sub _j2sa2 { my $this = shift; my $esc = shift; @@ -4622,13 +4957,38 @@ $str; } -# ----------------------------------------------------------------------------- -# $bytes_ucs4 = $unijp->ucs4(); -# -sub ucs4 +sub _ui2u1 { my $this = shift; - $this->_utf8_ucs4($this->{str}); + my $str = shift; + + if(!defined($str)) + { + return ''; + } + + if(!defined($ei2u2)) + { + $ei2u1 = $this->_getFile('jcode/emoji2/ei2u.dat'); + } + + $str = $this->_validate_utf8($str); + + # imode : F800-F9FF => U+0FF800 - U+0FF9FF + # E63E - E70B = ee 98 be - ee 9c 8b + # E70C - E757 = ee 9c 8c - ee 9d 97 + $str =~ s{\xee([\x98-\x9e][\x80-\xbf])}{ + my ($in1, $in2) = unpack("CC", $1); + my $in = (($in1 - 0x98) << 6) + ($in2 - 0x80); + my $diff = $in <= 0x9b ? ( 0xfc - 0x9b) + : $in <= 0xda ? (0x17e - 0xda) + : (0x1b0 - 0x10b); + my $sjisoffset = $diff + $in; + my $sjisbin = pack("n", $sjisoffset); + $in<=0x10b ? $S2U{$sjisbin} ||= substr($ei2u1, $sjisoffset * 4, 4) || '?' : '?'; + }xeg; + + $str; } sub _sd2u { my $this = shift; @@ -5004,7 +5364,7 @@ my $res = ""; $eol = "\n" unless defined $eol; - pos($str) = 0; # ensure start at the beginning + pos($str) = 0; # ensure we start matching from the beginning while ($str =~ /(.{1,45})/gs) { $res .= substr(pack('u', $1), 1); @@ -5014,7 +5374,8 @@ # fix padding at the end my $padding = (3 - length($str) % 3) % 3; $res =~ s/.{$padding}$/'=' x $padding/e if $padding; - # break encoded string into lines of no more than 76 characters each + # break encoded string into lines so that each lines have no more than 76 + # characters if (length $eol) { $res =~ s/(.{1,76})/$1$eol/g; @@ -5118,11 +5479,11 @@ if(ref($cutlen)) { - die "String->strcut, Param[1] is Ref.\n"; + die "String#strcut: param[1] is a Ref.\n"; } - if($cutlen =~ m/\D/) + if($cutlen !~ m/^\d+$/) { - die "String->strcut, Param[1] must be NUMERIC.\n"; + die "String#strcut: param[1] must be an integer.\n"; } my $ch_re = '[\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3}|[\xf8-\xfb][\x80-\xbf]{4}|[\xfc-\xfd][\x80-\xbf]{5}'; @@ -5157,7 +5518,7 @@ $str; } -  +   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~\N}L~ƒÃăŃƃǃȃɃʃ˃̃̓΃Ѓу҃ӃԃՃF@ABCDEGHIJKLMNOPQRSTUVWXYZ[\]^_`pqrstuwxyz{|}~v]\aefghdc~TUVWXYZ[\]݁ށ|假aȁɁ@ABCDEFGHIJKLMNOPQRS@ABVXYZqrstuvwxyzkl`‚ÂĂłƂǂȂɂʂ˂̂͂΂ςЂт҂ӂԂՂւׂ؂قڂۂ܂݂ނ߂JKTU@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~E[RSei`cakjdlfn_mbgh~rsopqut꒚O㉺s^NuLےO嘥TvVRh昩T\]܈jiS喒lYmwedtWM߈CNOPˆފꕚx`LQfAʒZCR]ØōƛCј͌gTSVUDNWErWܘۘMݘCoUZq{|[Yl㌑Ϙ`X^\Θ]UT_P×bBXC@AlDaEHFmGI`KJVMNLQPORSDUTWVXYZ[b[ƓeZ\}]cS_`ZaTbc~fegh`ijkdnlmyopq~ustrvwexyyz꣋{}}f~Mʉon؊Ygh䙍홎OUܔ虛nchiw[JNjuEk噫MlmkxnC\ߙڑ싦PmTƉKopɉpΙ~X}qQyFofrbpËْ@ڙ؉‘䎶jEiheg݉D@fNiܙߙz݌CĖuatBv@]PDCiAENFGLKNMJwSOHISBYXOPUR[VWTZQ`ea\fPxhA^b[슅c_igridcmkpjnlkorwutQqsRv}{|~\XxydXdlc͐}ޚy\nVByzR^C_{}|Wu|xꟉgYhUom큚n킕՚ϚҚdښܚӚߚmpsᐺ딄ϚĚ[OǏgVvΚޕt_턖zDz@DA@ܖDJWdBE툑WiF퉍GonKLIWHÕPpQORPNPMVWSKkUXwYT}ZQ[_\ś^]kda`bcefhgildjmnqopqrEs튎tuyFGǛvwwxyz{}~FvG@舶Xq鎺G{Qeh⛃ЖxQ@ǛJːRΎ˛ёqAڐKsAǛ͉rWjwRZx푛㛴풓sRśěÛɛ홗כޛۛBHIțߖbJFsztыAX蕝y햋NK􌶗cHLXM{xNfpLf@CDB_FEAGHILJKMNUOPMQTU|VOo팷WX^휒YJeZK[\]_`abSRc`FʕVjdeefihgamkjlk]ponqrzsOtJSKEuuYZzwyOxv|{|vӜ}}P~pbIxYߜ{fҜySĜz䜷DĜǜ‘ԍQT̜͜dSшԜʜМ׌c|J؜e荧匜^ʜ@ABCYDEF[G绔˝HKILJM}NQZOVPc}RSWTRe❫ZcS]d_fba[YUXS`qg@hminAE\kwlgjUҝp}Jqso{̝~xPv|{uzrt@||̒TyT[wdf͝}~`KghrgETQPdBohi^FC[xUq~ݔsŋǝUhG~ʝ|kl͎ҐώafzV{ѝԗٝڊU|{VՐftGE莞WWNAiqɝgÝb\A@BCjDFGHȉgXIJJ֑]\֍LÞK񒽞LN]MNO{DQpSVURTWǍޑZmXY۞[\aYt^ܝnf`f]cbʎ}ged_ki˞gmsȑuAt^_Mpoqnvljrhč`ɒ̓ȉhIxZz}ji{jy|~ˌKNJjVO~[旜BHǞ_IXoAŞk^ힾžƞ|OyT|PYɞ̍\Ƒl͞ߞW⏾͞~MӞk@ɞՊh@wKGFEBDCIELHJMQNORSTU~WVY\Ԋ\[]V^`_abc~cΗdefgihw}cjlBkmnopqsrtiuEkvaBwxꖈşyz|{~}CXiْ`ړ🇍]rܑDןBv@ݟAgDןjmk^FhlY_Q\CZߏOUtƟҟiWˈ[~䟹ǓYϏŸakЏًnԟ݈Q֑͟ύ`؟XNΓp팹anMHBYRAQ@NIRKHkEDMGFLCOPUTVYbSWQZX][^aZG\`_JdhfbcgemmjilnoqprsD܍FutxY{vzy_F}G~|wBRFonMRzWCݕઑu୕Дஔv௉Sq]@_ƋĒKTA͒LPQωblCDEXHFG]J䗝IKMLNԋՔilOPQZ@ZABCDFGErIHRKJLMONQPr[RYSpT팓cRb\jUV[YXEW\Z{L^l_]`aSfcbEidehgDa`^jklnmuvprt]usoqaxwyz|{ᅒs}~oᢔSTᤓIFcHWUVXM᱔u~mv᳓XᵖĔἔŌ^Z̖ruߖmZ⋸\uԋmCjv{]^d_VOqmA@CBDbFEGIH`JV_FSPOcLNj_MKI[QRh\TSВdfTUWXHYZ[׉ѓÏG\Hȕb]d`a`^_HbcBdetgfiljҌmkemsoωnnpqrntuvލwy{xzA|Eq~M}↗g⏏vhGj[^|J}y⤕M➒}͉Zk\⽕zUєӗԐ̌HeSl㊟WfnI@gC[RBэhAfaFݍGaIЍHIgDJmEoMQLUniROPNKGWTVSpXepa[_Zbfj\odY]^]ٔΏqgchjmiҊlknuovrtqwpcDks{~|z`}x@qJrDUyJ[@㚓Zs㫍ߌrul㭜rt㸌QA`HK|sVl͎̎k^ޒEW攣]IҎbmnx_wEE\ƘerE]BAtDCorTHIGFJBNOKLMpUQGPSRcVWVXZ^[Y^\]d_`acbefgbhLvijPklmnopqrs܊CwMtquwǔvDxzy|{}~䅐FH䎔mcF|䒗c꒗pv䗉֊s䟒t`rwx䮔yep،ԕHz“ĖGʈӗ{tⓟבKߕNf|葓~uWꖪDH@U@ԎB}C~nJPQDNFHRGKLOEEIFdOVTmSUWX[YZM\a`Abh]_^PAdcefgsi|jklqrm\naopztwsuvx`ua{^|}~gIwXIZIayOsȏpXqtˈ\΋UٗT琻JAA@CBDPEFGvHeIJKK`LoMONePQRSTUVpWXYGZ[\]vu`_P^LabcKidfehigٕ]frmwllkFlbYjopn_FsaUvrwtuqNbzxkyz_{懒~|@}慏dy抍u揗w擕T朕x桋c㿏]QJL涕^eLvnݔÊѐǒƋMȔ\fʘGdڑGo͎^qЍwԑӊqNzHHxH@DABCJEGIFLRKMNQPOSRUTVWYXgZ[]^_\`aORb]cfedygrihqkmjlpnPorySsAutx`wv{zyQ|}~Dh燒CJ_ӒҍHIv}犉猔R獏qޑ琋t磓r瘐痑畈ATiNِx礗V^Չ碓Bky穓K竑JIℊWM@xYSsXsAUޔz|ǗVy_X΍юחd؋B܊jt݊bnSzgeCLKNseI|K@BACdB^EDFBtKbGHLJIOZMNLPVYXLQRUWZTS^_`]\[dbcaefhgsiljkmopqtruwvxMyzJ[̊{|}~֊t}{hj虍~蚌@wA袒˓蜗zG@褊Ku襌ۏB讗ǔYW貎GJᎴ_뗋d軐kIP֐זrҊvxCfB쉹CŒ{aГzjopz{猰؊^@BACDEFHGIHQJKZOLM{a`NOPRSUQT܊VWXYZ\[^a]_`bcde]nfgyhʉwmljkiwnopqsrxtvRuxyz}|~{[ETS@鮖D鸕LNI~ӊkhوˉVߒLPDCEL@ABQJFKHG{LMNIOSTRQWPUVYX[\]hZ^_`abcdefghki[jlmnpqo˖sotuv썕wҖxzy{|}~Cl@Vꔗss~BYabceiluIij{C|D^OPQRSTUVWXFGH`abcdefghijklmnopqrstuvwxym_nOQMobp`ʁP。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙゚???????????????????????????????????????????????????????????????? 、。,.・:;?!゛゜´`¨^ ̄_ヽヾゝゞ〃仝々〆〇ー―‐/\~∥|…‥‘’“”()〔〕[]{}〈〉《》「」『』【】+-±×?÷=≠<>≦≧∞∴♂♀°′″℃¥$¢£%#&*@§☆★○●◎◇◆□■△▲▽▼※〒→←↑↓〓???????????∈∋⊆⊇⊂⊃∪∩????????∧∨¬⇒⇔∀∃???????????∠⊥⌒∂∇≡≒≪≫√∽∝∵∫∬???????ʼn♯♭♪†‡¶????◯??????????????????????????????????????????????????????????????????????????????????0123456789???????ABCDEFGHIJKLMNOPQRSTUVWXYZ???????abcdefghijklmnopqrstuvwxyz????ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをん??????????????????????????????????????????????????????????????????????????????ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミ?ムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶ????????ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ????????αβγδεζηθικλμνξοπρστυφχψω?????????????????????????????????????????????????????????????????????????????????????????????????????????АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ???????????????абвгдеёжзийклмн?опрстуфхцчшщъыьэюя?????????????─│┌┐┘└├┬┤┴┼━┃┏┓┛┗┣┳┫┻╋┠┯┨┷┿┝┰┥┸╂?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ?㍉㌔㌢㍍㌘㌧㌃㌶㍑㍗㌍㌦㌣㌫㍊㌻㎜㎝㎞㎎㎏㏄㎡????????㍻?〝〟№㏍℡㊤㊥㊦㊧㊨㈱㈲㈹㍾㍽㍼≒≡∫∮∑√⊥∠∟⊿∵∩∪??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????亜唖娃阿哀愛挨姶逢葵茜穐悪握渥旭葦芦鯵梓圧斡扱宛姐虻飴絢綾鮎或粟袷安庵按暗案闇鞍杏以伊位依偉囲夷委威尉惟意慰易椅為畏異移維緯胃萎衣謂違遺医井亥域育郁磯一壱溢逸稲茨芋鰯允印咽員因姻引飲淫胤蔭???????????????????????????????????????????????????????????????????院陰隠韻吋右宇烏羽迂雨卯鵜窺丑碓臼渦嘘唄欝蔚鰻姥厩浦瓜閏噂云運雲荏餌叡営嬰影映曳栄永泳洩瑛盈穎頴英衛詠鋭液疫益駅悦謁越閲榎厭円?園堰奄宴延怨掩援沿演炎焔煙燕猿縁艶苑薗遠鉛鴛塩於汚甥凹央奥往応押旺横欧殴王翁襖鴬鴎黄岡沖荻億屋憶臆桶牡乙俺卸恩温穏音下化仮何伽価佳加可嘉夏嫁家寡科暇果架歌河火珂禍禾稼箇花苛茄荷華菓蝦課嘩貨迦過霞蚊俄峨我牙画臥芽蛾賀雅餓駕介会解回塊壊廻快怪悔恢懐戒拐改???????????????????????????????????????????????????????????????????魁晦械海灰界皆絵芥蟹開階貝凱劾外咳害崖慨概涯碍蓋街該鎧骸浬馨蛙垣柿蛎鈎劃嚇各廓拡撹格核殻獲確穫覚角赫較郭閣隔革学岳楽額顎掛笠樫?橿梶鰍潟割喝恰括活渇滑葛褐轄且鰹叶椛樺鞄株兜竃蒲釜鎌噛鴨栢茅萱粥刈苅瓦乾侃冠寒刊勘勧巻喚堪姦完官寛干幹患感慣憾換敢柑桓棺款歓汗漢澗潅環甘監看竿管簡緩缶翰肝艦莞観諌貫還鑑間閑関陥韓館舘丸含岸巌玩癌眼岩翫贋雁頑顔願企伎危喜器基奇嬉寄岐希幾忌揮机旗既期棋棄???????????????????????????????????????????????????????????????????機帰毅気汽畿祈季稀紀徽規記貴起軌輝飢騎鬼亀偽儀妓宜戯技擬欺犠疑祇義蟻誼議掬菊鞠吉吃喫桔橘詰砧杵黍却客脚虐逆丘久仇休及吸宮弓急救?朽求汲泣灸球究窮笈級糾給旧牛去居巨拒拠挙渠虚許距鋸漁禦魚亨享京供侠僑兇競共凶協匡卿叫喬境峡強彊怯恐恭挟教橋況狂狭矯胸脅興蕎郷鏡響饗驚仰凝尭暁業局曲極玉桐粁僅勤均巾錦斤欣欽琴禁禽筋緊芹菌衿襟謹近金吟銀九倶句区狗玖矩苦躯駆駈駒具愚虞喰空偶寓遇隅串櫛釧屑屈???????????????????????????????????????????????????????????????????掘窟沓靴轡窪熊隈粂栗繰桑鍬勲君薫訓群軍郡卦袈祁係傾刑兄啓圭珪型契形径恵慶慧憩掲携敬景桂渓畦稽系経継繋罫茎荊蛍計詣警軽頚鶏芸迎鯨?劇戟撃激隙桁傑欠決潔穴結血訣月件倹倦健兼券剣喧圏堅嫌建憲懸拳捲検権牽犬献研硯絹県肩見謙賢軒遣鍵険顕験鹸元原厳幻弦減源玄現絃舷言諺限乎個古呼固姑孤己庫弧戸故枯湖狐糊袴股胡菰虎誇跨鈷雇顧鼓五互伍午呉吾娯後御悟梧檎瑚碁語誤護醐乞鯉交佼侯候倖光公功効勾厚口向???????????????????????????????????????????????????????????????????后喉坑垢好孔孝宏工巧巷幸広庚康弘恒慌抗拘控攻昂晃更杭校梗構江洪浩港溝甲皇硬稿糠紅紘絞綱耕考肯肱腔膏航荒行衡講貢購郊酵鉱砿鋼閤降?項香高鴻剛劫号合壕拷濠豪轟麹克刻告国穀酷鵠黒獄漉腰甑忽惚骨狛込此頃今困坤墾婚恨懇昏昆根梱混痕紺艮魂些佐叉唆嵯左差査沙瑳砂詐鎖裟坐座挫債催再最哉塞妻宰彩才採栽歳済災采犀砕砦祭斎細菜裁載際剤在材罪財冴坂阪堺榊肴咲崎埼碕鷺作削咋搾昨朔柵窄策索錯桜鮭笹匙冊刷???????????????????????????????????????????????????????????????????察拶撮擦札殺薩雑皐鯖捌錆鮫皿晒三傘参山惨撒散桟燦珊産算纂蚕讃賛酸餐斬暫残仕仔伺使刺司史嗣四士始姉姿子屍市師志思指支孜斯施旨枝止?死氏獅祉私糸紙紫肢脂至視詞詩試誌諮資賜雌飼歯事似侍児字寺慈持時次滋治爾璽痔磁示而耳自蒔辞汐鹿式識鴫竺軸宍雫七叱執失嫉室悉湿漆疾質実蔀篠偲柴芝屡蕊縞舎写射捨赦斜煮社紗者謝車遮蛇邪借勺尺杓灼爵酌釈錫若寂弱惹主取守手朱殊狩珠種腫趣酒首儒受呪寿授樹綬需囚収周???????????????????????????????????????????????????????????????????宗就州修愁拾洲秀秋終繍習臭舟蒐衆襲讐蹴輯週酋酬集醜什住充十従戎柔汁渋獣縦重銃叔夙宿淑祝縮粛塾熟出術述俊峻春瞬竣舜駿准循旬楯殉淳?準潤盾純巡遵醇順処初所暑曙渚庶緒署書薯藷諸助叙女序徐恕鋤除傷償勝匠升召哨商唱嘗奨妾娼宵将小少尚庄床廠彰承抄招掌捷昇昌昭晶松梢樟樵沼消渉湘焼焦照症省硝礁祥称章笑粧紹肖菖蒋蕉衝裳訟証詔詳象賞醤鉦鍾鐘障鞘上丈丞乗冗剰城場壌嬢常情擾条杖浄状畳穣蒸譲醸錠嘱埴飾???????????????????????????????????????????????????????????????????拭植殖燭織職色触食蝕辱尻伸信侵唇娠寝審心慎振新晋森榛浸深申疹真神秦紳臣芯薪親診身辛進針震人仁刃塵壬尋甚尽腎訊迅陣靭笥諏須酢図厨?逗吹垂帥推水炊睡粋翠衰遂酔錐錘随瑞髄崇嵩数枢趨雛据杉椙菅頗雀裾澄摺寸世瀬畝是凄制勢姓征性成政整星晴棲栖正清牲生盛精聖声製西誠誓請逝醒青静斉税脆隻席惜戚斥昔析石積籍績脊責赤跡蹟碩切拙接摂折設窃節説雪絶舌蝉仙先千占宣専尖川戦扇撰栓栴泉浅洗染潜煎煽旋穿箭線???????????????????????????????????????????????????????????????????繊羨腺舛船薦詮賎践選遷銭銑閃鮮前善漸然全禅繕膳糎噌塑岨措曾曽楚狙疏疎礎祖租粗素組蘇訴阻遡鼠僧創双叢倉喪壮奏爽宋層匝惣想捜掃挿掻?操早曹巣槍槽漕燥争痩相窓糟総綜聡草荘葬蒼藻装走送遭鎗霜騒像増憎臓蔵贈造促側則即息捉束測足速俗属賊族続卒袖其揃存孫尊損村遜他多太汰詑唾堕妥惰打柁舵楕陀駄騨体堆対耐岱帯待怠態戴替泰滞胎腿苔袋貸退逮隊黛鯛代台大第醍題鷹滝瀧卓啄宅托択拓沢濯琢託鐸濁諾茸凧蛸只???????????????????????????????????????????????????????????????????叩但達辰奪脱巽竪辿棚谷狸鱈樽誰丹単嘆坦担探旦歎淡湛炭短端箪綻耽胆蛋誕鍛団壇弾断暖檀段男談値知地弛恥智池痴稚置致蜘遅馳築畜竹筑蓄?逐秩窒茶嫡着中仲宙忠抽昼柱注虫衷註酎鋳駐樗瀦猪苧著貯丁兆凋喋寵帖帳庁弔張彫徴懲挑暢朝潮牒町眺聴脹腸蝶調諜超跳銚長頂鳥勅捗直朕沈珍賃鎮陳津墜椎槌追鎚痛通塚栂掴槻佃漬柘辻蔦綴鍔椿潰坪壷嬬紬爪吊釣鶴亭低停偵剃貞呈堤定帝底庭廷弟悌抵挺提梯汀碇禎程締艇訂諦蹄逓???????????????????????????????????????????????????????????????????邸鄭釘鼎泥摘擢敵滴的笛適鏑溺哲徹撤轍迭鉄典填天展店添纏甜貼転顛点伝殿澱田電兎吐堵塗妬屠徒斗杜渡登菟賭途都鍍砥砺努度土奴怒倒党冬?凍刀唐塔塘套宕島嶋悼投搭東桃梼棟盗淘湯涛灯燈当痘祷等答筒糖統到董蕩藤討謄豆踏逃透鐙陶頭騰闘働動同堂導憧撞洞瞳童胴萄道銅峠鴇匿得徳涜特督禿篤毒独読栃橡凸突椴届鳶苫寅酉瀞噸屯惇敦沌豚遁頓呑曇鈍奈那内乍凪薙謎灘捺鍋楢馴縄畷南楠軟難汝二尼弐迩匂賑肉虹廿日乳入???????????????????????????????????????????????????????????????????如尿韮任妊忍認濡禰祢寧葱猫熱年念捻撚燃粘乃廼之埜嚢悩濃納能脳膿農覗蚤巴把播覇杷波派琶破婆罵芭馬俳廃拝排敗杯盃牌背肺輩配倍培媒梅?楳煤狽買売賠陪這蝿秤矧萩伯剥博拍柏泊白箔粕舶薄迫曝漠爆縛莫駁麦函箱硲箸肇筈櫨幡肌畑畠八鉢溌発醗髪伐罰抜筏閥鳩噺塙蛤隼伴判半反叛帆搬斑板氾汎版犯班畔繁般藩販範釆煩頒飯挽晩番盤磐蕃蛮匪卑否妃庇彼悲扉批披斐比泌疲皮碑秘緋罷肥被誹費避非飛樋簸備尾微枇毘琵眉美???????????????????????????????????????????????????????????????????鼻柊稗匹疋髭彦膝菱肘弼必畢筆逼桧姫媛紐百謬俵彪標氷漂瓢票表評豹廟描病秒苗錨鋲蒜蛭鰭品彬斌浜瀕貧賓頻敏瓶不付埠夫婦富冨布府怖扶敷?斧普浮父符腐膚芙譜負賦赴阜附侮撫武舞葡蕪部封楓風葺蕗伏副復幅服福腹複覆淵弗払沸仏物鮒分吻噴墳憤扮焚奮粉糞紛雰文聞丙併兵塀幣平弊柄並蔽閉陛米頁僻壁癖碧別瞥蔑箆偏変片篇編辺返遍便勉娩弁鞭保舗鋪圃捕歩甫補輔穂募墓慕戊暮母簿菩倣俸包呆報奉宝峰峯崩庖抱捧放方朋???????????????????????????????????????????????????????????????????法泡烹砲縫胞芳萌蓬蜂褒訪豊邦鋒飽鳳鵬乏亡傍剖坊妨帽忘忙房暴望某棒冒紡肪膨謀貌貿鉾防吠頬北僕卜墨撲朴牧睦穆釦勃没殆堀幌奔本翻凡盆?摩磨魔麻埋妹昧枚毎哩槙幕膜枕鮪柾鱒桝亦俣又抹末沫迄侭繭麿万慢満漫蔓味未魅巳箕岬密蜜湊蓑稔脈妙粍民眠務夢無牟矛霧鵡椋婿娘冥名命明盟迷銘鳴姪牝滅免棉綿緬面麺摸模茂妄孟毛猛盲網耗蒙儲木黙目杢勿餅尤戻籾貰問悶紋門匁也冶夜爺耶野弥矢厄役約薬訳躍靖柳薮鑓愉愈油癒???????????????????????????????????????????????????????????????????諭輸唯佑優勇友宥幽悠憂揖有柚湧涌猶猷由祐裕誘遊邑郵雄融夕予余与誉輿預傭幼妖容庸揚揺擁曜楊様洋溶熔用窯羊耀葉蓉要謡踊遥陽養慾抑欲?沃浴翌翼淀羅螺裸来莱頼雷洛絡落酪乱卵嵐欄濫藍蘭覧利吏履李梨理璃痢裏裡里離陸律率立葎掠略劉流溜琉留硫粒隆竜龍侶慮旅虜了亮僚両凌寮料梁涼猟療瞭稜糧良諒遼量陵領力緑倫厘林淋燐琳臨輪隣鱗麟瑠塁涙累類令伶例冷励嶺怜玲礼苓鈴隷零霊麗齢暦歴列劣烈裂廉恋憐漣煉簾練聯???????????????????????????????????????????????????????????????????蓮連錬呂魯櫓炉賂路露労婁廊弄朗楼榔浪漏牢狼篭老聾蝋郎六麓禄肋録論倭和話歪賄脇惑枠鷲亙亘鰐詫藁蕨椀湾碗腕????????????????????????????????????????????弌丐丕个丱丶丼丿乂乖乘亂亅豫亊舒弍于亞亟亠亢亰亳亶从仍仄仆仂仗仞仭仟价伉佚估佛佝佗佇佶侈侏侘佻佩佰侑佯來侖儘俔俟俎俘俛俑俚俐俤俥倚倨倔倪倥倅伜俶倡倩倬俾俯們倆偃假會偕偐偈做偖偬偸傀傚傅傴傲???????????????????????????????????????????????????????????????????僉僊傳僂僖僞僥僭僣僮價僵儉儁儂儖儕儔儚儡儺儷儼儻儿兀兒兌兔兢竸兩兪兮冀冂囘册冉冏冑冓冕冖冤冦冢冩冪冫决冱冲冰况冽凅凉凛几處凩凭?凰凵凾刄刋刔刎刧刪刮刳刹剏剄剋剌剞剔剪剴剩剳剿剽劍劔劒剱劈劑辨辧劬劭劼劵勁勍勗勞勣勦飭勠勳勵勸勹匆匈甸匍匐匏匕匚匣匯匱匳匸區卆卅丗卉卍凖卞卩卮夘卻卷厂厖厠厦厥厮厰厶參簒雙叟曼燮叮叨叭叺吁吽呀听吭吼吮吶吩吝呎咏呵咎呟呱呷呰咒呻咀呶咄咐咆哇咢咸咥咬哄哈咨???????????????????????????????????????????????????????????????????咫哂咤咾咼哘哥哦唏唔哽哮哭哺哢唹啀啣啌售啜啅啖啗唸唳啝喙喀咯喊喟啻啾喘喞單啼喃喩喇喨嗚嗅嗟嗄嗜嗤嗔嘔嗷嘖嗾嗽嘛嗹噎噐營嘴嘶嘲嘸?噫噤嘯噬噪嚆嚀嚊嚠嚔嚏嚥嚮嚶嚴囂嚼囁囃囀囈囎囑囓囗囮囹圀囿圄圉圈國圍圓團圖嗇圜圦圷圸坎圻址坏坩埀垈坡坿垉垓垠垳垤垪垰埃埆埔埒埓堊埖埣堋堙堝塲堡塢塋塰毀塒堽塹墅墹墟墫墺壞墻墸墮壅壓壑壗壙壘壥壜壤壟壯壺壹壻壼壽夂夊夐夛梦夥夬夭夲夸夾竒奕奐奎奚奘奢奠奧奬奩???????????????????????????????????????????????????????????????????奸妁妝佞侫妣妲姆姨姜妍姙姚娥娟娑娜娉娚婀婬婉娵娶婢婪媚媼媾嫋嫂媽嫣嫗嫦嫩嫖嫺嫻嬌嬋嬖嬲嫐嬪嬶嬾孃孅孀孑孕孚孛孥孩孰孳孵學斈孺宀?它宦宸寃寇寉寔寐寤實寢寞寥寫寰寶寳尅將專對尓尠尢尨尸尹屁屆屎屓屐屏孱屬屮乢屶屹岌岑岔妛岫岻岶岼岷峅岾峇峙峩峽峺峭嶌峪崋崕崗嵜崟崛崑崔崢崚崙崘嵌嵒嵎嵋嵬嵳嵶嶇嶄嶂嶢嶝嶬嶮嶽嶐嶷嶼巉巍巓巒巖巛巫已巵帋帚帙帑帛帶帷幄幃幀幎幗幔幟幢幤幇幵并幺麼广庠廁廂廈廐廏???????????????????????????????????????????????????????????????????廖廣廝廚廛廢廡廨廩廬廱廳廰廴廸廾弃弉彝彜弋弑弖弩弭弸彁彈彌彎弯彑彖彗彙彡彭彳彷徃徂彿徊很徑徇從徙徘徠徨徭徼忖忻忤忸忱忝悳忿怡恠?怙怐怩怎怱怛怕怫怦怏怺恚恁恪恷恟恊恆恍恣恃恤恂恬恫恙悁悍惧悃悚悄悛悖悗悒悧悋惡悸惠惓悴忰悽惆悵惘慍愕愆惶惷愀惴惺愃愡惻惱愍愎慇愾愨愧慊愿愼愬愴愽慂慄慳慷慘慙慚慫慴慯慥慱慟慝慓慵憙憖憇憬憔憚憊憑憫憮懌懊應懷懈懃懆憺懋罹懍懦懣懶懺懴懿懽懼懾戀戈戉戍戌戔戛???????????????????????????????????????????????????????????????????戞戡截戮戰戲戳扁扎扞扣扛扠扨扼抂抉找抒抓抖拔抃抔拗拑抻拏拿拆擔拈拜拌拊拂拇抛拉挌拮拱挧挂挈拯拵捐挾捍搜捏掖掎掀掫捶掣掏掉掟掵捫?捩掾揩揀揆揣揉插揶揄搖搴搆搓搦搶攝搗搨搏摧摯摶摎攪撕撓撥撩撈撼據擒擅擇撻擘擂擱擧舉擠擡抬擣擯攬擶擴擲擺攀擽攘攜攅攤攣攫攴攵攷收攸畋效敖敕敍敘敞敝敲數斂斃變斛斟斫斷旃旆旁旄旌旒旛旙无旡旱杲昊昃旻杳昵昶昴昜晏晄晉晁晞晝晤晧晨晟晢晰暃暈暎暉暄暘暝曁暹曉暾暼???????????????????????????????????????????????????????????????????曄暸曖曚曠昿曦曩曰曵曷朏朖朞朦朧霸朮朿朶杁朸朷杆杞杠杙杣杤枉杰枩杼杪枌枋枦枡枅枷柯枴柬枳柩枸柤柞柝柢柮枹柎柆柧檜栞框栩桀桍栲桎?梳栫桙档桷桿梟梏梭梔條梛梃檮梹桴梵梠梺椏梍桾椁棊椈棘椢椦棡椌棍棔棧棕椶椒椄棗棣椥棹棠棯椨椪椚椣椡棆楹楷楜楸楫楔楾楮椹楴椽楙椰楡楞楝榁楪榲榮槐榿槁槓榾槎寨槊槝榻槃榧樮榑榠榜榕榴槞槨樂樛槿權槹槲槧樅榱樞槭樔槫樊樒櫁樣樓橄樌橲樶橸橇橢橙橦橈樸樢檐檍檠檄檢檣???????????????????????????????????????????????????????????????????檗蘗檻櫃櫂檸檳檬櫞櫑櫟檪櫚櫪櫻欅蘖櫺欒欖鬱欟欸欷盜欹飮歇歃歉歐歙歔歛歟歡歸歹歿殀殄殃殍殘殕殞殤殪殫殯殲殱殳殷殼毆毋毓毟毬毫毳毯?麾氈氓气氛氤氣汞汕汢汪沂沍沚沁沛汾汨汳沒沐泄泱泓沽泗泅泝沮沱沾沺泛泯泙泪洟衍洶洫洽洸洙洵洳洒洌浣涓浤浚浹浙涎涕濤涅淹渕渊涵淇淦涸淆淬淞淌淨淒淅淺淙淤淕淪淮渭湮渮渙湲湟渾渣湫渫湶湍渟湃渺湎渤滿渝游溂溪溘滉溷滓溽溯滄溲滔滕溏溥滂溟潁漑灌滬滸滾漿滲漱滯漲滌???????????????????????????????????????????????????????????????????漾漓滷澆潺潸澁澀潯潛濳潭澂潼潘澎澑濂潦澳澣澡澤澹濆澪濟濕濬濔濘濱濮濛瀉瀋濺瀑瀁瀏濾瀛瀚潴瀝瀘瀟瀰瀾瀲灑灣炙炒炯烱炬炸炳炮烟烋烝?烙焉烽焜焙煥煕熈煦煢煌煖煬熏燻熄熕熨熬燗熹熾燒燉燔燎燠燬燧燵燼燹燿爍爐爛爨爭爬爰爲爻爼爿牀牆牋牘牴牾犂犁犇犒犖犢犧犹犲狃狆狄狎狒狢狠狡狹狷倏猗猊猜猖猝猴猯猩猥猾獎獏默獗獪獨獰獸獵獻獺珈玳珎玻珀珥珮珞璢琅瑯琥珸琲琺瑕琿瑟瑙瑁瑜瑩瑰瑣瑪瑶瑾璋璞璧瓊瓏瓔珱???????????????????????????????????????????????????????????????????瓠瓣瓧瓩瓮瓲瓰瓱瓸瓷甄甃甅甌甎甍甕甓甞甦甬甼畄畍畊畉畛畆畚畩畤畧畫畭畸當疆疇畴疊疉疂疔疚疝疥疣痂疳痃疵疽疸疼疱痍痊痒痙痣痞痾痿?痼瘁痰痺痲痳瘋瘍瘉瘟瘧瘠瘡瘢瘤瘴瘰瘻癇癈癆癜癘癡癢癨癩癪癧癬癰癲癶癸發皀皃皈皋皎皖皓皙皚皰皴皸皹皺盂盍盖盒盞盡盥盧盪蘯盻眈眇眄眩眤眞眥眦眛眷眸睇睚睨睫睛睥睿睾睹瞎瞋瞑瞠瞞瞰瞶瞹瞿瞼瞽瞻矇矍矗矚矜矣矮矼砌砒礦砠礪硅碎硴碆硼碚碌碣碵碪碯磑磆磋磔碾碼磅磊磬???????????????????????????????????????????????????????????????????磧磚磽磴礇礒礑礙礬礫祀祠祗祟祚祕祓祺祿禊禝禧齋禪禮禳禹禺秉秕秧秬秡秣稈稍稘稙稠稟禀稱稻稾稷穃穗穉穡穢穩龝穰穹穽窈窗窕窘窖窩竈窰?窶竅竄窿邃竇竊竍竏竕竓站竚竝竡竢竦竭竰笂笏笊笆笳笘笙笞笵笨笶筐筺笄筍笋筌筅筵筥筴筧筰筱筬筮箝箘箟箍箜箚箋箒箏筝箙篋篁篌篏箴篆篝篩簑簔篦篥籠簀簇簓篳篷簗簍篶簣簧簪簟簷簫簽籌籃籔籏籀籐籘籟籤籖籥籬籵粃粐粤粭粢粫粡粨粳粲粱粮粹粽糀糅糂糘糒糜糢鬻糯糲糴糶糺紆???????????????????????????????????????????????????????????????????紂紜紕紊絅絋紮紲紿紵絆絳絖絎絲絨絮絏絣經綉絛綏絽綛綺綮綣綵緇綽綫總綢綯緜綸綟綰緘緝緤緞緻緲緡縅縊縣縡縒縱縟縉縋縢繆繦縻縵縹繃縷?縲縺繧繝繖繞繙繚繹繪繩繼繻纃緕繽辮繿纈纉續纒纐纓纔纖纎纛纜缸缺罅罌罍罎罐网罕罔罘罟罠罨罩罧罸羂羆羃羈羇羌羔羞羝羚羣羯羲羹羮羶羸譱翅翆翊翕翔翡翦翩翳翹飜耆耄耋耒耘耙耜耡耨耿耻聊聆聒聘聚聟聢聨聳聲聰聶聹聽聿肄肆肅肛肓肚肭冐肬胛胥胙胝胄胚胖脉胯胱脛脩脣脯腋???????????????????????????????????????????????????????????????????隋腆脾腓腑胼腱腮腥腦腴膃膈膊膀膂膠膕膤膣腟膓膩膰膵膾膸膽臀臂膺臉臍臑臙臘臈臚臟臠臧臺臻臾舁舂舅與舊舍舐舖舩舫舸舳艀艙艘艝艚艟艤?艢艨艪艫舮艱艷艸艾芍芒芫芟芻芬苡苣苟苒苴苳苺莓范苻苹苞茆苜茉苙茵茴茖茲茱荀茹荐荅茯茫茗茘莅莚莪莟莢莖茣莎莇莊荼莵荳荵莠莉莨菴萓菫菎菽萃菘萋菁菷萇菠菲萍萢萠莽萸蔆菻葭萪萼蕚蒄葷葫蒭葮蒂葩葆萬葯葹萵蓊葢蒹蒿蒟蓙蓍蒻蓚蓐蓁蓆蓖蒡蔡蓿蓴蔗蔘蔬蔟蔕蔔蓼蕀蕣蕘蕈???????????????????????????????????????????????????????????????????蕁蘂蕋蕕薀薤薈薑薊薨蕭薔薛藪薇薜蕷蕾薐藉薺藏薹藐藕藝藥藜藹蘊蘓蘋藾藺蘆蘢蘚蘰蘿虍乕虔號虧虱蚓蚣蚩蚪蚋蚌蚶蚯蛄蛆蚰蛉蠣蚫蛔蛞蛩蛬?蛟蛛蛯蜒蜆蜈蜀蜃蛻蜑蜉蜍蛹蜊蜴蜿蜷蜻蜥蜩蜚蝠蝟蝸蝌蝎蝴蝗蝨蝮蝙蝓蝣蝪蠅螢螟螂螯蟋螽蟀蟐雖螫蟄螳蟇蟆螻蟯蟲蟠蠏蠍蟾蟶蟷蠎蟒蠑蠖蠕蠢蠡蠱蠶蠹蠧蠻衄衂衒衙衞衢衫袁衾袞衵衽袵衲袂袗袒袮袙袢袍袤袰袿袱裃裄裔裘裙裝裹褂裼裴裨裲褄褌褊褓襃褞褥褪褫襁襄褻褶褸襌褝襠襞???????????????????????????????????????????????????????????????????襦襤襭襪襯襴襷襾覃覈覊覓覘覡覩覦覬覯覲覺覽覿觀觚觜觝觧觴觸訃訖訐訌訛訝訥訶詁詛詒詆詈詼詭詬詢誅誂誄誨誡誑誥誦誚誣諄諍諂諚諫諳諧?諤諱謔諠諢諷諞諛謌謇謚諡謖謐謗謠謳鞫謦謫謾謨譁譌譏譎證譖譛譚譫譟譬譯譴譽讀讌讎讒讓讖讙讚谺豁谿豈豌豎豐豕豢豬豸豺貂貉貅貊貍貎貔豼貘戝貭貪貽貲貳貮貶賈賁賤賣賚賽賺賻贄贅贊贇贏贍贐齎贓賍贔贖赧赭赱赳趁趙跂趾趺跏跚跖跌跛跋跪跫跟跣跼踈踉跿踝踞踐踟蹂踵踰踴蹊???????????????????????????????????????????????????????????????????蹇蹉蹌蹐蹈蹙蹤蹠踪蹣蹕蹶蹲蹼躁躇躅躄躋躊躓躑躔躙躪躡躬躰軆躱躾軅軈軋軛軣軼軻軫軾輊輅輕輒輙輓輜輟輛輌輦輳輻輹轅轂輾轌轉轆轎轗轜?轢轣轤辜辟辣辭辯辷迚迥迢迪迯邇迴逅迹迺逑逕逡逍逞逖逋逧逶逵逹迸遏遐遑遒逎遉逾遖遘遞遨遯遶隨遲邂遽邁邀邊邉邏邨邯邱邵郢郤扈郛鄂鄒鄙鄲鄰酊酖酘酣酥酩酳酲醋醉醂醢醫醯醪醵醴醺釀釁釉釋釐釖釟釡釛釼釵釶鈞釿鈔鈬鈕鈑鉞鉗鉅鉉鉤鉈銕鈿鉋鉐銜銖銓銛鉚鋏銹銷鋩錏鋺鍄錮???????????????????????????????????????????????????????????????????錙錢錚錣錺錵錻鍜鍠鍼鍮鍖鎰鎬鎭鎔鎹鏖鏗鏨鏥鏘鏃鏝鏐鏈鏤鐚鐔鐓鐃鐇鐐鐶鐫鐵鐡鐺鑁鑒鑄鑛鑠鑢鑞鑪鈩鑰鑵鑷鑽鑚鑼鑾钁鑿閂閇閊閔閖閘閙?閠閨閧閭閼閻閹閾闊濶闃闍闌闕闔闖關闡闥闢阡阨阮阯陂陌陏陋陷陜陞陝陟陦陲陬隍隘隕隗險隧隱隲隰隴隶隸隹雎雋雉雍襍雜霍雕雹霄霆霈霓霎霑霏霖霙霤霪霰霹霽霾靄靆靈靂靉靜靠靤靦靨勒靫靱靹鞅靼鞁靺鞆鞋鞏鞐鞜鞨鞦鞣鞳鞴韃韆韈韋韜韭齏韲竟韶韵頏頌頸頤頡頷頽顆顏顋顫顯顰???????????????????????????????????????????????????????????????????顱顴顳颪颯颱颶飄飃飆飩飫餃餉餒餔餘餡餝餞餤餠餬餮餽餾饂饉饅饐饋饑饒饌饕馗馘馥馭馮馼駟駛駝駘駑駭駮駱駲駻駸騁騏騅駢騙騫騷驅驂驀驃?騾驕驍驛驗驟驢驥驤驩驫驪骭骰骼髀髏髑髓體髞髟髢髣髦髯髫髮髴髱髷髻鬆鬘鬚鬟鬢鬣鬥鬧鬨鬩鬪鬮鬯鬲魄魃魏魍魎魑魘魴鮓鮃鮑鮖鮗鮟鮠鮨鮴鯀鯊鮹鯆鯏鯑鯒鯣鯢鯤鯔鯡鰺鯲鯱鯰鰕鰔鰉鰓鰌鰆鰈鰒鰊鰄鰮鰛鰥鰤鰡鰰鱇鰲鱆鰾鱚鱠鱧鱶鱸鳧鳬鳰鴉鴈鳫鴃鴆鴪鴦鶯鴣鴟鵄鴕鴒鵁鴿鴾鵆鵈???????????????????????????????????????????????????????????????????鵝鵞鵤鵑鵐鵙鵲鶉鶇鶫鵯鵺鶚鶤鶩鶲鷄鷁鶻鶸鶺鷆鷏鷂鷙鷓鷸鷦鷭鷯鷽鸚鸛鸞鹵鹹鹽麁麈麋麌麒麕麑麝麥麩麸麪麭靡黌黎黏黐黔黜點黝黠黥黨黯?黴黶黷黹黻黼黽鼇鼈皷鼕鼡鼬鼾齊齒齔齣齟齠齡齦齧齬齪齷齲齶龕龜龠堯槇遙瑤凜熙???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????纊褜鍈銈蓜俉炻昱棈鋹曻彅丨仡仼伀伃伹佖侒侊侚侔俍偀倢俿倞偆偰偂傔僴僘兊兤冝冾凬刕劜劦勀勛匀匇匤卲厓厲叝﨎咜咊咩哿喆坙坥垬埈埇﨏?塚增墲夋奓奛奝奣妤妺孖寀甯寘寬尞岦岺峵崧嵓﨑嵂嵭嶸嶹巐弡弴彧德忞恝悅悊惞惕愠惲愑愷愰憘戓抦揵摠撝擎敎昀昕昻昉昮昞昤晥晗晙晴晳暙暠暲暿曺朎朗杦枻桒柀栁桄棏﨓楨﨔榘槢樰橫橆橳橾櫢櫤毖氿汜沆汯泚洄涇浯涖涬淏淸淲淼渹湜渧渼溿澈澵濵瀅瀇瀨炅炫焏焄煜煆煇凞燁燾犱???????????????????????????????????????????????????????????????????犾猤猪獷玽珉珖珣珒琇珵琦琪琩琮瑢璉璟甁畯皂皜皞皛皦益睆劯砡硎硤硺礰礼神祥禔福禛竑竧靖竫箞精絈絜綷綠緖繒罇羡羽茁荢荿菇菶葈蒴蕓蕙?蕫﨟薰蘒﨡蠇裵訒訷詹誧誾諟諸諶譓譿賰賴贒赶﨣軏﨤逸遧郞都鄕鄧釚釗釞釭釮釤釥鈆鈐鈊鈺鉀鈼鉎鉙鉑鈹鉧銧鉷鉸鋧鋗鋙鋐﨧鋕鋠鋓錥錡鋻﨨錞鋿錝錂鍰鍗鎤鏆鏞鏸鐱鑅鑈閒隆﨩隝隯霳霻靃靍靏靑靕顗顥飯飼餧館馞驎髙髜魵魲鮏鮱鮻鰀鵰鵫鶴鸙黑??ⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹ¬¦'"???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ¬¦'"㈱№℡∵纊褜鍈銈蓜俉炻昱棈鋹曻彅丨仡仼伀伃伹佖侒侊侚侔俍偀倢俿倞偆偰偂傔僴僘兊?兤冝冾凬刕劜劦勀勛匀匇匤卲厓厲叝﨎咜咊咩哿喆坙坥垬埈埇﨏塚增墲夋奓奛奝奣妤妺孖寀甯寘寬尞岦岺峵崧嵓﨑嵂嵭嶸嶹巐弡弴彧德忞恝悅悊惞惕愠惲愑愷愰憘戓抦揵摠撝擎敎昀昕昻昉昮昞昤晥晗晙晴晳暙暠暲暿曺朎朗杦枻桒柀栁桄棏﨓楨﨔榘槢樰橫橆橳橾櫢櫤毖氿汜沆汯泚洄涇浯???????????????????????????????????????????????????????????????????涖涬淏淸淲淼渹湜渧渼溿澈澵濵瀅瀇瀨炅炫焏焄煜煆煇凞燁燾犱犾猤猪獷玽珉珖珣珒琇珵琦琪琩琮瑢璉璟甁畯皂皜皞皛皦益睆劯砡硎硤硺礰礼神?祥禔福禛竑竧靖竫箞精絈絜綷綠緖繒罇羡羽茁荢荿菇菶葈蒴蕓蕙蕫﨟薰蘒﨡蠇裵訒訷詹誧誾諟諸諶譓譿賰賴贒赶﨣軏﨤逸遧郞都鄕鄧釚釗釞釭釮釤釥鈆鈐鈊鈺鉀鈼鉎鉙鉑鈹鉧銧鉷鉸鋧鋗鋙鋐﨧鋕鋠鋓錥錡鋻﨨錞鋿錝錂鍰鍗鎤鏆鏞鏸鐱鑅鑈閒隆﨩隝隯霳霻靃靍靏靑靕顗顥飯飼餧館馞驎髙???????????????????????????????????????????????????????????????????髜魵魲鮏鮱鮻鰀鵰鵫鶴鸙黑???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? ???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????󿢟󿢠󿢡󿢢󿢣󿢤󿢥󿢦󿢧󿢨󿢩󿢪󿢫󿢬󿢭󿢮󿢯󿢰󿢱󿢲󿢳󿢴󿢵󿢶󿢷󿢸󿢹󿢺󿢻󿢼󿢽󿢾󿢿󿣀󿣁󿣂󿣃󿣄󿣅󿣆󿣇󿣈󿣉󿣊󿣋󿣌󿣍󿣎󿣏󿣐󿣑󿣒󿣓󿣔󿣕󿣖󿣗󿣘󿣙󿣚󿣛󿣜󿣝󿣞󿣟󿣠󿣡󿣢󿣣󿣤󿣥󿣦󿣧󿣨󿣩󿣪󿣫󿣬󿣭󿣮󿣯󿣰󿣱󿣲󿣳󿣴󿣵󿣶󿣷󿣸󿣹󿣺󿣻󿣼???????????????????????????????????????????????????????????????????󿥀󿥁󿥂󿥃󿥄󿥅󿥆󿥇󿥈󿥉??????󿥐󿥑󿥒??󿥕󿥖󿥗???󿥛󿥜󿥝󿥞???????????????????󿥲󿥳󿥴󿥵󿥶󿥷󿥸󿥹󿥺󿥻󿥼󿥽󿥾?󿦀󿦁󿦂󿦃󿦄󿦅󿦆󿦇󿦈󿦉󿦊󿦋󿦌󿦍󿦎󿦏󿦐󿦑󿦒󿦓󿦔󿦕󿦖󿦗󿦘󿦙󿦚󿦛󿦜󿦝󿦞󿦟󿦠󿦡󿦢󿦣󿦤󿦥󿦦󿦧󿦨󿦩󿦪󿦫󿦬󿦭󿦮󿦯󿦰????????????????????????????????????????????????????????????????????????????????H???????????????{|?????????^??????~?????~?Ӂ?I??E???????????????wz??????}??????????E??????????R?s????????????t?G??@ ??10????????????????????????P??????H???????????????F?????????????????????????Ez{??UP??????@??????????????????????????????VS???????????????T|wc????????????j????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????`anAB???????????????????????????????????????????????w??????????????w?????????????w??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????10111210cZxNEȎw`钴}őSn????????E???????????????F???????H??????????????????????????????????????????????????????????~???????????????????????????????????????????????????????????????????????????????????????????????????t????^???R?????w??????????????????????????????Q}?????????????????????????V???????????tďH~???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????10??-!%./:;?@^_'"{=~z?????????NG???????????????????????????????????????????????????????????????????`abcdefghijklmnopqrstuvwxy‚?ĂłƂǂȂɂʂ˂̂͂΂ςЂт҂ӂԂՂւׂ؂قڂۂ܂݂ނ߂@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????G??????CD????I?????????????????????w??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????@ABCDEFGHI??????PQR??UVW???[\]^???????????????????rstuvwxyz{|}~??????????Q???R???????????????NG??????֋󍇖?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????sr??????????????????????????????VSQ??????????????????????????????????????????????????????????????????????????j????????????????P?~H?????????????????????????????????????????????????????????????????????????????????UP?L\???T|wc????I????????i??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????t??????GH???????????????H?????}????????F??E??????????????????????????????????????????????????????????????????????????????????E???????`aABn????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????󿢟󿢠󿢡󿢢󿢣󿢤󿢥󿢦󿢧󿢨󿢩󿢪󿢫󿢬󿢭󿢮󿢯󿢰󿢱󿢲󿢳󿢴󿢵󿢶󿢷󿢸󿢹󿢺󿢻󿢼󿢽󿢾󿢿󿣀󿣁󿣂󿣃󿣄󿣅󿣆󿣇󿣈󿣉󿣊󿣋󿣌󿣍󿣎󿣏󿣐󿣑󿣒󿣓󿣔󿣕󿣖󿣗󿣘󿣙󿣚󿣛󿣜󿣝󿣞󿣟󿣠󿣡󿣢󿣣󿣤󿣥󿣦󿣧󿣨󿣩󿣪󿣫󿣬󿣭󿣮󿣯󿣰󿣱󿣲󿣳󿣴󿣵󿣶󿣷󿣸󿣹󿣺󿣻󿣼???????????????????????????????????????????????????????????????????󿥀󿥁󿥂󿥃󿥄󿥅󿥆󿥇󿥈󿥉??????󿥐󿥑󿥒??󿥕󿥖󿥗???󿥛󿥜󿥝󿥞???????????????????󿥲󿥳󿥴󿥵󿥶󿥷󿥸󿥹󿥺󿥻󿥼󿥽󿥾?󿦀󿦁󿦂󿦃󿦄󿦅󿦆󿦇󿦈󿦉󿦊󿦋󿦌󿦍󿦎󿦏󿦐󿦑󿦒󿦓󿦔󿦕󿦖󿦗󿦘󿦙󿦚󿦛󿦜󿦝󿦞󿦟󿦠󿦡󿦢󿦣󿦤󿦥󿦦󿦧󿦨󿦩󿦪󿦫󿦬󿦭󿦮󿦯󿦰󿦱󿦲󿦳󿦴󿦵󿦶󿦷󿦸󿦹󿦺󿦻󿦼󿦽󿦾󿦿󿧀󿧁󿧂󿧃󿧄󿧅󿧆󿧇󿧈󿧉󿧊󿧋󿧌󿧍󿧎󿧏󿧐󿧑󿧒󿧓󿧔󿧕󿧖󿧗󿧘󿧙󿧚󿧛󿧜󿧝󿧞󿧟󿧠󿧡󿧢󿧣󿧤󿧥󿧦󿧧󿧨󿧩󿧪󿧫󿧬󿧭󿧮󿧯󿧰󿧱󿧲󿧳󿧴󿧵󿧶󿧷󿧸󿧹󿧺󿧻󿧼????H?????????????ā{|?????????^???ׁ~???~?Ӂ?ρI?E????????????wz?????}?????????E??????????s??f?????????t?G??@ ??10??????????????????????P??????H????????F????????????????????Ez{??UP??????@?????????????????????????VS??????????????T|ގwc???????????j???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????`anAB??????????????????????????????????????????????w??????????????w?????w??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????10111210cZxNEȎw`ْ}őSn???????E???????????????F?????H???????????????????????????????????????????????~??????????????????????????????????????????????????????????????????????????????????????????????t???^??????w???????????????????????????}????????????????????????V??????????tďH~??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????10??-!%./:;?@^_'"{=~z?????????ԓ???????????????????????????????????????????????????????????????????`abcdefghijklmnopqrstuvwxy‚?ĂłƂǂȂɂʂ˂̂͂΂ςЂт҂ӂԂՂւׂ؂قڂۂ܂݂ނ߂@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????G?????CD????I?????????????????????w??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????@ABCDEFGHI??????PQR??UVW???[\]^???????????????????rstuvwxyz{|}~???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????sr???????????????????????VS???????????????????????????????????????????????????????????????????????j???????????????P~H????????????????????????????????????????????????????????????????????????????UP?L\???T|ގwc????I??????i?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????t??GH??????????????H??}???????F??E?????????????????????????????????????????????????????????????????????????????E???????`aABn?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????󿬡󿬢󿬣󿬤󿬥󿬦󿬧󿬨󿬩󿬪󿬫󿬬󿬭󿬮󿬯󿬰󿬱󿬲󿬳󿬴󿬵󿬶󿬷󿬸󿬹󿬺󿬻󿬼󿬽󿬾󿬿󿭀󿭁󿭂󿭃󿭄󿭅󿭆󿭇󿭈󿭉󿭊󿭋󿭌󿭍󿭎󿭏󿭐󿭑󿭒󿭓󿭔󿭕󿭖󿭗󿭘󿭙󿭚󿭛󿭜󿭝󿭞󿭟󿭠󿭡󿭢󿭣󿭤󿭥󿭦󿭧󿭨󿭩󿭪󿭫󿭬󿭭󿭮󿭯󿭰󿭱󿭲󿭳󿭴󿭵󿭶󿭷󿭸󿭹󿭺??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????󿰡󿰢󿰣󿰤󿰥󿰦󿰧󿰨󿰩󿰪󿰫󿰬󿰭󿰮󿰯󿰰󿰱󿰲󿰳󿰴󿰵󿰶󿰷󿰸󿰹󿰺󿰻󿰼󿰽󿰾󿰿󿱀󿱁󿱂󿱃󿱄󿱅󿱆󿱇󿱈󿱉󿱊󿱋󿱌󿱍󿱎󿱏󿱐󿱑󿱒󿱓󿱔󿱕󿱖󿱗󿱘󿱙󿱚󿱛󿱜󿱝󿱞󿱟󿱠󿱡󿱢󿱣󿱤󿱥󿱦󿱧󿱨󿱩󿱪󿱫󿱬󿱭󿱮󿱯󿱰󿱱󿱲󿱳󿱴󿱵󿱶󿱷󿱸󿱹󿱺??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????󿴡󿴢󿴣󿴤󿴥󿴦󿴧󿴨󿴩󿴪󿴫󿴬󿴭󿴮󿴯󿴰󿴱󿴲󿴳󿴴󿴵󿴶󿴷󿴸󿴹󿴺󿴻󿴼󿴽󿴾󿴿󿵀󿵁󿵂󿵃󿵄󿵅󿵆󿵇󿵈󿵉󿵊󿵋󿵌󿵍󿵎󿵏󿵐󿵑󿵒󿵓󿵔󿵕󿵖󿵗󿵘󿵙󿵚󿵛󿵜󿵝󿵞󿵟󿵠󿵡󿵢󿵣󿵤󿵥󿵦󿵧󿵨󿵩󿵪󿵫󿵬󿵭󿵮󿵯󿵰󿵱󿵲󿵳󿵴󿵵󿵶󿵷󿵸󿵹󿵺??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????$Fr$GA$G@p$F[$FZ$F]$F\???$Eb?$E]?????????{|$FR$FS?????????$FW$FX$Gj??$Gl??$G"$GB?$E0?~?????~?$FT$FU$Gg?$Gv$FV$FY$GU?$Gr??$E/??$G!$Fn$Fo$E>?$G)???$Eh?$Ge$G($Gk?$Eh?$En$Eh$Eh$Eh???$EE$Gi$E#$G]?$GV??$EI???$E4$G_$Eh$Eh??$Ez???$E!???$E8$Gr?????$FP$FQ$Eh?$E2?$Gc$Eh???$E#???$G)?????$G*????$G+?$G=$G<??$G>@ ??$F($F*$F)$F<$F=$F>$F?$F@$FA$FB$FC$FD10?$Gh?????????????$Et?$Eq$Eo$Ep$Ek?$Em$GZ??$Ey$G???$G5$G3$ER$ED?????$ES?$EP??$GS$GP$E;??$Gf??$G9??$E@$Gt?$G:$E)?$Go$Gu?$E+??$Gw$Gy$Gx?$E\$Gy$E($E%$GC$GB?$E=?$El$GB$G#$E,??$E:??$G-???$FF$E<$G.$EJ$G\?$G^$Ga?$G^?$E3?Ez?$EF??$F3$E[?$G4???????$F.$F-$F/??$G1$G2????$FE$Fm?$GB?$E(???$F2??$G,$EH?$GW?$G[$G^$Gn$Gp$Gq$Gs?$E&$E'$E($E*$E-$E.$E1$E6$E7$E9$E>$EA$EB$EM$EN$EQ???$EW$E_?$Ef?$Ej?$Es$Eu$Ew$Ex$F"$F'$F+$F4$FG$FH$FI$FJ$FK$FL$FM$FN$FO?$Fp$Fq???????j???????????$F,$GB$GB$GB$GB$GB?$G3???????????????????$Gx???????????$G^$Gv??????????????$Gb??????????????????????$Ex$Gm???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????$EZ??$G9?$G9????????`anAB?????!?!!`???????????????$G7???????????????????????????$Gy????$E#??????????????$E#$F!??$GB??????????$E#?$G0$F0?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????$FE$F<$F=$F>$F?$F@$FA$FB$FC$FD101112$F<$F=$F>$F?$F@$FA$FB$FC$FD10$FMZxNE$FLp`}Sn$Gs$Gp???$G:$E)?$Gr???????????????$Go?$Gt$G9$E*???$GS???$GP$E9???????$G$$G%??$G!$G"$EZ??$E,$Gn$E:$Ez$Ey$G>$F!$G?$G=$F"??$Gc$Ev$E@??$Gg?$Ge$Gc???$G\$Ex$Ex?$ED?$G]???$G[???$El$ES$EM$ET$EV$EU????$G5$G4$G3$G3?$Eo$GZ$Ep$Gc??$Eu$EA$EC$Em??$Ew$F(?$Gj$Gi$Gk$Gh$E]?$F[$FZ???$FQ$FP$FO$FN$G0$G1$G2$G#$E<$GB$GB$F,$F-???????????????????????????????????????????????????????????????????$F/$F.!!!??????$G^$E0???$G&??$G&?????$E^??????$E>???$GT$G*$G*$G)$G)?$G+$G,????$G($E/?$EJ$EH$G^?$EF$E-??$Eh$Eh???$Es??$E#$E4??$GV$GX$GW???????????????????$E3??????$E.$GU$EO$G_$Ed?$E6?????????$Ga?$Eb$EE$E2???$Gk????????$Gz??$Gf???????tH~?$Gw$Gx$Gy??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????$E5$E5$E5$E5????????????????????$G:$G:$G:$G:$E/$E/$E/$E/$Gj$Gj$Gj$Gj$GC$GC$GC$GC?$Gw$Gw$Gw?????????????????????????????????????????????????????????$F0$FE??$FE$F<$F=$F>$F?$F@$FA$FB$FC$FD10??-!%./:;?@^_'"{=~?????????$FmNG$FF$FG???????????????????????????????????????????????????????????????????`abcdefghijklmnopqrstuvwxy?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????$G$$G%???$Ez$Ez?$G<$ER??$En???$Et$Eq$F*?????????$FV$FX$FW$FY???$F2$GA??$F)?$GB$F1??????????????$E#????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????$Gj$Gi$Gk$Gh$E]???$F_$F`$Fa$Fb$Fc$Fd$Fe$Ff$Fg$Fh$Fi$Fj??$G4$G5?$G3?$ER?$G>?$G?$Ez$Ez$Ey$F"$G=$GV$GX$Es$Eu$Em$Et$Ex$Ev$GZ$Eo$En$Eq$Gc$Ge?$Gg$E@$E^?$G\$G]$FV?$G^???$EE?$F($G($E>$Eh?$E2?$G)$G*$Eh$EJ?$EF$F,$F.$F-$F/??$G0$G1$G2$FX$FW???$F*????????????????????????????????????????????????????????????????????????$Gr$Go$G<$GS$FY??????????????????ON?????????????????????$E$$E#$G+$E#???$FI$G_???$E4$F2?$F1$F0p$F<$F=$F>$F?$F@$FA$FB$FC$FD$FE$GB$GB$GC$GB$Gw$Gy$Gx$Gx??$G^$EC?$G#?$E/$Gy$G-???$E\$GA!?!!?$E($E(?``$Fm??$G&???$G3??$EO$G,???$E.$GT??????$E($E($Gy?$E&$G.$E%?$Gw??$Gx?NG?$Fn?$E5?$Fo$Fr$FK$FJ?$Ew?$G[$E0?????$E8$GP?$Gf?????$Gu$G9??$G:$E+?$E'????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????$E!$E"$E#$E$$E%$E&$E'$E($E)$E*$E+$E,$E-$E.$E/$E0$E1$E2$E3$E4$E5$E6$E7$E8$E9$E:$E;$E<$E=$E>$E?$E@$EA$EB$EC$ED$EE$EF$EG$EH$EI$EJ$EK$EL$EM$EN$EO$EP$EQ$ER$ES$ET$EU$EV$EW$EX$EY$EZ$E[$E\$E]$E^$E_$E`$Ea$Eb$Ec$Ed$Ee$Ef$Eg$Eh$Ei$Ej$Ek$El$Em$En$Eo$Ep$Eq$Er$Es$Et$Eu$Ev$Ew$Ex$Ey$Ez??????????????????????????????????????????????$E`$G^??j????????$Eb???????????$E>??$G^$GB$GB$GB$GB$GB$GB$GB??$E(~$Gy$G@$GA?????????????????????????????????????????????????????????????????????????$F!$F"$F#$F$$F%$F&$F'$F($F)$F*$F+$F,$F-$F.$F/$F0$F1$F2$F3$F4$F5$F6$F7$F8$F9$F:$F;$F<$F=$F>$F?$F@$FA$FB$FC$FD$FE$FF$FG$FH$FI$FJ$FK$FL$FM$FN$FO$FP$FQ$FR$FS$FT$FU$FV$FW$FX$FY$FZ$F[$F\$F]$F^$F_$F`$Fa$Fb$Fc$Fd$Fe$Ff$Fg$Fh$Fi$Fj$Fk$Fl$Fm$Fn$Fo$Fp$Fq$Fr$Fs$Ft$Fu$Fv$Fw$Fx$Fy$Fz??????????????????????????????????????$E(?????$Gx????????????$Gv$Gw$Gy???????????????????????$Ez??????$G????????$GW?????????$E9?$Gm???????????????????????????????????????????????????????$G!$G"$G#$G$$G%$G&$G'$G($G)$G*$G+$G,$G-$G.$G/$G0$G1$G2$G3$G4$G5$G6$G7$G8$G9$G:$G;$G<$G=$G>$G?$G@$GA$GB$GC$GD$GE$GF$GG$GH$GI$GJ$GK$GL$GM$GN$GO$GP$GQ$GR$GS$GT$GU$GV$GW$GX$GY$GZ$G[$G\$G]$G^$G_$G`$Ga$Gb$Gc$Gd$Ge$Gf$Gg$Gh$Gi$Gj$Gk$Gl$Gm$Gn$Go$Gp$Gq$Gr$Gs$Gt$Gu$Gv$Gw$Gx$Gy$Gz????????????????????????????????????????????$G]??????????????????$EZ?????$G9?$G9?????$E)?$Gr???????`aABn???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????󿮡󿮢󿮣󿮤󿮥󿮦󿮧󿮨󿮩󿮪󿮫󿮬󿮭󿮮󿮯󿮰󿮱󿮲󿮳󿮴󿮵󿮶󿮷󿮸󿮹󿮺󿮻󿮼󿮽󿮾󿮿󿯀󿯁󿯂󿯃󿯄󿯅󿯆󿯇󿯈󿯉󿯊󿯋󿯌󿯍󿯎󿯏󿯐󿯑󿯒󿯓󿯔󿯕󿯖󿯗󿯘󿯙󿯚󿯛󿯜󿯝󿯞󿯟󿯠󿯡󿯢󿯣󿯤󿯥󿯦󿯧󿯨󿯩󿯪󿯫󿯬󿯭???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????󿲡󿲢󿲣󿲤󿲥󿲦󿲧󿲨󿲩󿲪󿲫󿲬󿲭󿲮󿲯󿲰󿲱󿲲󿲳󿲴󿲵󿲶󿲷󿲸󿲹󿲺󿲻󿲼󿲽󿲾󿲿󿳀󿳁󿳂󿳃󿳄󿳅󿳆󿳇󿳈󿳉󿳊󿳋󿳌󿳍󿳎󿳏󿳐󿳑󿳒󿳓󿳔󿳕󿳖󿳗󿳘󿳙󿳚󿳛󿳜󿳝󿳞󿳟󿳠󿳡󿳢󿳣󿳤󿳥󿳦󿳧󿳨󿳩󿳪󿳫󿳬????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????󿶡󿶢󿶣󿶤󿶥󿶦󿶧󿶨󿶩󿶪󿶫󿶬󿶭󿶮󿶯󿶰󿶱󿶲󿶳󿶴󿶵󿶶󿶷󿶸󿶹󿶺󿶻󿶼󿶽󿶾󿶿󿷀󿷁󿷂󿷃󿷄󿷅󿷆󿷇󿷈󿷉󿷊󿷋󿷌󿷍󿷎󿷏󿷐󿷑󿷒󿷓󿷔󿷕󿷖󿷗󿷘󿷙???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????$Fr$GA$G@p$F[$FZ$F]$F\???$O7?$E]?????????{|$OO$FR$FS?????????$FW$FX$Gj??$Gl$OE?$G"$GB?$E0$QW~?????$OS?$FT$FU$Gg?$P4$OO$FV$FY$GU?$Gr$Pk?$E/$QC?$G!$Fn$Fo$OC?$G)??$Q,$Eh$O!$Ge$G($Gk$PK$Eh?$En$Eh$Eh$Eh?$O3?$EE$Gi$E#$G]?$GV$O$?$EI???$E4$G_$Eh$Eh?$O:$Ez???$E!???$E8$QJ?????$FP$FQ$Eh?$E2?$Gc$Eh$PO??$E#???$G)?????$G*????$G+?$G=$G<??$G>@ ?$O.$F($F*$F)$F<$F=$F>$F?$F@$FA$FB$FC$FD10$Pc$Gh?????????????$Et?$Eq$Eo$Ep$Ek?$Em$GZ??$Ey$G???$G5$G3$ER$ED??$OD?$Q)$ES?$EP??$GS$GP$E;$Q+$Oh$Gf??$G9$Og$Ob$E@$Gt$QL$G:$E)$QQ$Go$Gu?$E+$O'$O%$P5$P6$Gx?$E\$OT$E($E%$GC$GB$OO$O1$E=?$El$OI$G#$E,?$QV$E:?$O5?$G-$OP??$FF$E<$G.$EJ$G\?$G^$Ga?$O*$O<$E3$O>Ez?$EF$O9?$F3$E[?$G4$PJ????$O4$Ok$F.$F-$F/$P9$P;$G1$G2????$FE$Fm?$GB?$OQ??$O`$F2??$G,$EH?$GW$PT$G[$G^$Gn$Gp$Gq$Gs?$E&$E'$E($E*$E-$E.$E1$E6$E7$E9$E>$EA$EB$EM$EN$EQ?$Q1$Q2$EW$E_?$Ef?$Ej?$Es$Eu$Ew$Ex$F"$F'$F+$F4$FG$FH$FI$FJ$FK$FL$FM$FN$FO?$Fp$Fq$O!$O"$O#$O&$O($O+$O,$O-?$O0$O2?$O8$O;$O=$O?$O@$OA$OB$F,$OG$OJ$OK$OL$OM?$G3?$OX$OY$OZ$O[$O\$O]$O^$O_$Oa$Oc$Od$Oe$Of$Oi$Oj$Ol?$P"$P'?????$P/??$P.??$OF$Gv?$P8$P:$P<?$P?$P@$PA$P>???$PE$PI$Gb$PL$PM$PP$PQ$PR$PS$PV$PX??$P[$P\?$P_$P`$Pa$Pb?$Pf$Ph$Pk$Pl$Ex$Pi$Q#$Q$$Q%$Q&?$Q-???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????$Q.$Q/$Q0$Q3$Q4$Q5$Q6$Q7??$Q:$Q;$Q<$Q@?$QB$QE$QF$QG$QK?$QA?$QP$QR$QS$QU$QT$QV????!?!!`???????????????$G7????????$OZ??????????????????$Gy????$E#??????????????$E#$F!??$OH$QC????$P^????$E#?$G0$F0?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????$FE$F<$F=$F>$F?$F@$FA$FB$FC$FD101112$F<$F=$F>$F?$F@$FA$FB$FC$FD10$FMZxNE$FLp`$O5}Sn$Gs$Gp$QL?$QM$G:$E)$QN$Gr$QO??????????????$Go$QF$Gt$Q@$E*???$GS?$O$$O%$GP$E9$Of$Oe?$Og???$G$$G%??$G!$G"$Q:?$Ph$E,$Gn$E:$PN$Ey$G>$F!$G?$G=$F"??$Gc$Ev$E@$O`$O+$Gg?$Ge$Gc?$Od?$G\$Ex$Ex?$ED?$G]???$G[???$El$ES$EM$ET$EV$EU???$PJ$G5$G4$G3$G3?$Eo$GZ$Ep$Gc??$Eu$EA$EC$Em??$Ew$F($O.$Gj$Gi$Gk$Gh$E]?$F[$FZ???$FQ$FP$FO$FN$G0$G1$G2$G#$E<$GB$GB$F,$F-???????????????????????????????????????????????????????????????????$F/$F.!!!??????$G^$E0$QE$PY?$G&??$G&?????$E^?$O"?$Q#??$OC???$GT$G*$G*$G)$G)?$G+$G,????$G($E/?$EJ$EH$O*?$EF$E-??$Eh$Eh?$O3?$Es??$E#$E4??$GV$GX$GW$Q&??????????????????$E3$O1?????$E.$GU$EO$G_$Ed?$E6???$OZ?????$Ga?$O7$EE$E2$O<??$Gk??$O]?????$Gz??$Gf???$O,?$O2?tH~$P($P5$Gx$P6??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????$E5$E5$E5$E5????????????????????$G:$G:$G:$G:$E/$E/$E/$E/$Gj$Gj$Gj$Gj$GC$GC$GC$GC?$Gw$Gw$Gw?????????????????????????????????????????????????????????$F0$FE??$FE$F<$F=$F>$F?$F@$FA$FB$FC$FD10??-!%./:;?@^_'"{=~$OR?????????$FmNG$FF$FG???????????????????????????????????????????????????????????????????`abcdefghijklmnopqrstuvwxy?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~??????????????????????????????????????????????????????????????????????????$QV$QV?????????????????????????????????????????????????????????$QQ???$G$$G%???$Ez$Ez?$G<$ER$Ob?$En???$Et$Eq$F*?$Pc$P\??$Pk???$FV$FX$FW$FY???$F2$OW??$F)?$GB$F1??????????????$E#?????????????$P;??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????$Gj$Gi$Gk$Gh$E]$Pc?$P\$F_$F`$Fa$Fb$Fc$Fd$Fe$Ff$Fg$Fh$Fi$Fj??$G4$G5?$G3$PJ$ER?$G>?$G?$Ez$PN$Ey$F"$G=$GV$GX$Es$Eu$Em$Et$Ex$Ev$GZ$Eo$En$Eq$Gc$Ge?$Gg$E@$E^$O3$G\$G]$FV?$O*???$EE$O.$F($G($OC$Eh$O4$E2?$G)$G*$Eh$EJ?$EF$F,$F.$F-$F/$P9$P;$G0$G1$G2$FX$FW$QV??$F*????????????????????????????????????????????????????????????????????????$Gr$Go$G<$GS$FY??????$OD???????????ON?????????????????????$E$$E#$G+$E#???$FI$G_???$E4$F2?$F1$F0p$F<$F=$F>$F?$F@$FA$FB$FC$FD$FE$GB$GB$GC$GB$Gw$P6$Gx$P'??$G^$EC?$G#$ON$E/$OT$G-$O1??$E\$OW!?!!?$OQ$E($OP``$Fm??$G&?$O<?$G3$OE?$EO$G,???$E.$GT??$OX???$E($P!$Gy?$E&$G.$E%$P%$P5$P&?$Gx$P(NG?$Fn$QW$E5$O5?$Fo$Fr$FK$FJ?$Ew$P^$G[$E0?$O$?$Oe?$E8$GP$Ob$Gf$O+$O`$OY?$QC$Gu$G9??$G:$E+?$E'????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????$E!$E"$E#$E$$E%$E&$E'$E($E)$E*$E+$E,$E-$E.$E/$E0$E1$E2$E3$E4$E5$E6$E7$E8$E9$E:$E;$E<$E=$E>$E?$E@$EA$EB$EC$ED$EE$EF$EG$EH$EI$EJ$EK$EL$EM$EN$EO$EP$EQ$ER$ES$ET$EU$EV$EW$EX$EY$EZ$E[$E\$E]$E^$E_$E`$Ea$Eb$Ec$Ed$Ee$Ef$Eg$Eh$Ei$Ej$Ek$El$Em$En$Eo$Ep$Eq$Er$Es$Et$Eu$Ev$Ew$Ex$Ey$Ez??????????????????????????????????????$O!$O"$O#$O$$O%$O&$O'$O($O)$O*$O+$O,$O-$O.$O/$O0$O1$O2$O3$O4$O5$O6$O7$O8$O9$O:$O;$O<$O=$O>$O?$O@$OA$OB$OC$OD$OE$OF$OG$OH$OI$OJ$OK$OL$OM$ON$OO$OP$OQ$OR$OS$OT$OU$OV$OW$OX$OY$OZ$O[$O\$O]$O^$O_$O`$Oa$Ob$Oc$Od$Oe$Of$Og$Oh$Oi$Oj$Ok$Ol$Om???????????????????????????????????????????????????$F!$F"$F#$F$$F%$F&$F'$F($F)$F*$F+$F,$F-$F.$F/$F0$F1$F2$F3$F4$F5$F6$F7$F8$F9$F:$F;$F<$F=$F>$F?$F@$FA$FB$FC$FD$FE$FF$FG$FH$FI$FJ$FK$FL$FM$FN$FO$FP$FQ$FR$FS$FT$FU$FV$FW$FX$FY$FZ$F[$F\$F]$F^$F_$F`$Fa$Fb$Fc$Fd$Fe$Ff$Fg$Fh$Fi$Fj$Fk$Fl$Fm$Fn$Fo$Fp$Fq$Fr$Fs$Ft$Fu$Fv$Fw$Fx$Fy$Fz??????????????????????????????????????$P!$P"$P#$P$$P%$P&$P'$P($P)$P*$P+$P,$P-$P.$P/$P0$P1$P2$P3$P4$P5$P6$P7$P8$P9$P:$P;$P<$P=$P>$P?$P@$PA$PB$PC$PD$PE$PF$PG$PH$PI$PJ$PK$PL$PM$PN$PO$PP$PQ$PR$PS$PT$PU$PV$PW$PX$PY$PZ$P[$P\$P]$P^$P_$P`$Pa$Pb$Pc$Pd$Pe$Pf$Pg$Ph$Pi$Pj$Pk$Pl????????????????????????????????????????????????????$G!$G"$G#$G$$G%$G&$G'$G($G)$G*$G+$G,$G-$G.$G/$G0$G1$G2$G3$G4$G5$G6$G7$G8$G9$G:$G;$G<$G=$G>$G?$G@$GA$GB$GC$GD$GE$GF$GG$GH$GI$GJ$GK$GL$GM$GN$GO$GP$GQ$GR$GS$GT$GU$GV$GW$GX$GY$GZ$G[$G\$G]$G^$G_$G`$Ga$Gb$Gc$Gd$Ge$Gf$Gg$Gh$Gi$Gj$Gk$Gl$Gm$Gn$Go$Gp$Gq$Gr$Gs$Gt$Gu$Gv$Gw$Gx$Gy$Gz??????????????????????????????????????$Q!$Q"$Q#$Q$$Q%$Q&$Q'$Q($Q)$Q*$Q+$Q,$Q-$Q.$Q/$Q0$Q1$Q2$Q3$Q4$Q5$Q6$Q7$Q8$Q9$Q:$Q;$Q<$Q=$Q>$Q?$Q@$QA$QB$QC$QD$QE$QF$QG$QH$QI$QJ$QK$QL$QM$QN$QO$QP$QQ$QR$QS$QT$QU$QV$QW$QX$QY??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????󿁀󿁁󿁂󿁃󿁄󿁅󿁆󿁇󿁈󿁉󿁊󿁋󿁌󿁍󿁎󿁏󿁐󿁑󿁒󿁓󿁔󿁕󿁖󿁗󿁘󿁙󿁚󿁛󿁜󿁝󿁞󿁟󿁠󿁡󿁢󿁣󿁤󿁥󿁦󿁧󿁨󿁩󿁪󿁫󿁬󿁭󿁮󿁯󿁰󿁱󿁲󿁳󿁴󿁵󿁶󿁷󿁸󿁹󿁺󿁻󿁼󿁽󿁾?󿂀󿂁󿂂󿂃󿂄󿂅󿂆󿂇󿂈󿂉󿂊󿂋󿂌󿂍󿂎󿂏󿂐󿂑󿂒󿂓󿂔󿂕󿂖󿂗󿂘󿂙󿂚󿂛󿂜󿂝󿂞󿂟󿂠󿂡󿂢󿂣󿂤󿂥󿂦󿂧󿂨󿂩󿂪󿂫󿂬󿂭󿂮󿂯󿂰󿂱󿂲󿂳󿂴󿂵󿂶󿂷󿂸󿂹󿂺󿂻󿂼󿂽󿂾󿂿󿃀󿃁󿃂󿃃󿃄󿃅󿃆󿃇󿃈󿃉󿃊󿃋󿃌󿃍󿃎󿃏󿃐󿃑󿃒󿃓󿃔󿃕󿃖󿃗󿃘󿃙󿃚󿃛󿃜󿃝󿃞󿃟󿃠󿃡󿃢󿃣󿃤󿃥󿃦󿃧󿃨󿃩󿃪󿃫󿃬󿃭󿃮󿃯󿃰󿃱󿃲󿃳󿃴󿃵󿃶󿃷󿃸󿃹󿃺󿃻󿃼???????????????????????????????????????????????????????????????????󿅀󿅁󿅂󿅃󿅄󿅅󿅆󿅇󿅈󿅉󿅊󿅋󿅌󿅍󿅎󿅏󿅐󿅑󿅒󿅓󿅔󿅕󿅖󿅗󿅘󿅙󿅚󿅛󿅜󿅝󿅞󿅟󿅠󿅡󿅢󿅣󿅤󿅥󿅦󿅧󿅨󿅩󿅪󿅫󿅬󿅭󿅮󿅯󿅰󿅱󿅲󿅳󿅴󿅵󿅶󿅷󿅸󿅹󿅺󿅻󿅼󿅽󿅾?󿆀󿆁󿆂󿆃󿆄󿆅󿆆󿆇󿆈󿆉󿆊󿆋󿆌󿆍󿆎󿆏󿆐󿆑󿆒󿆓󿆔󿆕󿆖󿆗󿆘󿆙󿆚󿆛󿆜󿆝󿆞󿆟󿆠󿆡󿆢󿆣󿆤󿆥󿆦󿆧󿆨󿆩󿆪󿆫󿆬󿆭󿆮󿆯󿆰󿆱󿆲󿆳󿆴󿆵󿆶󿆷󿆸󿆹󿆺󿆻󿆼󿆽󿆾󿆿󿇀󿇁󿇂󿇃󿇄󿇅󿇆󿇇󿇈󿇉󿇊󿇋󿇌󿇍󿇎󿇏󿇐󿇑󿇒󿇓󿇔󿇕󿇖?????????????????????????????????????????????????????????????????????????????????????????????????????????󿉀󿉁󿉂󿉃󿉄󿉅󿉆󿉇󿉈󿉉󿉊󿉋󿉌󿉍󿉎󿉏󿉐󿉑󿉒󿉓󿉔󿉕󿉖󿉗󿉘󿉙󿉚󿉛󿉜󿉝󿉞󿉟󿉠󿉡󿉢󿉣󿉤󿉥󿉦󿉧󿉨󿉩󿉪󿉫󿉬󿉭󿉮󿉯󿉰󿉱󿉲󿉳󿉴󿉵󿉶󿉷󿉸󿉹󿉺󿉻󿉼󿉽󿉾?󿊀󿊁󿊂󿊃󿊄󿊅󿊆󿊇󿊈󿊉󿊊󿊋󿊌󿊍󿊎󿊏󿊐󿊑󿊒󿊓󿊔󿊕󿊖󿊗󿊘󿊙󿊚󿊛󿊜󿊝󿊞󿊟󿊠󿊡󿊢󿊣󿊤󿊥󿊦󿊧󿊨󿊩󿊪󿊫????󿊰󿊱󿊲󿊳󿊴󿊵󿊶󿊷󿊸󿊹󿊺󿊻󿊼󿊽󿊾󿊿󿋀󿋁󿋂󿋃󿋄󿋅󿋆󿋇󿋈󿋉󿋊󿋋󿋌󿋍󿋎󿋏󿋐󿋑󿋒󿋓󿋔󿋕?????????󿋟󿋠󿋡󿋢󿋣󿋤󿋥󿋦󿋧󿋨󿋩󿋪󿋫󿋬󿋭󿋮󿋯󿋰󿋱󿋲󿋳󿋴󿋵󿋶󿋷󿋸󿋹󿋺󿋻󿋼???????????????????????????????????????????????????????????????????󿍀󿍁󿍂󿍃󿍄󿍅󿍆󿍇󿍈󿍉󿍊󿍋󿍌󿍍󿍎󿍏󿍐󿍑󿍒󿍓󿍔󿍕󿍖󿍗󿍘󿍙󿍚󿍛󿍜󿍝󿍞󿍟󿍠󿍡󿍢󿍣󿍤󿍥󿍦󿍧󿍨󿍩󿍪󿍫󿍬󿍭󿍮󿍯󿍰󿍱󿍲󿍳󿍴󿍵󿍶󿍷󿍸󿍹󿍺󿍻󿍼󿍽󿍾?󿎀󿎁󿎂󿎃󿎄󿎅󿎆󿎇󿎈󿎉󿎊󿎋󿎌󿎍󿎎󿎏󿎐󿎑󿎒󿎓󿎔󿎕󿎖󿎗󿎘󿎙󿎚󿎛󿎜󿎝󿎞󿎟󿎠󿎡󿎢󿎣󿎤󿎥󿎦󿎧󿎨󿎩󿎪󿎫󿎬󿎭󿎮󿎯󿎰󿎱󿎲󿎳󿎴󿎵󿎶󿎷󿎸󿎹󿎺󿎻󿎼󿎽󿎾󿎿󿏀󿏁󿏂󿏃󿏄󿏅󿏆󿏇󿏈󿏉󿏊󿏋󿏌󿏍󿏎󿏏󿏐󿏑󿏒󿏓󿏔󿏕󿏖󿏗󿏘󿏙󿏚󿏛󿏜󿏝󿏞󿏟󿏠󿏡󿏢󿏣󿏤󿏥󿏦󿏧󿏨󿏩󿏪󿏫󿏬󿏭󿏮󿏯󿏰󿏱󿏲󿏳󿏴󿏵󿏶󿏷󿏸󿏹󿏺?????????????????????????????????????????????????????????????????????󿑀󿑁󿑂󿑃󿑄󿑅󿑆󿑇󿑈󿑉󿑊󿑋󿑌󿑍󿑎󿑏????????????????????????????????????????????????󿒀???󿒄󿒅󿒆󿒇󿒈󿒉󿒊?󿒌󿒍󿒎?󿒐???󿒔󿒕󿒖?󿒘󿒙󿒚󿒛󿒜???󿒠󿒡󿒢󿒣󿒤???󿒨󿒩󿒪󿒫󿒬󿒭󿒮󿒯????󿒴󿒵??????󿒼󿒽󿒾?????󿓄󿓅??󿓈???󿓌????????????????????????????????????????????????????H_?????????????Ё|?????????m??J?????~Ӂ?s?o????^?e?񳁣?yyn?z?zzz|???`??yy???????sk???y}?z?w????e?{{?d???h???@ ?MNOPQRSTUVuvwxyz{|~}??????????p??????mpq??????????D?E?b?????q?I?s?Ez?u??UP????????A@????????????ir??Il?k???v?^?VS??????????~???TID^W????Y???ʏj?????????????????????????????????????I???????????????????????????????????[?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????K??????`anABEV???CB`???????????n?????????????????????????????????????????????S???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~????????????????????????????????????????????????????????????????????@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~??????????????????????????????????????????????????????????????????????????????????????????????????????????@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~?????????????????????????????????????????????????????????????????????????????????@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~??????????????????????????????????????????????????????????????????????@ABCDEFGHIJKLMNO??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????uvwxyz{|~?g?~W|?s??n^z??edyqpuA@?EV`??????????????????????????????????????????????????????????????????????s????????w????????ON?m???????????????????ch???ID???_????I?o???CB????``??O?S?i?wb?????????????}??Lb?֋󍇖?J?????????p?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????c???q?voJL???????uur?q??VS????????W?????z????~???????????????????????????????????????Y????sʏj??|?b???????????^??I???ԁ~ՁH????????????????????????????????????????????????????????????????????????A@UP?L?\???MNOPQRSTUTID^W?uvwxyz{|~}??????i????????????????????????????????????????????????????????????????????????????????????????????L??????????????????????????????????????????????????????????????????O?nedhi???pH???????????????b?I?????l?sk?????????????????????????????????????????[????????????????????????????K?q?s?mort?`aABnE?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????󾀁󾀂󾀃󾀄󾀅󾀆󾀇󾀈󾀉󾀊󾀋󾀌󾀍󾀎󾀏󾀐󾀑󾀒󾀓󾀔󾀕󾀖󾀗󾀘󾀙󾀚󾀛󾀜󾀝󾀞󾀟󾀠󾀡󾀢󾀣󾀤󾀥󾀦󾀧󾀨󾀩󾀪󾀫󾀬󾀭󾀮󾀯󾀰󾀱󾀲󾀳󾀴󾀵󾀶󾀷󾀸󾀹󾀺󾀻󾀼󾀽󾀾󾀿󾁀󾁁󾁂󾁃󾁄󾁅󾁆󾁇󾁈󾁉󾁊󾁋󾁌󾁍󾁎󾁏󾁐󾁑󾁒󾁓󾁔󾁕󾁖󾁗󾁘󾁙󾁚󾁛󾁜󾁝󾁞󾁟󾁠󾁡󾁢󾁣󾁤󾁥󾁦󾁧󾁨󾁩󾁪󾁫󾁬󾁭󾁮󾁯󾁰󾁱󾁲󾁳󾁴󾁵󾁶󾁷󾁸󾁹󾁺󾁻󾁼󾁽󾁾󾁿󾂀󾂁󾂂󾂃󾂄󾂅󾂆󾂇󾂈󾂉󾂊󾂋󾂌󾂍󾂎󾂏󾂐󾂑󾂒󾂓󾂔󾂕󾂖󾂗󾂘󾂙󾂚󾂛󾂜󾂝󾂞󾂟󾂠󾂡󾂢󾂣󾂤󾂥󾂦󾂧󾂨󾂩󾂪󾂫󾂬󾂭󾂮󾂯󾂰󾂱󾂲󾂳󾂴󾂵󾂶󾂷󾂸󾂹󾂺󾂻󾂼󾂽󾂾󾂿󾃀󾃁󾃂󾃃󾃄󾃅󾃆󾃇󾃈󾃉󾃊󾃋󾃌󾃍󾃎󾃏󾃐󾃑󾃒󾃓󾃔󾃕󾃖󾃗󾃘󾃙󾃚󾃛󾃜󾃝󾃞󾃟󾃠󾃡󾃢󾃣󾃤󾃥󾃦󾃧󾃨󾃩󾃪󾃫󾃬󾃭󾃮󾃯󾃰󾃱󾃲󾃳󾃴󾃵󾃶󾃷󾃸󾃹󾃺󾃻󾃼󾃽󾃾󾃿󾄀󾄁󾄂󾄃󾄄󾄅󾄆󾄇󾄈󾄉󾄊󾄋󾄌󾄍󾄎󾄏󾄐󾄑󾄒󾄓󾄔󾄕󾄖󾄗󾄘󾄙󾄚󾄛󾄜󾄝󾄞󾄟󾄠󾄡󾄢󾄣󾄤󾄥󾄦󾄧󾄨󾄩󾄪󾄫󾄬󾄭󾄮󾄯󾄰󾄱󾄲󾄳󾄴󾄵󾄶󾄷󾄸󾄹󾄺󾄻󾄼󾄽󾄾󾄿󾅀󾅁󾅂󾅃󾅄󾅅󾅆󾅇󾅈󾅉󾅊 ?  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJ????????????#????? @@ -5166,8 +5527,16 @@ ?G?#??M ??!?!!?J``F????'?0????H9???? ????NGQ6??R֋󍇖????5q???????N?? ?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? ????M5?(w??L? ?????j,,?s ???VS????????0|? ????a??c?????}?????????????????????????????????????????q??&??j?? ?h8? ?-|?'?)????S0#333333?EJ=E????????????????9????????????????????????????????????????????????????????????3;:??????????????3`??}?????????????????????????K?????????????????????????????????????????????????????P2????^U???@?2-?}3 ??????????????HHp????!n#x?$??]Ak,_/????J?D?????????????????????????????????????????????n??Z????????????????????N??????????`aABn6????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????󾀁󾀂󾀃󾀄󾀅󾀆󾀇󾀈󾀉󾀊󾀋󾀌󾀍󾀎󾀏󾀐󾀑󾀒󾀓󾀔󾀕󾀖󾀗󾀘󾀙󾀚󾀛󾀜󾀝󾀞󾀟󾀠󾀡󾀢󾀣󾀤󾀥󾀦󾀧󾀨󾀩󾀪󾀫󾀬󾀭󾀮󾀯󾀰󾀱󾀲󾀳󾀴󾀵󾀶󾀷󾀸󾀹󾀺󾀻󾀼󾀽󾀾󾀿󾁀󾁁󾁂󾁃󾁄󾁅󾁆󾁇󾁈󾁉󾁊󾁋󾁌󾁍󾁎󾁏󾁐󾁑󾁒󾁓󾁔󾁕󾁖󾁗󾁘󾁙󾁚󾁛󾁜󾁝󾁞󾁟󾁠󾁡󾁢󾁣󾁤󾁥󾁦󾁧󾁨󾁩󾁪󾁫󾁬󾁭󾁮󾁯󾁰󾁱󾁲󾁳󾁴󾁵󾁶󾁷󾁸󾁹󾁺󾁻󾁼󾁽󾁾󾁿󾂀󾂁󾂂󾂃󾂄󾂅󾂆󾂇󾂈󾂉󾂊󾂋󾂌󾂍󾂎󾂏󾂐󾂑󾂒󾂓󾂔󾂕󾂖󾂗󾂘󾂙󾂚󾂛󾂜󾂝󾂞󾂟󾂠󾂡󾂢󾂣󾂤󾂥󾂦󾂧󾂨󾂩󾂪󾂫󾂬󾂭󾂮󾂯󾂰󾂱󾂲󾂳󾂴󾂵󾂶󾂷󾂸󾂹󾂺󾂻󾂼󾂽󾂾󾂿󾃀󾃁󾃂󾃃󾃄󾃅󾃆󾃇󾃈󾃉󾃊󾃋󾃌󾃍󾃎󾃏󾃐󾃑󾃒󾃓󾃔󾃕󾃖󾃗󾃘󾃙󾃚󾃛󾃜󾃝󾃞󾃟󾃠󾃡󾃢󾃣󾃤󾃥󾃦󾃧󾃨󾃩󾃪󾃫󾃬󾃭󾃮󾃯󾃰󾃱󾃲󾃳󾃴󾃵󾃶󾃷󾃸󾃹󾃺󾃻󾃼󾃽󾃾󾃿󾄀󾄁󾄂󾄃󾄄󾄅󾄆󾄇󾄈󾄉󾄊󾄋󾄌󾄍󾄎󾄏󾄐󾄑󾄒󾄓󾄔󾄕󾄖󾄗󾄘󾄙󾄚󾄛󾄜󾄝󾄞󾄟󾄠󾄡󾄢󾄣󾄤󾄥󾄦󾄧󾄨󾄩󾄪󾄫󾄬󾄭󾄮󾄯󾄰󾄱󾄲󾄳󾄴󾄵󾄶󾄷󾄸󾄹󾄺󾄻󾄼󾄽󾄾󾄿󾅀󾅁󾅂󾅃󾅄󾅅󾅆󾅇󾅈󾅉󾅊󾅋󾅌󾅍󾅎󾅏󾅐󾅑󾅒󾅓󾅔󾅕󾅖󾅗󾅘󾅙󾅚󾅛󾅜󾅝󾅞󾅟󾅠󾅡󾅢󾅣󾅤󾅥󾅦󾅧󾅨󾅩󾅪󾅫󾅬󾅭󾅮󾅯󾅰󾅱󾅲󾅳󾅴󾅵󾅶󾅷󾅸󾅹󾅺󾅻󾅼󾅽󾅾󾅿󾆀󾆁󾆂󾆃󾆄󾆅󾆆󾆇󾆈󾆉󾆊󾆋󾆌󾆍󾆎󾆏󾆐󾆑󾆒󾆓󾆔󾆕󾆖󾆗󾆘󾆙󾆚󾆛󾆜󾆝󾆞󾆟󾆠󾆡󾆢󾆣󾆤󾆥󾆦󾆧󾆨󾆩󾆪󾆫󾆬󾆭󾆮󾆯󾆰󾆱󾆲󾆳󾆴󾆵󾆶󾆷󾆸󾆹󾆺󾆻󾆼󾆽󾆾󾆿󾇀󾇁󾇂󾇃󾇄󾇅󾇆󾇇󾇈󾇉󾇊󾇋󾇌󾇍󾇎󾇏󾇐󾇑󾇒󾇓󾇔󾇕󾇖󾇗󾇘󾇙󾇚󾇛󾇜󾇝󾇞󾇟󾇠󾇡󾇢󾇣󾇤󾇥󾇦󾇧󾇨󾇩󾇪󾇫󾇬󾇭󾇮󾇯󾇰󾇱󾇲󾇳󾇴󾇵󾇶󾇷󾇸󾇹󾇺󾇻󾇼󾇽󾇾󾇿󾈀󾈁󾈂󾈃󾈄󾈅󾈆?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????󾊼󾊽󾊾󾊿󾋀󾋁󾋂󾋃󾋄󾋅󾋆󾋇󾋈󾋉󾋊󾋋󾋌󾋍󾋎󾋏󾋐󾋑󾋒󾋓󾋔󾋕󾋖󾋗󾋘󾋙󾋚󾋛󾋜󾋝󾋞󾋟󾋠󾋡󾋢󾋣󾋤󾋥󾋦󾋧󾋨󾋩󾋪󾋫󾋬󾋭󾋮󾋯󾋰󾋱󾋲󾋳󾋴󾋵󾋶󾋷󾋸󾋹󾋺󾋻󾋼󾋽󾋾󾋿󾌀󾌁󾌂󾌃󾌄󾌅󾌆󾌇󾌈󾌉󾌊󾌋󾌌󾌍󾌎󾌏󾌐󾌑󾌒󾌓󾌔󾌕󾌖󾌗󾌘󾌙󾌚󾌛󾌜󾌝󾌞󾌟󾌠󾌡󾌢󾌣󾌤󾌥󾌦󾌧󾌨󾌩󾌪󾌫󾌬󾌭󾌮󾌯󾌰󾌱󾌲󾌳󾌴󾌵󾌶 ? -  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~  -    !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????  -    !"#$%&'()*+,-./0123456?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????E1112ZxNE`}őSn[Y??J??`???qf????P2?X} {?MA4]??!z??n??V??j????-32?xh?v?y,k_?)?'1?@3 -3;???????????????????????????????????????????????????????????????????<:????W5???????%??|????S?t?HU?Q??.^M R&?,a?[ahw?&lwC1p?T??????????????????( ?????bHx?d{????$W j'?_????????m??????tďH~???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????MMMM,,,,    ??????????????????????????????????????????????????????????2E??E??-!%./:;?@^_'"=7Bm?????????FNG???????????????????????????????????????????????????????????????????`abcdefghijklmnopqrstuvwxy‚?ĂłƂǂȂɂʂ˂̂͂΂ςЂт҂ӂԂՂւׂ؂قڂۂ܂݂ނ߂@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????}}??c??????K???F+*G(???N?H????????????g>l?????????????>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????,k_1?-23??}}{p?wxzc]4A|h!nF?&???j^Sa8?U[ ,3:;<=>1?@+*t???????????????????????????????????????????????????????????????????A????JG????????????????ON?.????????????????????l??m+x??DwN??2E3H  -GW??M ??J`F????'%0?Q&bH9??_ ?]????NGQ6?'R()y*V5q+M?N?. ^?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????]^_`abM5c(w?def ??hi?j,,Rs ??jkl???p???0|q ??s?a?uc?wxyz}??????????????????????????????????????q&? h8? -|')S0#?EJ=EM9???????????????????????????????????????????????????? {????|}3;:<2?N/~L\???E?@F*+G??FQR?i??????????????????????????????????????????????????????????D?=>???????3`}U???T*???sK????????????????????????????????????????????????????P2????^UQ?1?@2-}3 ??????????????HHpT??V!nWx?$?]Ak,_/XYZJ[?????????????????????????????????????????n??Zno?????N?????6????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????󾁋󾂾󾁣󾃚󾁌󾃟󾃥󾃦󾃶󾄁󾄂󾄃󾄄󾄅󾁍󾄉󾄊󾄋󾄌󾄍󾀺󾂰󾂱󾂲󾂳󾀁󾀂󾀃󾁢󾂿󾀏󾀐󾀬󾀯󾀼󾁅󾁟󾁫󾂧󾃀󾃁󾃂󾃃󾃄󾃅󾃆󾃇󾃈󾃉󾃊󾃋󾃌󾁓󾁛󾁪󾁺󾂏󾂕󾂞󾃍󾃎󾃏󾃐?󾃑󾃒󾃓󾃔󾁰󾂒󾂜󾃗󾃘󾃙󾁽󾂔󾂨󾂩󾂬󾃛󾃜󾃝󾃞󾀭󾁠󾃠󾃡󾃢󾃣󾃤󾀌󾀴󾁁󾂠󾃧󾃨󾃩󾂪󾃪󾃫󾃬󾃭󾃮󾂅󾂐󾃯󾃰󾃱󾃲󾃳󾃴󾃵󾃷󾃸󾃹󾃺󾃻󾃼󾃽󾃾󾁊󾁎󾂆󾃿󾄀󾁱󾄆󾄇󾄈󾄎󾄏󾄐󾄑󾄒󾄓󾄔󾄕󾄖󾄗󾄘󾄙󾄚󾄛󾄜󾄝󾄞󾄟󾀲󾁄󾁐󾁖󾁴󾂌󾂍󾂣󾄠󾄡󾄢󾄣󾄤󾄥󾄦󾄧󾄨󾄩󾄬󾄭󾄮󾄯󾄰󾀍󾀰󾀵󾁈󾁞󾁨󾁮󾁷󾁸󾁼󾂁󾂊󾂑󾂛󾂥󾂦󾁬󾂴󾂵???????????????????????????????????????????????????????????????????󾂶󾂷󾂸󾂹󾂺󾂻󾂼󾂽󾀄󾀅󾀆󾀇󾀈󾀉󾀊󾀋󾀑󾀒󾀓󾀔󾀕󾀖󾀗󾀘󾀚󾀛󾀜󾀝󾀞󾀟󾀠󾀡󾀢󾀣󾀤󾀥󾀦󾀧󾀨󾀩󾀪󾀫󾀶󾀷󾀽󾀾󾀿󾁀󾁂󾁆󾁇󾁉󾁑󾁒󾁘󾁙󾁵󾁶󾂄󾂈󾀱󾀸󾀻?󾁃󾁜󾁡󾁤󾁥󾁦󾁧󾁩󾁹󾂃󾂉󾂎󾂓󾂝󾃕󾃖󾁚󾁿󾂀󾂟󾄪󾄫󾀎󾀙󾀹󾁗󾁭󾁯󾁲󾁳󾁻󾁾󾂂󾂇󾂋󾂖󾂘󾂡󾂢󾂤󾂫󾂭󾂮󾂯󾁏󾁔󾂗󾂙󾂚󾀮󾀳󾁕󾁝󾄱󾄲󾄳󾄴󾄵󾄶󾄷󾄸󾄹󾄺󾄻󾄼󾄽󾄾󾄿󾅀󾅁󾅂󾅃󾅄󾅅󾅆󾅇󾅈󾅉󾅊??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????YZ[HIJKLMNO^_PQRSTUVWXYZ[\]^_`abcdefghi`a|jk}T~blmnopcqrs@DNtutvwud\Bvexywz{xyzfUVWX@ABCDEFGA]ghijklmnopqrs{|}~CEFGHIJKLMOPQRS?????????????????P??????D?t???VS??????????????????TIDwc????????????j???????????????????????????????????????????????????????????????????????????????@???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????H?H????????`anAB????!?!!`??????????????????????????????????????????J?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????@ABCDEFG1112@ABCDEFGcZxNEȎwH`ʒ}őSn??????ghijklmnso?pqr?HH?????????????????????|????????E???????F???????~???????VU`ed]_?IJ?????P???????????????????????????????????????????????????????????????????!!!???????????????????????t??????N???y?uzx???|?????????????????????R???????????????v??d?????????????????tďH~?IKJ??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????NNNN````OOOO?III????????????????????????????????????????????????????????????@ABCDEFG??-!%./:;?@^_'"X=kp?????????NGЊ???????????????????????????????????????????????????????????????????`abcdefghijklmnopqrstuvwxy‚?ĂłƂǂȂɂʂ˂̂͂΂ςЂт҂ӂԂՂւׂ؂قڂۂ܂݂ނ߂@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????B???{}W?A???@???qihr????ZX?????????????m???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????`ed]_A?ghijklmnopqr???????{?|~B}q????vUVt?uih?W???????????????????????????????????????????????????????????????????????r???????y?????????ON???????????????????????ID??????H@ABCDEFOPIJK????NR??MZ!?!!?́``????????y????J?P?I??K?NGxtj??uY֋󍇖???????????H????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????P????N???D?S?????Ev????VS?F???????M_?}??????B~}??{?|?????????????????????????????????????????????}??jU??R?????????t?c΁lc[Z???????????????????????????????????????????????????????????????????????????VXW?????L\???@ABCDEFЊTIDwc??[\noqhirJILK?ghijklmnopqrs?tu??Y?i??????????????????????????????????????????????????????????????IJ?????????????????????????????????????A???????@?????????????????????????????????????????????????????????????H[ZO?????????????????????]e`da?????HIKJ??????????????????????????????????????????????????????????????????H?H??????????볂`aABnj????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????󾁋󾂾󾁣󾃚󾁌󾃟󾃥󾃦󾃶󾄁󾄂󾄃󾄄󾄅󾁍󾄉󾄊󾄋󾄌󾄍󾀺󾂰󾂱󾂲󾂳󾀁󾀂󾀃󾁢󾂿󾀏󾀐󾀬󾀯󾀼󾁅󾁟󾁫󾂧󾃀󾃁󾃂󾃃󾃄󾃅󾃆󾃇󾃈󾃉󾃊󾃋󾃌󾁓󾁛󾁪󾁺󾂏󾂕󾂞󾃍󾃎󾃏󾃐?󾃑󾃒󾃓󾃔󾁰󾂒󾂜󾃗󾃘󾃙󾁽󾂔󾂨󾂩󾂬󾃛󾃜󾃝󾃞󾀭󾁠󾃠󾃡󾃢󾃣󾃤󾀌󾀴󾁁󾂠󾃧󾃨󾃩󾂪󾃪󾃫󾃬󾃭󾃮󾂅󾂐󾃯󾃰󾃱󾃲󾃳󾃴󾃵󾃷󾃸󾃹󾃺󾃻󾃼󾃽󾃾󾁊󾁎󾂆󾃿󾄀󾁱󾄆󾄇󾄈󾄎󾄏󾄐󾄑󾄒󾄓󾄔󾄕󾄖󾄗󾄘󾄙󾄚󾄛󾄜󾄝󾄞󾄟󾀲󾁄󾁐󾁖󾁴󾂌󾂍󾂣󾄠󾄡󾄢󾄣󾄤󾄥󾄦󾄧󾄨󾄩󾄬󾄭󾄮󾄯󾄰󾀍󾀰󾀵󾁈󾁞󾁨󾁮󾁷󾁸󾁼󾂁󾂊󾂑󾂛󾂥󾂦󾁬󾂴󾂵???????????????????????????????????????????????????????????????????󾂶󾂷󾂸󾂹󾂺󾂻󾂼󾂽󾀄󾀅󾀆󾀇󾀈󾀉󾀊󾀋󾀑󾀒󾀓󾀔󾀕󾀖󾀗󾀘󾀚󾀛󾀜󾀝󾀞󾀟󾀠󾀡󾀢󾀣󾀤󾀥󾀦󾀧󾀨󾀩󾀪󾀫󾀶󾀷󾀽󾀾󾀿󾁀󾁂󾁆󾁇󾁉󾁑󾁒󾁘󾁙󾁵󾁶󾂄󾂈󾀱󾀸󾀻?󾁃󾁜󾁡󾁤󾁥󾁦󾁧󾁩󾁹󾂃󾂉󾂎󾂓󾂝󾃕󾃖󾁚󾁿󾂀󾂟󾄪󾄫󾀎󾀙󾀹󾁗󾁭󾁯󾁲󾁳󾁻󾁾󾂂󾂇󾂋󾂖󾂘󾂡󾂢󾂤󾂫󾂭󾂮󾂯󾁏󾁔󾂗󾂙󾂚󾀮󾀳󾁕󾁝󾄱󾄲󾄳󾄴󾄵󾄶󾄷󾄸󾄹󾄺󾄻󾄼󾄽󾄾󾄿󾅀󾅁󾅂󾅃󾅄󾅅󾅆󾅇󾅈󾅉󾅊󾅋󾅌󾅍󾇴󾇵󾇶󾇷󾇸󾇹󾇺󾇻󾇼󾇽󾇾󾇿󾈀󾈁󾈂󾈃󾈄󾈅󾈆󾅎󾅏󾅐󾅑󾅒󾅓󾅔󾅕󾅖󾅗󾅘󾅙󾅚󾅛󾅜󾅝󾅞󾅟󾅠󾅡󾅢󾅣󾅤󾅥???????????????????????????????????????????????????????????????????󾅦󾅧󾅨󾅩󾅪󾅫󾅬󾅭󾅮󾅯󾅰󾅱󾅲󾅳󾅴󾅵󾅶󾅷󾅸󾅹󾅺󾅻󾅼󾅽󾅾󾅿󾆀󾆁󾆂󾆃󾆄󾆅󾆆󾆇󾆈󾆉󾆊󾆋󾆌󾆍󾆎󾆏󾆐󾆑󾆒󾆓󾆔󾆕󾆖󾆗󾆘󾆙󾆚󾆛󾆜󾆝󾆞󾆟󾆠󾆡󾆢󾆣󾆤?󾆥󾆦󾆧󾆨󾆩󾆪󾆫󾆬󾆭󾆮󾆯󾆰󾆱󾆲󾆳󾆴󾆵󾆶󾆷󾆸󾆹󾆺󾆻󾆼󾆽󾆾󾆿󾇀󾇁󾇂󾇃󾇄󾇅󾇆󾇇󾇈󾇉󾇊󾇋󾇌󾇍󾇎󾇏󾇐󾇑󾇒󾇓󾇔󾇕󾇖󾇗󾇘󾇙󾇚󾇛󾇜󾇝󾇞󾇟󾇠󾇡󾇢󾇣󾇤󾇥󾇦󾇧󾇨󾇩󾇪󾇫󾇬󾇭󾇮󾇯󾇰󾇱󾇲󾇳󾊼󾊽󾊾󾊿󾋀󾋁󾋂󾋃󾋄󾋅󾋆󾋇󾋈󾋉󾋊󾋋󾋌󾋍󾋎󾋏󾋐󾋑󾋒󾋓󾋔󾋕󾋖󾋗󾋘󾋙󾋚󾋛󾋜󾋝󾋞󾋟󾋠󾋡󾋢󾋣󾋤󾋥󾋦󾋧󾋨󾋩???????????????????????????????????????????????????????????????????󾋪󾋫󾋬󾋭󾋮󾋯󾋰󾋱󾋲󾋳󾋴󾋵󾋶󾋷󾋸󾋹󾋺󾋻󾋼󾋽󾋾󾋿󾌀󾌁󾌂󾌃󾌄󾌅󾌆󾌇󾌈󾌉󾌊󾌋󾌌󾌍󾌎󾌏󾌐󾌑󾌒󾌓󾌔󾌕󾌖󾌗󾌘󾌙󾌚󾌛󾌜󾌝󾌞󾌟󾌠󾌡󾌢󾌣󾌤󾌥󾌦󾌧󾌨?󾌩󾌪󾌫󾌬󾌭󾌮󾌯󾌰󾌱󾌲󾌳󾌴󾌵󾌶???????????????????????????????????????????????????????????????????????????????????????????????????????????????????YZ[HIJKLMNO^_PQRSTUVWXYZ[\]^_`abcdefghi`a|jk}T~blmnopcqrs@DNtutvwud\Bvexywz{xyzfUVWX@ABCDEFGA]ghijklmnopqrs{|}~CEFGHIJKLMOPQRS@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????@ABCDEFG1112@ABCDEFG_ZxNE^H`ʒ}őSnC???ghijklmnso?pqrH???M@?????vU?|j??T?E?????FD????q~?RB?P?SVU`ed]_?IJ?}a`P??????????????????????????????????????????????????????????????????????????????{???f???t?????N?y?uzxQ?||???????????????????R????????NQ?R?vm?d????????????k?o?tďH~?IKJ??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????NNNN````OOOO?III???????????????????????????????????????????????????????????@ABCDEFG??-!%./:;?@^_'"X=kp?????????NGY???????????????????????????????????????????????????????????????????`abcdefghijklmnopqrstuvwxy‚?ĂłƂǂȂɂʂ˂̂͂΂ςЂт҂ӂԂՂւׂ؂قڂۂ܂݂ނ߂@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????B???{}W?A??@???qihr~???ZX?????????????m???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????`ed]_Aghijklmnopqr???U?QR{T|~B}q????vUVt?uihW???????????????????????????????????????????????????????????????????????r???????y?????????ON???????????????????????[????H@ABCDEFOPIJK?M?NR?MZ?́`????{?|y??a??I??K?NGxtj?}uY]\~Sj?H??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????N?@S??BC?Ev??DEFF???J???M_K}??M??OB~}?Q{R|ST??????????????????????????????????????efghi}jklU?nRo?qrstuvwtyyz{|}?c΁lc[Z????????????????????????????????????????????????????vU????VVXWW?XL\???@ABCDEFYZ[\]^_`a[\noqhirJILK?ghijklmnopqrs?tucdY?i??????????????????????????????????????????????????????????IJ???????????A???M@?????????????????????????????????????????????????????????EH[ZO??????????????????]e`daHIKJ???????????????????????????????????????????HI??????????j???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? \ No newline at end of file + + !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~  +   + !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????  +   + !"#$%&'()*+,-./0123456?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????E1112ZxNE`}őSn[Y??J??`???qf????P2?X} {?MA4]??!z??n??V??j????-32?xh?v?y,k_?)?'1?@3 +3;???????????????????????????????????????????????????????????????????<:????W5???????%??|????S?t?HU?Q??.^M R&?,a?[ahw?&lwC1p?T??????????????????( ?????bHx?d{????$W +j'?_????????m??????tďH~???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????MMMM,,,,    ??????????????????????????????????????????????????????????2E??E??-!%./:;?@^_'"=7Bm?????????FNG???????????????????????????????????????????????????????????????????`abcdefghijklmnopqrstuvwxy‚?ĂłƂǂȂɂʂ˂̂͂΂ςЂт҂ӂԂՂւׂ؂قڂۂ܂݂ނ߂@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????}}??c??????K???F+*G(???N?H????????????g>l?????????????>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????,k_1?-23??}}{p?wxzc]4A|h!nF?&???j^Sa8?U[ ,3:;<=>1?@+*t???????????????????????????????????????????????????????????????????A????JG????????????????ON?.????????????????????l??m+x??DwN??2E3H  +GW??M ??J`F????'%0?Q&bH9??_ ?]????NGQ6?'R()y*V5q+M?N?. ^?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????]^_`abM5c(w?def +??hi?j,,Rs ??jkl???p???0|q + +??s?a?uc?wxyz}??????????????????????????????????????q&? h8? +-|')S0#?EJ=EM9???????????????????????????????????????????????????? {????|}3;:<2?N/~L\???E?@F*+G??FQR?i??????????????????????????????????????????????????????????D?=>???????3`}U???T*???sK????????????????????????????????????????????????????P2????^UQ?1?@2-}3 ??????????????HHpT??V!nWx?$?]Ak,_/XYZJ[?????????????????????????????????????????n??Zno?????N?????6????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????󾁋󾂾󾁣󾃚󾁌󾃟󾃥󾃦󾃶󾄁󾄂󾄃󾄄󾄅󾁍󾄉󾄊󾄋󾄌󾄍󾀺󾂰󾂱󾂲󾂳󾀁󾀂󾀃󾁢󾂿󾀏󾀐󾀬󾀯󾀼󾁅󾁟󾁫󾂧󾃀󾃁󾃂󾃃󾃄󾃅󾃆󾃇󾃈󾃉󾃊󾃋󾃌󾁓󾁛󾁪󾁺󾂏󾂕󾂞󾃍󾃎󾃏󾃐?󾃑󾃒󾃓󾃔󾁰󾂒󾂜󾃗󾃘󾃙󾁽󾂔󾂨󾂩󾂬󾃛󾃜󾃝󾃞󾀭󾁠󾃠󾃡󾃢󾃣󾃤󾀌󾀴󾁁󾂠󾃧󾃨󾃩󾂪󾃪󾃫󾃬󾃭󾃮󾂅󾂐󾃯󾃰󾃱󾃲󾃳󾃴󾃵󾃷󾃸󾃹󾃺󾃻󾃼󾃽󾃾󾁊󾁎󾂆󾃿󾄀󾁱󾄆󾄇󾄈󾄎󾄏󾄐󾄑󾄒󾄓󾄔󾄕󾄖󾄗󾄘󾄙󾄚󾄛󾄜󾄝󾄞󾄟󾀲󾁄󾁐󾁖󾁴󾂌󾂍󾂣󾄠󾄡󾄢󾄣󾄤󾄥󾄦󾄧󾄨󾄩󾄬󾄭󾄮󾄯󾄰󾀍󾀰󾀵󾁈󾁞󾁨󾁮󾁷󾁸󾁼󾂁󾂊󾂑󾂛󾂥󾂦󾁬󾂴󾂵???????????????????????????????????????????????????????????????????󾂶󾂷󾂸󾂹󾂺󾂻󾂼󾂽󾀄󾀅󾀆󾀇󾀈󾀉󾀊󾀋󾀑󾀒󾀓󾀔󾀕󾀖󾀗󾀘󾀚󾀛󾀜󾀝󾀞󾀟󾀠󾀡󾀢󾀣󾀤󾀥󾀦󾀧󾀨󾀩󾀪󾀫󾀶󾀷󾀽󾀾󾀿󾁀󾁂󾁆󾁇󾁉󾁑󾁒󾁘󾁙󾁵󾁶󾂄󾂈󾀱󾀸󾀻?󾁃󾁜󾁡󾁤󾁥󾁦󾁧󾁩󾁹󾂃󾂉󾂎󾂓󾂝󾃕󾃖󾁚󾁿󾂀󾂟󾄪󾄫󾀎󾀙󾀹󾁗󾁭󾁯󾁲󾁳󾁻󾁾󾂂󾂇󾂋󾂖󾂘󾂡󾂢󾂤󾂫󾂭󾂮󾂯󾁏󾁔󾂗󾂙󾂚󾀮󾀳󾁕󾁝󾄱󾄲󾄳󾄴󾄵󾄶󾄷󾄸󾄹󾄺󾄻󾄼󾄽󾄾󾄿󾅀󾅁󾅂󾅃󾅄󾅅󾅆󾅇󾅈󾅉󾅊??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????YZ[HIJKLMNO^_PQRSTUVWXYZ[\]^_`abcdefghi`a|jk}T~blmnopcqrs@DNtutvwud\Bvexywz{xyzfUVWX@ABCDEFGA]ghijklmnopqrs{|}~CEFGHIJKLMOPQRS?????????????????P??????D?t???VS??????????????????TIDwc????????????j???????????????????????????????????????????????????????????????????????????????@???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????H?H????????`anAB????!?!!`??????????????????????????????????????????J?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????@ABCDEFG1112@ABCDEFGcZxNEȎwH`ʒ}őSn??????ghijklmnso?pqr?HH?????????????????????|????????E???????F???????~???????VU`ed]_?IJ?????P???????????????????????????????????????????????????????????????????!!!???????????????????????t??????N???y?uzx???|?????????????????????R???????????????v??d?????????????????tďH~?IKJ??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????NNNN````OOOO?III????????????????????????????????????????????????????????????@ABCDEFG??-!%./:;?@^_'"X=kp?????????NGЊ???????????????????????????????????????????????????????????????????`abcdefghijklmnopqrstuvwxy‚?ĂłƂǂȂɂʂ˂̂͂΂ςЂт҂ӂԂՂւׂ؂قڂۂ܂݂ނ߂@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????B???{}W?A???@???qihr????ZX?????????????m???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????`ed]_A?ghijklmnopqr???????{?|~B}q????vUVt?uih?W???????????????????????????????????????????????????????????????????????r???????y?????????ON???????????????????????ID??????H@ABCDEFOPIJK????NR??MZ!?!!?́``????????y????J?P?I??K?NGxtj??uY֋󍇖???????????H????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????P????N???D?S?????Ev????VS?F???????M_?}??????B~}??{?|?????????????????????????????????????????????}??jU??R?????????t?c΁lc[Z???????????????????????????????????????????????????????????????????????????VXW?????L\???@ABCDEFЊTIDwc??[\noqhirJILK?ghijklmnopqrs?tu??Y?i??????????????????????????????????????????????????????????????IJ?????????????????????????????????????A???????@?????????????????????????????????????????????????????????????H[ZO?????????????????????]e`da?????HIKJ??????????????????????????????????????????????????????????????????H?H??????????볂`aABnj????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????󾁋󾂾󾁣󾃚󾁌󾃟󾃥󾃦󾃶󾄁󾄂󾄃󾄄󾄅󾁍󾄉󾄊󾄋󾄌󾄍󾀺󾂰󾂱󾂲󾂳󾀁󾀂󾀃󾁢󾂿󾀏󾀐󾀬󾀯󾀼󾁅󾁟󾁫󾂧󾃀󾃁󾃂󾃃󾃄󾃅󾃆󾃇󾃈󾃉󾃊󾃋󾃌󾁓󾁛󾁪󾁺󾂏󾂕󾂞󾃍󾃎󾃏󾃐?󾃑󾃒󾃓󾃔󾁰󾂒󾂜󾃗󾃘󾃙󾁽󾂔󾂨󾂩󾂬󾃛󾃜󾃝󾃞󾀭󾁠󾃠󾃡󾃢󾃣󾃤󾀌󾀴󾁁󾂠󾃧󾃨󾃩󾂪󾃪󾃫󾃬󾃭󾃮󾂅󾂐󾃯󾃰󾃱󾃲󾃳󾃴󾃵󾃷󾃸󾃹󾃺󾃻󾃼󾃽󾃾󾁊󾁎󾂆󾃿󾄀󾁱󾄆󾄇󾄈󾄎󾄏󾄐󾄑󾄒󾄓󾄔󾄕󾄖󾄗󾄘󾄙󾄚󾄛󾄜󾄝󾄞󾄟󾀲󾁄󾁐󾁖󾁴󾂌󾂍󾂣󾄠󾄡󾄢󾄣󾄤󾄥󾄦󾄧󾄨󾄩󾄬󾄭󾄮󾄯󾄰󾀍󾀰󾀵󾁈󾁞󾁨󾁮󾁷󾁸󾁼󾂁󾂊󾂑󾂛󾂥󾂦󾁬󾂴󾂵???????????????????????????????????????????????????????????????????󾂶󾂷󾂸󾂹󾂺󾂻󾂼󾂽󾀄󾀅󾀆󾀇󾀈󾀉󾀊󾀋󾀑󾀒󾀓󾀔󾀕󾀖󾀗󾀘󾀚󾀛󾀜󾀝󾀞󾀟󾀠󾀡󾀢󾀣󾀤󾀥󾀦󾀧󾀨󾀩󾀪󾀫󾀶󾀷󾀽󾀾󾀿󾁀󾁂󾁆󾁇󾁉󾁑󾁒󾁘󾁙󾁵󾁶󾂄󾂈󾀱󾀸󾀻?󾁃󾁜󾁡󾁤󾁥󾁦󾁧󾁩󾁹󾂃󾂉󾂎󾂓󾂝󾃕󾃖󾁚󾁿󾂀󾂟󾄪󾄫󾀎󾀙󾀹󾁗󾁭󾁯󾁲󾁳󾁻󾁾󾂂󾂇󾂋󾂖󾂘󾂡󾂢󾂤󾂫󾂭󾂮󾂯󾁏󾁔󾂗󾂙󾂚󾀮󾀳󾁕󾁝󾄱󾄲󾄳󾄴󾄵󾄶󾄷󾄸󾄹󾄺󾄻󾄼󾄽󾄾󾄿󾅀󾅁󾅂󾅃󾅄󾅅󾅆󾅇󾅈󾅉󾅊󾅋󾅌󾅍󾇴󾇵󾇶󾇷󾇸󾇹󾇺󾇻󾇼󾇽󾇾󾇿󾈀󾈁󾈂󾈃󾈄󾈅󾈆󾅎󾅏󾅐󾅑󾅒󾅓󾅔󾅕󾅖󾅗󾅘󾅙󾅚󾅛󾅜󾅝󾅞󾅟󾅠󾅡󾅢󾅣󾅤󾅥???????????????????????????????????????????????????????????????????󾅦󾅧󾅨󾅩󾅪󾅫󾅬󾅭󾅮󾅯󾅰󾅱󾅲󾅳󾅴󾅵󾅶󾅷󾅸󾅹󾅺󾅻󾅼󾅽󾅾󾅿󾆀󾆁󾆂󾆃󾆄󾆅󾆆󾆇󾆈󾆉󾆊󾆋󾆌󾆍󾆎󾆏󾆐󾆑󾆒󾆓󾆔󾆕󾆖󾆗󾆘󾆙󾆚󾆛󾆜󾆝󾆞󾆟󾆠󾆡󾆢󾆣󾆤?󾆥󾆦󾆧󾆨󾆩󾆪󾆫󾆬󾆭󾆮󾆯󾆰󾆱󾆲󾆳󾆴󾆵󾆶󾆷󾆸󾆹󾆺󾆻󾆼󾆽󾆾󾆿󾇀󾇁󾇂󾇃󾇄󾇅󾇆󾇇󾇈󾇉󾇊󾇋󾇌󾇍󾇎󾇏󾇐󾇑󾇒󾇓󾇔󾇕󾇖󾇗󾇘󾇙󾇚󾇛󾇜󾇝󾇞󾇟󾇠󾇡󾇢󾇣󾇤󾇥󾇦󾇧󾇨󾇩󾇪󾇫󾇬󾇭󾇮󾇯󾇰󾇱󾇲󾇳󾊼󾊽󾊾󾊿󾋀󾋁󾋂󾋃󾋄󾋅󾋆󾋇󾋈󾋉󾋊󾋋󾋌󾋍󾋎󾋏󾋐󾋑󾋒󾋓󾋔󾋕󾋖󾋗󾋘󾋙󾋚󾋛󾋜󾋝󾋞󾋟󾋠󾋡󾋢󾋣󾋤󾋥󾋦󾋧󾋨󾋩???????????????????????????????????????????????????????????????????󾋪󾋫󾋬󾋭󾋮󾋯󾋰󾋱󾋲󾋳󾋴󾋵󾋶󾋷󾋸󾋹󾋺󾋻󾋼󾋽󾋾󾋿󾌀󾌁󾌂󾌃󾌄󾌅󾌆󾌇󾌈󾌉󾌊󾌋󾌌󾌍󾌎󾌏󾌐󾌑󾌒󾌓󾌔󾌕󾌖󾌗󾌘󾌙󾌚󾌛󾌜󾌝󾌞󾌟󾌠󾌡󾌢󾌣󾌤󾌥󾌦󾌧󾌨?󾌩󾌪󾌫󾌬󾌭󾌮󾌯󾌰󾌱󾌲󾌳󾌴󾌵󾌶???????????????????????????????????????????????????????????????????????????????????????????????????????????????????YZ[HIJKLMNO^_PQRSTUVWXYZ[\]^_`abcdefghi`a|jk}T~blmnopcqrs@DNtutvwud\Bvexywz{xyzfUVWX@ABCDEFGA]ghijklmnopqrs{|}~CEFGHIJKLMOPQRS@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????@ABCDEFG1112@ABCDEFG_ZxNE^H`ʒ}őSnC???ghijklmnso?pqrH???M@?????vU?|j??T?E?????FD????q~?RB?P?SVU`ed]_?IJ?}a`P??????????????????????????????????????????????????????????????????????????????{???f???t?????N?y?uzxQ?||???????????????????R????????NQ?R?vm?d????????????k?o?tďH~?IKJ??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????NNNN````OOOO?III???????????????????????????????????????????????????????????@ABCDEFG??-!%./:;?@^_'"X=kp?????????NGY???????????????????????????????????????????????????????????????????`abcdefghijklmnopqrstuvwxy‚?ĂłƂǂȂɂʂ˂̂͂΂ςЂт҂ӂԂՂւׂ؂قڂۂ܂݂ނ߂@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????B???{}W?A??@???qihr~???ZX?????????????m???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????`ed]_Aghijklmnopqr???U?QR{T|~B}q????vUVt?uihW???????????????????????????????????????????????????????????????????????r???????y?????????ON???????????????????????[????H@ABCDEFOPIJK?M?NR?MZ?́`????{?|y??a??I??K?NGxtj?}uY]\~Sj?H??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????N?@S??BC?Ev??DEFF???J???M_K}??M??OB~}?Q{R|ST??????????????????????????????????????efghi}jklU?nRo?qrstuvwtyyz{|}?c΁lc[Z????????????????????????????????????????????????????vU????VVXWW?XL\???@ABCDEFYZ[\]^_`a[\noqhirJILK?ghijklmnopqrs?tucdY?i??????????????????????????????????????????????????????????IJ???????????A???M@?????????????????????????????????????????????????????????EH[ZO??????????????????]e`daHIKJ???????????????????????????????????????????HI??????????j???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? \ No newline at end of file diff -urN tiarra-20080510/doc/auto-reply-comparison.txt tiarra-20090206/doc/auto-reply-comparison.txt --- tiarra-20080510/doc/auto-reply-comparison.txt 1970-01-01 09:00:00.000000000 +0900 +++ tiarra-20090206/doc/auto-reply-comparison.txt 2009-02-09 22:30:11.000000000 +0900 @@ -0,0 +1,59 @@ + +自動応答系モジュールの比較 +-------------------------- + +1. 発言に対して回答を返す系. + - Auto::Alias ユーザエイリアス情報の管理を行ないます。 + - Auto::Answer 特定の発言に反応して対応する発言をする。 + - Auto::Random 特定の発言に反応してランダムな発言をします。 + - Auto::Reply 特定の発言に反応して発言をします。 + - Auto::Response データファイルの指定にしたがって反応する。 +2. 状態変化系. + - Auto::ChannelWithoutOper チャンネルオペレータ権限がなくなってしまったときに発言する。 + - Auto::Joined 特定のチャンネルに誰かがJOINする度に特定のメッセージを発言する。 + - Auto::Oper 特定の文字列を発言した人を+oする。 +3. 処理を行わせる系 + - Auto::Calc Perlの式を計算させるモジュール。 + - Auto::MesMail 伝言をメールとして送信する。 +4. 自動処理系. + - Auto::FetchTitle 発言に含まれるURLからタイトルを取得. + - Auto::Im 名前が呼ばれると、その発言をim.kayac.comに送信する + +1. 発言に対して回答を返す系. +---------------------------- + +Auto::Alias +- これは実際には応答を返すわけではなくて, + nick から名前に変換したりの共通データベースの定義. + +Auto::Answer +- 設定: tiarra.conf 内. +- 任意の発言に対してそれにマッチした発言を返す. +- IRC上で追加/削除はできない. +- 例: 案内メッセージ等. + +Auto::Random +- 設定: 外部ファイル. +- 特定の発言に対してファイルの中からランダムに1つを返す. +- IRC上で追加/削除可能. +- 例: おみくじ等. + +Auto::Reply +- 設定: 外部ファイル. +- 任意の発言に対してそれにマッチした発言を返す. +- IRC上で追加/削除可能. +- 例: キーワード反応系(編集可能). + +Auto::Response +- 設定: 外部ファイル. +- 任意の発言に対してそれにマッチした発言を返す. +- IRC上で追加/削除はできない. +- Auto::Reply のブロック単位設定(rate, mask)をエントリ単位で行えるようにした感じ. +- 例: キーワード反応系(編集不可). + +2. その他 +--------- + +別段悩まないと思うので説明は省略. + +[EOF] diff -urN tiarra-20080510/doc/macro.txt tiarra-20090206/doc/macro.txt --- tiarra-20080510/doc/macro.txt 1970-01-01 09:00:00.000000000 +0900 +++ tiarra-20090206/doc/macro.txt 2009-02-09 22:30:11.000000000 +0900 @@ -0,0 +1,63 @@ + +マクロ展開. +----------- + +#(xxx) + ==> xxx の値. +#(xxx|yyy|zzz) + ==> xxx の値か yyy の値か zzz の値. + (値はdefinedかどうかで判断される) +#(foo;FOO-%s|bar;BAR-%s) + ==> 書式付き展開. + {foo=>1} なら "FOO-1", {bar=>2} なら "BAR-2" + +展開用のライブラリ. +------------------- + +Tools::HashTools::replace_recursive($format, [\%pairs], [\&callback]); + +%pairs は $pairs{$key} = $vlaue か $pairs{$key} = \@values. +配列だった場合最初の要素が使われる. + +[\%pairs] に見つからなかった場合は[\&callback]を順番に呼び出して探索. + +便利ライブラリ. +--------------- + +Auto::AliasDB->shared->replace($userinfo, $str, key=>$value, ...) + $userinfo ::= 'nick!user@addr' + +Auto::AliasDB->shared->stdreplace($userinfo, $str, $msg, $sender, key=>$value, ...) + nick.now + user.now + host.now + prefix.now + (多分他にも?) + (date:やrandomselect:とかのフィルタ類も有効になる) + +Tools::GroupDB + $userinfo はこっちにスルーされる. + データベースファイルから, + 'user' カラム($primaryで指定される)で $userinfo を検索して + マッチしたレコードのキー/値ペアを置換キーに追加する. + そして Tools::HashTools::replace_recursive(..) に. + +日付展開. +--------- + +%Y-%m-%d とか. +Tools::DateConvert. + +EBNF +---- +大体こんな感じ: + + string := *( text | macro | '#' ) + macro := '#(' expand ( '|' expand )* ')' + expand := ( variable-name )? ( ';' ( macro | format-specifier | text-without-paren | paren-pair )* )? + variable-name := text-without-paren | paren-pair + paren-pair := '(' ( text-without-paren | paren-pair )* ')' + format-specifier := '%' char-without-paren + text-without-paren := ( char-without-paren )+ + text := /[^#]+/ + char-without-paren := /[^()]/ diff -urN tiarra-20080510/doc/module/Auto.html tiarra-20090206/doc/module/Auto.html --- tiarra-20080510/doc/module/Auto.html 2008-05-11 00:25:28.000000000 +0900 +++ tiarra-20090206/doc/module/Auto.html 2009-02-09 22:30:12.000000000 +0900 @@ -1,8 +1,8 @@ - + - + @@ -279,7 +279,7 @@

NOTE:
- 利用するにはcodereposから
+ 利用するにはCodeReposから
 module/Tools/HTTPClient.pm rev.8220
 main/Tiarra/Socket/Buffered.pm rev.8219
 以降が必要です.
@@ -292,6 +292,85 @@


+
+

Auto::FetchTitle::Plugin::ExtractHeading

+ 本文から見出しを抽出するFetchTitleプラグイン.
+
+

+Auto::FetchTitle { ... } での設定.
++ Auto::FetchTitle {
+    plugins {
+      ExtractHeading {
+        extra: name1 name2 ...
+        extra-name1 {
+          url: http://www.example.com/*
+          recv_limit: 10*1024
+          extract: re:<div id="title">(.*?)</div>
+        }
+      }
+   }
+ }
+

+ +
+
+ + +
+ + +
+

Auto::FetchTitle::Plugin::Mixi

+ Mixiにログインして見出し抽出出来るようにするFetchTitleプラグイン.
+
+

+ Auto::FetchTitle { ... } での設定.
+
+ + Auto::FetchTitle {
+    mask: #* &mixi http://*
+    plugins {
+      Mixi {
+        mixi-user: xxx
+        mixi-pass: yyy
+      }
+    }
+    conf-mixi {
+      filter-mixi {
+        url: http://mixi.jp/*
+        url: http://news.mixi.jp/*
+        type: mixi
+        timeout: 10
+        #閲覧可能なコミュニティの指定.
+        #mixi-community: 0
+        #閲覧可能なユーザの指定.
+        #指定したユーザには足跡踏んで見に行きます.
+        #mixi-friend: 0
+        #閲覧可能にしていないページを表示したときのメッセージ.
+        #要求されたページを #(url) で展開できます.
+        #mixi-noperm-msg: not permitted #(url).
+      }
+    }
+  }
+
+ アカウント情報は plugins Mixi に記述.
+ mixi-pass には {B}bbbb でBASE64エンコード値も可能.
+
+ newsだけしか使わない場合でも, ログイン処理が必要なので
+ mixi.jp 内のいくつかのURLはこのプラグインで処理する必要があります.
+   url: http://news.mixi.jp/*
+   url: http://mixi.jp/issue_ticket.pl?*
+   url: http://mixi.jp/login.pl
+   url: http://mixi.jp/check.pl?*
+ (それぞれ, ニュースページ, ログイン処理, エラー検出, 途中経路になります.)
+

+ +
+
+ + +
+ +

Auto::Im

名前が呼ばれると、その発言をim.kayac.comに送信する
@@ -314,6 +393,7 @@

im.kayac.com に送るメッセージのフォーマットを指定します。
デフォルト値: [tiarra][#(channel):#(nick.now)] #(text)
+#(channel) のかわりに #(raw_channel) を利用するとネットワーク名がつきません。

format:[tiarra][#(channel):#(nick.now)] #(text)

@@ -583,6 +663,39 @@


+
+

Auto::Outputz

+ チャンネルの発言文字数を outputz に送信する
+
+

+復活の呪文。
+

+
key:some secret
+

+送信対象にするコマンドの設定。
+省略された場合は PRIVMSG 。
+パラメータ1が送信先、パラメータ2が本文でなければ動作しないので、
+動作するコマンドは PRIVMSG/NOTICE/TOPIC/PART 程度。
+

+
command:PRIVMSG
+

+各チャンネルのURIの設定。
+記述された順序で検索されるので、全てのチャンネルにマッチする"*"などは最後に書かなければならない。
+フォーマットは次の通り。
+channel: <URI> (<チャンネル名> / 'priv')@<ネットワーク名>
+#(channel) はチャンネル名に、 #(channel_short) はネットワークなしの
+チャンネル名に、 #(network) はネットワーク名にそれぞれ置き換えられる。
+また、危険な文字は自動的にエスケープされる。
+

+
channel:http://#(network).irc.example.com/#(channel_short) *
+ +
+
+ + +
+ +

Auto::Random

特定の発言に反応してランダムな発言をします。
@@ -679,74 +792,132 @@

使用するブロックの定義。
+省略すると std を使用.
+複数個の blocks の指定も可能.

blocks:std
std

+1つの応答ブロックの定義.
+一応全ての項目が省略可能ではあるけれど,
+通常は最低限 file と file-encoding を使用する.
+IRCで応答の追加削除等を行いたいときにはそれに更に設定を追加する形.
+(IRC上で応答の追加削除は行うが保存はしない時に限ってfileを省略可能.)
+

+

+機能:
+- 通常応答(mask)
+- 登録数確認(count-query/mask)
+- 反応確認(request/modifier)
+- 反応追加(add/modifier)
+- 反応削除(remove/modifier)
+通常応答以外は設定を省略することで機能を無効にできます。
+

+

データファイルと文字コードを指定します。
-ファイルの中では一行に一つの"反応:メッセージ"を書いて下さい。
+ファイルの中では一行に一つの"反応マスク:メッセージ"を書いて下さい。

file:reply.txt
file-encoding:euc

-反応チェックを行うキーワードを指定します。
-実際の指定方法は、「<requestで指定したキーワード> <チェックしたい発言>」です。
+1つの発言で複数の反応マスクにマッチする場合,
+どれにマッチするかは未定義です.
+ただ, どちらか1つにのみマッチします.

-
request:反応チェック

-request に反応するときのフォーマットを指定します。
-#(key) がキーワード、 #(message) が発言に置換されます。
+同じ反応マスクに複数個のメッセージが記述してあった場合の処理.
+multivalue: random #==> ランダムに1つ選択.
+multivalue: all #==> 全て返す.
+multivalue: seq #==> 順番に1つずつ返す.
+省略時及び認識できなかったときは random.

-
reply-format:「#(key)」という発言に「#(message)」と反応します。
+
multivalue:random

-request に反応する最大個数を指定します。
-あまり大きな値を指定すると、アタックが可能になったり、ログが流れて邪魔なので注意してください。
+返す最大行数.
+multivalue: all の時のみ有効.
+(それ以外の時は1行しか返さない)
+デフォルトは 5 行まで.

-
max-reply:5
+
multivalue-limit:5
+

+反応する人のマスク。
+通常応答と登録数の返答時にチェックされる。
+

+
mask:* *!*@*
+

+plum: mask: *!*@*
+

+

+マッチした1つの反応マスクが実際に発言に反応する確率を指定します。
+百分率です。省略された場合は100と見做されます。
+

+
rate:100

メッセージの登録数を返答するキーワードを指定します。
+省略するとこの機能は無効になります。
+指定したときだけこの機能が有効になります。
+mask で許可された人(通常応答を返す人)が使えます。

count-query:反応登録数

メッセージの登録数を返答するときの反応を指定します。
formatで指定できるものと同じです。#(count)は登録数になります。
+count-query を指定したときのみ必要。

count-format:反応は#(count)件登録されています。

-反応する人のマスク。
-

-
mask:* *!*@*
-

-plum: mask: *!*@*
+メッセージを追加するキーワードを指定します。
+ここで指定したキーワードを発言すると、新しいメッセージを追加します。
+実際の追加方法は「<addで指定したキーワード> <追加するメッセージ>」です。
+省略するとこの機能は無効になります。
+指定したときだけこの機能が有効になります。
+modifier で許可された人だけ使えます。

+
add:反応追加

反応が追加されたときの反応を指定します。
formatで指定できるものと同じです。#(message)は追加されたメッセージになります。

added-format:#(name|nick.now): #(key) に対する反応 #(message) を追加しました。

+メッセージを削除するキーワードを指定します。
+実際の削除方法は「<removeで指定したキーワード> <削除するキーワード>」です。
+省略するとこの機能は無効になります。
+指定したときだけこの機能が有効になります。
+modifier で許可された人だけ使えます。
+

+
remove:反応削除
+

メッセージが削除されたときの反応を指定します。
formatで指定できるものと同じです。#(message)は削除されたメッセージになります。

removed-format:#(name|nick.now): #(key) #(message;に対する反応 %s|;) を #(count) 件削除しました。

-発言に反応する確率を指定します。百分率です。省略された場合は100と見做されます。
+反応の確認を行うためのキーワードを指定します。
+通常応答と違って, multivalue-limit の制限を受けずに全てのマッチした応答を返します。
+実際の指定方法は、「<requestで指定したキーワード> <チェックしたい発言>」です。
+省略するとこの機能は無効になります。
+指定したときだけこの機能が有効になります。
+modifier で許可された人だけ使えます。

-
rate:100
+
request:反応チェック

-メッセージを追加するキーワードを指定します。
-ここで指定したキーワードを発言すると、新しいメッセージを追加します。
-実際の追加方法は「<addで指定したキーワード> <追加するメッセージ>」です。
+request に反応するときのフォーマットを指定します。
+#(key) がキーワード、 #(message) が発言に置換されます。
+request を指定したときのみ必要。

-
add:反応追加
+
reply-format:「#(key)」という発言に「#(message)」と反応します。

-メッセージを削除するキーワードを指定します。
-実際の削除方法は「<removeで指定したキーワード> <削除するキーワード>」です。
+request に反応する最大個数(反応マスクの数)を指定します。
+(1つの反応マスクに対応するメッセージの数は制限されません。)
+あまり大きな値を指定すると、アタックが可能になったり、ログが流れて邪魔なので注意してください。
+通常の反応には関与しません。また、応答の行数ではありません。

-
remove:反応削除
+
max-reply:5

-addとremoveを許可する人。省略された場合は「* *!*@*」と見做します。
+編集系コマンド, add とremove と request を許可する人。
+省略された場合は「* *!*@*」(全員許可)と見做します。

modifier:* *!*@*

@@ -821,6 +992,10 @@

  • Auto::FetchTitle
  • +
  • Auto::FetchTitle::Plugin::ExtractHeading
  • + +
  • Auto::FetchTitle::Plugin::Mixi
  • +
  • Auto::Im
  • Auto::Joined
  • @@ -829,6 +1004,8 @@
  • Auto::Oper
  • +
  • Auto::Outputz
  • +
  • Auto::Random
  • Auto::Reply
  • diff -urN tiarra-20080510/doc/module/CTCP.html tiarra-20090206/doc/module/CTCP.html --- tiarra-20080510/doc/module/CTCP.html 2008-05-11 00:25:28.000000000 +0900 +++ tiarra-20090206/doc/module/CTCP.html 2009-02-09 22:30:12.000000000 +0900 @@ -1,8 +1,8 @@ - + - + @@ -39,7 +39,7 @@ クライアントが送信した CTCP DCC のアドレスを変換する。

    - CTCP DCC に指定されているアドレスを、 tiarra で取得したものに
    + CTCP DCC に指定されているアドレスを、 Tiarra で取得したものに
     書き換えます。(EXPERIMENTAL)

     IPv4 のみサポートしています。
    @@ -72,7 +72,7 @@

    クライアントソケットのリモートアドレスを取ります。
    -client [this address]<-> tiarra <-> server
    +client [this address]<-> Tiarra <-> server

    dns @@ -99,16 +99,16 @@

     リゾルバの選び方

    -  * tiarra を動作させているサーバとインターネットの間にルータ等があり、
    +  * Tiarra を動作させているサーバとインターネットの間にルータ等があり、
        グローバルアドレスがない場合
          *-socket は役に立ちません。 http を利用してください。
          適当な DDNS を持っていればdns も良いでしょう。

    -  * tiarra がレンタルサーバなどLAN上にないサーバで動作している場合
    +  * Tiarra がレンタルサーバなどLAN上にないサーバで動作している場合
          server-socket, http は役に立ちません。
          client-socket がお勧めです。

    -  * tiarra がLAN上にあり、グローバルアドレスのついているホストで
    +  * Tiarra がLAN上にあり、グローバルアドレスのついているホストで
        動作している場合
          client-socket は役に立ちません。
          server-socket がお勧めです。
    diff -urN tiarra-20080510/doc/module/Channel.html tiarra-20090206/doc/module/Channel.html --- tiarra-20080510/doc/module/Channel.html 2008-05-11 00:25:28.000000000 +0900 +++ tiarra-20090206/doc/module/Channel.html 2009-02-09 22:30:12.000000000 +0900 @@ -1,8 +1,8 @@ - + - + @@ -61,7 +61,7 @@ 注意点
    - この機能はまだ実装途中です。いろいろな不具合があるかもしれません。むしろきっとあります。
    - サーバがわとの通信に割り込みますのでログにもとられません。
    -- この機能を使っている tiarra より上流に multi-server-mode な tiarra を置かないでください。
    +- この機能を使っている Tiarra より上流に multi-server-mode な Tiarra を置かないでください。

    チャンネルの定義。
    diff -urN tiarra-20080510/doc/module/Client.html tiarra-20090206/doc/module/Client.html --- tiarra-20080510/doc/module/Client.html 2008-05-11 00:25:28.000000000 +0900 +++ tiarra-20090206/doc/module/Client.html 2009-02-09 22:30:12.000000000 +0900 @@ -1,8 +1,8 @@ - + - + @@ -125,6 +125,30 @@


    +
    +

    Client::List

    + Clientの一覧を取得.
    +
    +

    +接続しているクライアントを一覧.
    +/clientlist を投げると, その時に接続しているクライアントの一覧を返す.
    +出力例:
    +clientlist
    +  *** 1 client
    +  *** [1] 127.0.0.1:23695
    +

    +

    +一覧を返すトリガーとするコマンド.
    +

    +
    command:clientlist
    + +
    +
    + + +
    + +

    Client::PatchworkMessage

    IRC メッセージにちょっと変更を加えて、クライアントのバグを抑制する
    @@ -248,6 +272,8 @@
  • Client::GetVersion
  • +
  • Client::List
  • +
  • Client::PatchworkMessage
  • Client::ProtectMyself
  • diff -urN tiarra-20080510/doc/module/Debug.html tiarra-20090206/doc/module/Debug.html --- tiarra-20080510/doc/module/Debug.html 2008-05-11 00:25:28.000000000 +0900 +++ tiarra-20090206/doc/module/Debug.html 2009-02-09 22:30:12.000000000 +0900 @@ -1,8 +1,8 @@ - + - + diff -urN tiarra-20080510/doc/module/Log.html tiarra-20090206/doc/module/Log.html --- tiarra-20080510/doc/module/Log.html 2008-05-11 00:25:28.000000000 +0900 +++ tiarra-20090206/doc/module/Log.html 2009-02-09 22:30:12.000000000 +0900 @@ -1,8 +1,8 @@ - + - + @@ -101,9 +101,19 @@ channel: others *
    この例では、#IRC談話室@ircnetのログはIRCDanwasitu/%Y.%m.%d.txtに、
    それ以外(privも含む)のログはothers/%Y.%m.%d.txtに保存される。
    +#(channel) はチャンネル名に展開される。
    +(古いバージョンだと展開されずにそのままディレクトリ名になってしまいます。)

    channel:priv priv
    +
    channel:#(channel) *
    channel:others *
    +

    +ファイル名のエンコーディング.
    +指定可能な値は, utf8, sjis, euc, jis, ascii.
    +ascii は実際には utf8 と同等で8bit部分が全てquoted-printableされる.
    +デフォルトはWindowsではsjis, それ以外では utf8.
    +

    +
    filename-encoding:utf8
    diff -urN tiarra-20080510/doc/module/System.html tiarra-20090206/doc/module/System.html --- tiarra-20080510/doc/module/System.html 2008-05-11 00:25:28.000000000 +0900 +++ tiarra-20090206/doc/module/System.html 2009-02-09 22:30:12.000000000 +0900 @@ -1,8 +1,8 @@ - + - + @@ -51,6 +51,8 @@

    対応している箇所.
    ModuleManager / reload_modules_if_modified / r3004 => r8809
    +LinedINETSocket / connect / r3004 => r8930
    +Configuration::Block / equals / r3004 => r11868

    /livepatch check で確認.
    @@ -276,9 +278,9 @@ WebClient を起動させる場所の指定.

    bind-addr:127.0.0.1
    -
    bind-port:8668
    +
    bind-port:8667
    path:/irc
    -
    css:/style/irc-style.css
    +
    css:/irc/style/style.css

    上の設定をapacheでReverseProxyさせる場合, httpd.conf には次のように設定.
     ProxyPass /irc/ http://localhost:8667/irc/
    @@ -290,6 +292,7 @@

    ReverseProxy 利用時の追加設定.
    接続元が全部プロキシサーバになっちゃうのでその対応.
    +ReverseProxy 使わず直接公開の場合は不要.

    extract-forwarded-for:127.0.0.1

    @@ -317,8 +320,15 @@

    host:127.0.0.1

    認証設定.
    +auth: <user> <pass>
    +auth: :basic <user> <pass>
    +auth: :softbank <端末ID>
    +auth: :softbank <UID>
    +auth: :au <SUBNO>
    +各値(<pass>等)には {MD5}xxxx や {B}xxx や {CRYPT}xxx を利用可能.
    +そのままべた書きも出来るけれど.

    -
    auth:user pass
    +
    auth::basic user pass

    公開するチャンネルの指定.

    @@ -350,10 +360,22 @@

    sort-order:asc

    -発言BOXで名前指定しなかったときのデフォルトの名前.
    -mode: shared の時に使われる.
    +name-default 設定は VERSION 0.05 で廃止されました.
    +# 発言BOXで名前指定しなかったときのデフォルトの名前.
    +# mode: shared の時に使われる.
    +-name-default: (noname)
    +

    +

    +外部にTiarraさんを使っているときに, そこのネットワークを切り出して表示する.
    +exteact-network: <netname> <remote-sep>
    +<netname> ::= このTiarraさんから見たときの外部Tiarraさんのネットワーク名.
    +              (このtiarra.confで指定しているネットワーク名)
    +<remote-sep> ::= 外部Tiarraさんで使っているセパレータ.
    +                 (こっちはこのtiarra.confのではないです)
    +                 省略すると @ と仮定.

    -
    name-default:(noname)
    +
    exteact-network:tiarra
    +
    exteact-network:tiarra @
    diff -urN tiarra-20080510/doc/module/UNCLASSIFIED.html tiarra-20090206/doc/module/UNCLASSIFIED.html --- tiarra-20080510/doc/module/UNCLASSIFIED.html 2008-05-11 00:25:28.000000000 +0900 +++ tiarra-20090206/doc/module/UNCLASSIFIED.html 2009-02-09 22:30:12.000000000 +0900 @@ -1,8 +1,8 @@ - + - + @@ -19,8 +19,8 @@
    -

    Skelton

    - Skelton for tiarra-module.
    +

    Skelton

    + Skeleton for tiarra-module.

    モジュールの説明をこのあたりに書く.
    @@ -43,7 +43,7 @@

    diff -urN tiarra-20080510/doc/module/User.html tiarra-20090206/doc/module/User.html --- tiarra-20080510/doc/module/User.html 2008-05-11 00:25:28.000000000 +0900 +++ tiarra-20090206/doc/module/User.html 2009-02-09 22:30:12.000000000 +0900 @@ -1,8 +1,8 @@ - + - + diff -urN tiarra-20080510/doc/module-reload.txt tiarra-20090206/doc/module-reload.txt --- tiarra-20080510/doc/module-reload.txt 1970-01-01 09:00:00.000000000 +0900 +++ tiarra-20090206/doc/module-reload.txt 2009-02-09 22:30:11.000000000 +0900 @@ -0,0 +1,108 @@ + +Tiarra モジュールのリロードに関する改善案 +========================================= + +(2008/05/25) + +現状 +---- + +destruct()/new($config). + +破棄->再生成の間で値を渡すにはBBSを利用する必要がある. +(もしくはどこかサブパッケージとかにおいちゃう) +けれどunloadだった場合にはそれが破棄されない. +(BBSはそもそも削除がない) + (値に undef を指定したら BBS を消すようにしました) + +また, 設定変更とモジュール変更が同時に起こった場合, +モジュールの再起動はかからずに設定の変更のみで処理される. + +案1 +--- + +config_reload() メソッドの追加. +defualt: destruct() & new(). + +懸念点: + 設定の変更でのみ. + モジュールの再起動時だと, destruct() は古いモジュール, + new() は新しいモジュールという処理が行えない. + +ステータス: + とりあえず実装中. + $mod->config_reload($old_config) を呼ぶ. + $mod->config は既に新しい config になっている. + モジュール再読込と設定の変更が同時の場合は: + + $mod->destruct (古いモジュール/新しい config), + mod->new (新しいモジュール/新しい config) + +案2 +--- + +destruct(\%info) & (モジュールreload) & new($config, \%info). +$info->{reason}{load} = $bool; +$info->{reason}{config} = $bool; +$info->{reason}{module} = $bool; +$info->{reason}{unload} = $bool; +$info->{stash} = 自由に使うえりあ. + +懸念点: + モジュールの読み込みに失敗した場合どうするか. + 関連モジュールのインスタンスは解放できるか(デストラクタコードが残ってるか) + stashをしばらく保持?->いつ解放するか + タイマー系インスタンス/コードの扱い. + (Code=>sub{ $this->xxx() } 系が使えなくなる) + +案3 +--- + +BBSを用いる. +main いじらなくていいのでお手軽. + +とりあえず Tools::Rreload をつくってみた. +動作確認してないからうごくかはわかんない. + + my $my_key = __PACKAGE__; + + # At destruct(). + Tools::Reload->store($my_key, $value); + + # At new(). + my $value = Tools::Reload->fetch($my_key); + if( !$value ) + { + # new loading. + }else + { + # reloading. + } + +案4 +--- + +思いついたらふやす. + + +補足:タイマー関係 +----------------- + +自分で保持するのも面倒なので, +モジュールに紐づけるオプションなりなんなり. +ModuleManager が $module->destruct() よんだあともまだ残ってたら捨てるとか. + +とりあえず: + ModuleManager の ->add_module_object($module, @objects) を呼んで登録, + ->remove_module_object($module, @objects) を呼んで解除. + weakref を使ったので, 他のモジュールにくっつけ直すとかでなければ, + 解除は必須ではないです. + で, destruct よんだあともまだ残っていたときは + $object->module_destruct($module) という感じでよぶ。 + $module はインスタンスまたはクラス名で, ハッシュのキーはクラス名だけど, + ->module_destruct にくる $module がインスタンスかクラス名かはわかりません. + Module に ->_add_object, ->_remove_object を増やして簡単に登録できるようにした. + module_destruct を Timer, Hook, ExternalSocket, Tiarra::Socket, それから + Tiarra::WrapMainLoop に実装した. あと何かありましたっけ. + +[EOF] diff -urN tiarra-20080510/doc/module-toc.html tiarra-20090206/doc/module-toc.html --- tiarra-20080510/doc/module-toc.html 2008-05-11 00:25:28.000000000 +0900 +++ tiarra-20090206/doc/module-toc.html 2009-02-09 22:30:12.000000000 +0900 @@ -1,8 +1,8 @@ - + - + @@ -19,7 +19,7 @@ UNCLASSIFIED 未分類のモジュール
      -
    • Skelton Skelton for tiarra-module.
    • +
    • Skelton Skeleton for tiarra-module.
    @@ -38,6 +38,10 @@
  • Auto::FetchTitle 発言に含まれるURLからタイトルを取得.
  • +
  • Auto::FetchTitle::Plugin::ExtractHeading 本文から見出しを抽出するFetchTitleプラグイン.
  • + +
  • Auto::FetchTitle::Plugin::Mixi Mixiにログインして見出し抽出出来るようにするFetchTitleプラグイン.
  • +
  • Auto::Im 名前が呼ばれると、その発言をim.kayac.comに送信する
  • Auto::Joined 特定のチャンネルに誰かがJOINする度に特定のメッセージを発言する。
  • @@ -46,6 +50,8 @@
  • Auto::Oper 特定の文字列を発言した人を+oする。
  • +
  • Auto::Outputz チャンネルの発言文字数を outputz に送信する
  • +
  • Auto::Random 特定の発言に反応してランダムな発言をします。
  • Auto::Reply 特定の発言に反応して発言をします。
  • @@ -113,6 +119,8 @@
  • Client::GetVersion クライアントに CTCP Version を発行してバージョン情報を得る
  • +
  • Client::List Clientの一覧を取得.
  • +
  • Client::PatchworkMessage IRC メッセージにちょっと変更を加えて、クライアントのバグを抑制する
  • Client::ProtectMyself 意図せず自分のニックが変わってしまうのを防止する
  • diff -urN tiarra-20080510/doc-src/README tiarra-20090206/doc-src/README --- tiarra-20080510/doc-src/README 2008-05-11 00:25:26.000000000 +0900 +++ tiarra-20090206/doc-src/README 2009-02-09 22:30:11.000000000 +0900 @@ -48,7 +48,7 @@ # リロードを実行するコマンド名。省略されるとコマンドを追加しません。 # 例えば"load"を設定すると、"/load"と発言しようとした時にリロードを実行します。 # この時コマンドはTiarraが握り潰すので、IRCプロトコル上で定義された -# コマンド名を設定すべきではありません。 +# コマンド名を設定すべきではありません。 command: load =cut diff -urN tiarra-20080510/doc-src/conf-main.tdoc tiarra-20090206/doc-src/conf-main.tdoc --- tiarra-20080510/doc-src/conf-main.tdoc 2008-05-11 00:25:26.000000000 +0900 +++ tiarra-20090206/doc-src/conf-main.tdoc 2009-02-09 22:30:11.000000000 +0900 @@ -1,5 +1,5 @@ -*- outline -*- -$Id: conf-main.tdoc 11365 2008-05-10 14:58:28Z topia $ +$Id: conf-main.tdoc 15771 2008-07-13 23:55:21Z drry $ perlのソースに使うpodパーサを流用しているので、package文と=pod〜=cutで書く必要があります。 ヘッダのinfo-is-ommitedとno-switchはどちらも値を真に定義しなければなりません。 @@ -50,7 +50,7 @@ # crypt は ./tiarra --make-password で行えます。 tiarra-password: xl7cflIcH9AwE -# 外部プログラムからtiarraをコントロールする為のUNIXドメインソケットの名前。 +# 外部プログラムからTiarraをコントロールする為のUNIXドメインソケットの名前。 # 例えば"foo"を指定した場合、ソケット/tmp/tiarra-control/fooが作られる。 # 省略された場合はこの機能を無効とする。 # また、非UNIX環境ではそもそもUNIXドメインソケットが利用可能でないため、 @@ -67,7 +67,7 @@ client-in-encoding: jis client-out-encoding: jis -# Tiarraは標準出力に様々なメッセージを出力するが、その文字コードを指定する。省略時にはeucとなる。 +# Tiarraは標準出力に様々なメッセージを出力するが、その文字コードを指定する。省略時にはutf8となる。 # ただしtiarra.confのパースが完了するまでは文字コードの変換は行なわれない(つまりこの設定が有効にならない)ことに注意して下さい。 stdout-encoding: utf8 @@ -103,7 +103,7 @@ -ipv4-bind-addr: 0.0.0.0 -ipv6-bind-addr: ::0 -# tiarra が、 001 や 002 や、 recent log を送信するときなどに使う prefix +# Tiarra が、 001 や 002 や、 recent log を送信するときなどに使う prefix # を指定します。 hostname や fqdn っぽいものを指定すると良いかもしれません。 # デフォルトは tiarra です。普通変える必要はありません。 -sysmsg-prefix: tiarra @@ -222,9 +222,16 @@ info-is-omitted: 1 no-switch: 1 -# サーバーのホストとポート。省略不可。 -host: irc.nara.wide.ad.jp -port: 6663 + +# サーバーのホストとポートを指定。複数行可。(host/port が指定されていない場合は)省略不可。 +# 同じサーバの複数のポート指定は順番に試すので、大量に書かない方がよい。 +server: irc.nara.wide.ad.jp 6662 6663 +server: irc.fujisawa.wide.ad.jp 6661 6664 + +# サーバーのホストとポート。(server が指定されていない場合は)省略不可。 +# server を指定した場合は server が優先されます。 +-host: irc.nara.wide.ad.jp +-port: 6663 # general/userで設定したユーザ名を使わずに、各ネットワークで独自のユーザ名を使用する事も可能。 # 省略されたら当然、general/userで設定したものが使われる。 diff -urN tiarra-20080510/doc-src/contents.html tiarra-20090206/doc-src/contents.html --- tiarra-20080510/doc-src/contents.html 2008-05-11 00:25:26.000000000 +0900 +++ tiarra-20090206/doc-src/contents.html 2009-02-09 22:30:11.000000000 +0900 @@ -1,8 +1,8 @@ - + - + diff -urN tiarra-20080510/doc-src/module-toc.html tiarra-20090206/doc-src/module-toc.html --- tiarra-20080510/doc-src/module-toc.html 2008-05-11 00:25:26.000000000 +0900 +++ tiarra-20090206/doc-src/module-toc.html 2009-02-09 22:30:11.000000000 +0900 @@ -1,8 +1,8 @@ - + - + diff -urN tiarra-20080510/doc-src/sample.conf.in tiarra-20090206/doc-src/sample.conf.in --- tiarra-20080510/doc-src/sample.conf.in 2008-05-11 00:25:26.000000000 +0900 +++ tiarra-20090206/doc-src/sample.conf.in 2009-02-09 22:30:11.000000000 +0900 @@ -1,10 +1,10 @@ # -*- tiarra-conf -*- # ----------------------------------------------------------------------------- -# $Id: sample.conf.in 11365 2008-05-10 14:58:28Z topia $ +# $Id: sample.conf.in 15771 2008-07-13 23:55:21Z drry $ # ----------------------------------------------------------------------------- # tiarra.conf サンプル # -# tiarraは起動時に全ての設定をこのファイルから取得します。 +# Tiarraは起動時に全ての設定をこのファイルから取得します。 # このファイルの文字コードは任意ですが、改行コードはLFもしくはCRLFでなければなりません。 # # 半角の#で始まる行はコメントとして無視されます。 diff -urN tiarra-20080510/main/BulletinBoard.pm tiarra-20090206/main/BulletinBoard.pm --- tiarra-20080510/main/BulletinBoard.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/main/BulletinBoard.pm 2009-02-09 22:30:09.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: BulletinBoard.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: BulletinBoard.pm 13830 2008-06-13 13:45:55Z topia $ # ----------------------------------------------------------------------------- # モジュール間の情報伝達に使われるクラス。 # インスタンスは共有される。 @@ -22,7 +22,11 @@ sub set { my ($class_or_this,$key,$value) = @_; my $this = $class_or_this->_this; - $this->{table}->{$key} = $value; + if (defined $value) { + $this->{table}->{$key} = $value; + } else { + delete $this->{table}->{$key}; + } $this; } @@ -39,13 +43,13 @@ sub AUTOLOAD { # $board->foo_bar => $board->get('foo-bar') # $board->foo_bar('foo') => $board->set('foo-bar','foo'); - my ($class_or_this,$newvalue) = @_; + my $class_or_this = shift; my $this = $class_or_this->_this; (my $key = $AUTOLOAD) =~ s/.+?:://g; $key =~ s/_/-/g; - if (defined $newvalue) { - $this->set($key,$newvalue); + if (@_ > 0) { + $this->set($key, @_); } else { $this->get($key); diff -urN tiarra-20080510/main/Configuration/Block.pm tiarra-20090206/main/Configuration/Block.pm --- tiarra-20080510/main/Configuration/Block.pm 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/main/Configuration/Block.pm 2009-02-09 22:30:09.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Block.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Block.pm 14151 2008-06-16 15:47:01Z topia $ # ----------------------------------------------------------------------------- package Configuration::Block; use strict; @@ -61,24 +61,20 @@ return undef; } # キーの数 - my @this_keys = keys %{$this->[TABLE]}; - my @that_keys = keys %{$that->[TABLE]}; + my @this_keys = sort keys %{$this->[TABLE]}; + my @that_keys = sort keys %{$that->[TABLE]}; if (@this_keys != @that_keys) { return undef; } - # 各要素 - my $size = @this_keys; - for (my $i = 0; $i < $size; $i++) { - # キー - if ($this_keys[$i] ne $that_keys[$i]) { - return undef; - } + my $walk; + $walk = sub { + my ($this_value, $that_value) = @_; + # 値の型 - my $this_value = $this->[TABLE]->{$this_keys[$i]}; - my $that_value = $that->[TABLE]->{$that_keys[$i]}; if (ref($this_value) ne ref($that_value)) { return undef; } + # 値 if (ref($this_value) eq 'ARRAY') { # 配列なので要素数と全要素を比較。 @@ -87,29 +83,47 @@ } my $valsize = @$this_value; for (my $j = 0; $j < $valsize; $j++) { - if ($this_value->[$j] ne $that_value->[$j]) { + if (!$walk->($this_value->[$j], $that_value->[$j])) { return undef; } } } elsif (UNIVERSAL::isa($this_value,'Configuration::Block')) { # ブロックなので再帰的に比較。 - return $this_value->equals($that_value); + if (!$this_value->equals($that_value)) { + return undef; + } } else { if ($this_value ne $that_value) { return undef; } } + 1; + }; + + # 各要素 + my $size = @this_keys; + for (my $i = 0; $i < $size; $i++) { + # キー + if ($this_keys[$i] ne $that_keys[$i]) { + return undef; + } + # 値の型 + my $this_value = $this->[TABLE]->{$this_keys[$i]}; + my $that_value = $that->[TABLE]->{$that_keys[$i]}; + if (!$walk->($this_value, $that_value)) { + return undef; + } } return 1; } -sub eval_code { +sub _eval_code { # 渡された文字列中の、全ての%CODE{ ... }EDOC%を評価して返す。 my ($this,$str) = @_; - if (ref($str)) { + if (!defined($str) || ref($str)) { return $str; # 文字列でなかったらそのまま返す。 } @@ -130,15 +144,54 @@ $evaluated; } +sub _coerce_to_block { + my ($this, $key, $value) = @_; + + if (ref($value) and UNIVERSAL::isa($value, 'Configuration::Block')) { + return $value; + } + else { + my $tmp_block = Configuration::Block->new($key); + $tmp_block->set($key, $value); + return $tmp_block; + } +} + sub get { - my ($this,$key,$option) = @_; + my $this = shift; + my $key = shift; + my %option; + if (@_) { + @option{@_} = (1) x @_; + } + + if ($option{all}) { + # list context + my @values = $this->_get($key, %option); + return map { + $option{block} ? $this->_coerce_to_block($key, $_) : $_; + } map { + $this->_eval_code($_); + } @values; + } else { + # scalar context + my $value = $this->_eval_code($this->_get($key, %option)); + if ($option{block}) { + $value = $this->_coerce_to_block($key, $value); + } + return $value; + } +} + +sub _get { + my ($this, $key, %option) = @_; unless (exists $this->[TABLE]->{$key}) { # そのような値は定義されていない。 - if ($option && $option eq 'all') { + if ($option{all}) { return (); } - elsif ($option and $option eq 'block') { + elsif ($option{block}) { return Configuration::Block->new($key); } else { @@ -147,43 +200,20 @@ } my $value = $this->[TABLE]->{$key}; - if ($option && $option eq 'all') { - if (ref($value) eq 'ARRAY') { - return map { - $this->eval_code($_); - } @{$value}; # 配列リファなら逆参照して返す。 - } - else { - return $this->eval_code($value); - } - } - elsif ($option && $option eq 'random') { - if (ref($value) eq 'ARRAY') { - # 配列リファならランダムに選んで返す - return $this->eval_code( - $value->[int(rand(0xffffffff)) % @$value]); - } - else { - return $this->eval_code($value); - } - } - elsif ($option and $option eq 'block') { - if (ref($value) and UNIVERSAL::isa($value, 'Configuration::Block')) { - return $value; - } - else { - my $tmp_block = Configuration::Block->new($key); - $tmp_block->set($key, $value); - return $tmp_block; - } + if (ref($value) ne 'ARRAY') { + # 配列のリファレンスでなければそのまま返す。 + return $value; + } elsif ($option{all}) { + # 逆参照して返す。 + return @{$value}; + } + elsif ($option{random}) { + # ランダムに選んで返す + return $value->[int(rand(0xffffffff)) % @$value]; } else { - if (ref($value) eq 'ARRAY') { - return $this->eval_code($value->[0]); # 配列リファなら先頭の値を返す。 - } - else { - return $this->eval_code($value); - } + # 先頭の値を返す。 + return $value->[0]; } } @@ -220,26 +250,30 @@ my ($this,$encoding) = @_; my $unicode = Tiarra::Encoding->new; + my $walk; + $walk = sub { + my $value = shift; + + if (ref($value) eq 'ARRAY') { + # 配列なので中身を全て変換。 + my @newarray = map { + $walk->($_); + } @$value; + \@newarray; + } + elsif (UNIVERSAL::isa($value, 'Configuration::Block')) { + # ブロックなので再帰的にコード変換。 + $value->reinterpret_encoding($encoding); + } + else { + $unicode->set($value, $encoding)->utf8 + } + }; + my $newtable = {}; while (my ($key,$value) = each %{$this->[TABLE]}) { my $newkey = $unicode->set($key,$encoding)->utf8; - my $newvalue = do { - if (ref($value) eq 'ARRAY') { - # 配列なので中身を全てコード変換。 - my @newarray = map { - $unicode->set($_,$encoding)->utf8; - } @$value; - \@newarray; - } - elsif (UNIVERSAL::isa($value,'Configuration::Block')) { - # ブロックなので再帰的にコード変換。 - $value->reinterpret_encoding($encoding); - } - else { - $unicode->set($value,$encoding)->utf8; - } - }; - $newtable->{$newkey} = $newvalue; + $newtable->{$newkey} = $walk->($value);; } $this->[TABLE] = $newtable; @@ -247,8 +281,8 @@ } sub AUTOLOAD { - my ($this,$option) = @_; - + my ($this,@options) = @_; + if ($AUTOLOAD =~ /::DESTROY$/) { # DESTROYは伝達させない。 return; @@ -256,7 +290,7 @@ (my $key = $AUTOLOAD) =~ s/.+?:://g; $key =~ s/_/-/g; - return $this->get($key,$option); + return $this->get($key,@options); } 1; diff -urN tiarra-20080510/main/Configuration/Parser.pm tiarra-20090206/main/Configuration/Parser.pm --- tiarra-20080510/main/Configuration/Parser.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/main/Configuration/Parser.pm 2009-02-09 22:30:09.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Parser.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Parser.pm 14151 2008-06-16 15:47:01Z topia $ # ----------------------------------------------------------------------------- # confファイルの構文解析を行なうクラス。 # このクラスはConfiguration::LexicalAnalyzerを用いて字句解析を行ないます。 @@ -108,7 +108,7 @@ # ブロックをパース。 my $newblock = $this->_parse_block('block'); - $block->set($newblock->block_name,$newblock); + $block->add($newblock->block_name,$newblock); } elsif ($type eq 'blockend') { # 読み過ぎたので戻す。 diff -urN tiarra-20080510/main/Configuration/Preprocessor.pm tiarra-20090206/main/Configuration/Preprocessor.pm --- tiarra-20080510/main/Configuration/Preprocessor.pm 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/main/Configuration/Preprocessor.pm 2009-02-09 22:30:09.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Preprocessor.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Preprocessor.pm 24347 2008-11-19 14:45:12Z topia $ # ----------------------------------------------------------------------------- # tiarraのconfファイルのプリプロセッサです。 # このクラスは次のような機能を持ちます。 @@ -177,7 +177,7 @@ my $result = ''; foreach my $line (split /\n/,$body) { # この行が@undef,@ifdef,@ifndef文でないなら、@defineされた全ての置換を実行。 - if ($line !~ m/^\s*\@\s*(?:undef|ifdef|ifndef)\s+/) { + if ($line !~ m/^\s*\@\s*(?:undef|(?:els)?ifn?def)\s+/) { while (my ($key,$value) = each %{$this->{consts}}) { $line =~ s/\Q$key\E/$value/g; } @@ -187,7 +187,7 @@ # if文のブロック内である。 my $action = $ifstack[@ifstack - 1]; - if ($line =~ m/^\s*\@\s*(?:if|elsif|ifdef|ifndef|else|endif)/) { + if ($line =~ m/^\s*\@\s*(?:(?:els)?if(?:n?def)?|else|endif)/) { # 状態が変わる可能性がある。 # とりあえず何もしない。 } @@ -206,11 +206,11 @@ $line =~ s/^\s*\@\s*|\s*$//g; # ifdefとifndefはif文に書換える - if ($line =~ m/^ifdef\s+(.+)$/) { - $line = q{if $this->defined_p(q@}.$1.q{@)}; + if ($line =~ m/^(els)?ifdef\s+(.+)$/) { + $line = $1.q{if $this->defined_p(q@}.$2.q{@)}; } - elsif ($line =~ m/^ifndef\s+(.+)$/) { - $line = q{if !$this->defined_p(q@}.$1.q{@)}; + elsif ($line =~ m/^(els)?ifndef\s+(.+)$/) { + $line = $1.q{if !$this->defined_p(q@}.$2.q{@)}; } if ($line =~ m/^include\s+(.+)$/) { @@ -235,7 +235,7 @@ print "$1\n"; } elsif ($line =~ m/^if\s+(.+)$/) { - if (@ifstack > 0 && !$ifstack[@ifstack - 2]) { + if (@ifstack > 0 && !$ifstack[@ifstack - 1]) { # 下のフレームが存在し、一つ下のフレームのアクションが'消す'なら、無条件に消す。 push @ifstack,0; } diff -urN tiarra-20080510/main/Configuration.pm tiarra-20090206/main/Configuration.pm --- tiarra-20080510/main/Configuration.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/main/Configuration.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Configuration.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Configuration.pm 29652 2009-02-06 13:55:13Z topia $ # ----------------------------------------------------------------------------- # このクラスはフック`reloaded'を用意します。 # フック`reloaded'は、設定ファイルがリロードされた時に呼ばれます。 @@ -219,10 +219,16 @@ } @$blocks; $this->_complete_block_with_defaults($root_block, $defaults); + # networksのdefaultだけは別処理。 + my $networks = $root_block->networks; + if (!defined $networks->default) { + $networks->set('default',$networks->name); + } + my $general = $root_block->general; if (!defined $general->nick_fix_mode) { $general->set('nick-fix-mode', do { - if ($general->multi_server_mode) { + if ($networks->multi_server_mode) { 0; } else { 1; @@ -230,12 +236,6 @@ }); } - # networksのdefaultだけは別処理。 - my $networks = $root_block->networks; - if (!defined $networks->default) { - $networks->set('default',$networks->name); - } - @$blocks = values(%{$root_block->table}); $blocks; } @@ -275,7 +275,6 @@ general => ['nick','user','name'], # [ネットワーク名]のhost,portは別処理。 }; -my $required_in_each_networks = ['host','port']; sub _check_required_definitions { my ($this,$blocks) = @_; if (!defined $blocks) { @@ -297,17 +296,19 @@ } } - # 各ネットワークのhostとportをチェック。 + # 各ネットワークのserver/host,portをチェック。 my @network_names = $blocks->{networks}->name('all'); foreach my $network_name (@network_names) { - foreach my $required_key (@{$required_in_each_networks}) { - my $block = $blocks->{$network_name}; - if (!defined $block) { - die "Block $network_name was not found. It was enumerated in networks/name.\n"; - } - if (!defined $blocks->{$network_name}->get($required_key)) { - # 必要だとされているのに定義が無かった。 - $error->($network_name,$required_key); + my $block = $blocks->{$network_name}; + if (!defined $block) { + die "Block $network_name was not found. It was enumerated in networks/name.\n"; + } + if (!defined $block->get('server')) { + foreach my $required_key (qw(host port)) { + if (!defined $block->get($required_key)) { + # 必要だとされているのに定義が無かった。 + $error->($network_name,$required_key); + } } } } diff -urN tiarra-20080510/main/ExternalSocket.pm tiarra-20090206/main/ExternalSocket.pm --- tiarra-20080510/main/ExternalSocket.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/main/ExternalSocket.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: ExternalSocket.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: ExternalSocket.pm 13831 2008-06-13 14:01:33Z topia $ # ----------------------------------------------------------------------------- # RunLoopは任意のソケットを監視する事が出来るが、その登録の為にこのクラスを用いる。 # ----------------------------------------------------------------------------- @@ -28,6 +28,12 @@ # ::printmsg($this->errmsg('foo socket error')); # $this->disconnect; # }, +# Name => undef, # 名前。エラー表示とかで使います。 Tiarra::Socket->name 参照。 +# Module => undef, +# # 登録もとのモジュールを指定します。 +# # ModuleManager に登録しておいて、モジュールをアンロードした時に +# # もしソケットが残っていたら、自動的に捨てます。 +# # Module のサブクラスまたはパッケージ名で指定してください。 # )->install; # # $esock->uninstall; @@ -94,6 +100,11 @@ $this->name($opts{Name}); } + if (defined $opts{Module}) { + require ModuleManager; + ModuleManager->shared_manager->add_module_object($opts{Module}, $this); + } + $this; } @@ -167,4 +178,12 @@ $this->{exception}->($this) if defined $this->{exception}; } +sub module_destruct { + my ($this, $module) = @_; + + $this->SUPER::module_destruct($module); + $this->{read} = $this->{write} = $this->{wanttowrite} = + $this->{exception} = undef; +} + 1; diff -urN tiarra-20080510/main/Hook.pm tiarra-20090206/main/Hook.pm --- tiarra-20080510/main/Hook.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/main/Hook.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Hook.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Hook.pm 13831 2008-06-13 14:01:33Z topia $ # ----------------------------------------------------------------------------- # Hook: あらゆるフックのベースクラス # HookTarget: あらゆるフック先のベースクラス @@ -177,6 +177,14 @@ } } + +sub module_destruct { + my ($this, $module) = @_; + + $this->uninstall if $this->{target}; + undef $this->{code}; +} + # ----------------------------------------------------------------------------- package HookTarget; diff -urN tiarra-20080510/main/IrcIO/Client.pm tiarra-20090206/main/IrcIO/Client.pm --- tiarra-20080510/main/IrcIO/Client.pm 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/main/IrcIO/Client.pm 2009-02-09 22:30:09.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Client.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Client.pm 28573 2009-01-17 18:01:46Z topia $ # ----------------------------------------------------------------------------- # IrcIO::Clientはクライアントからの接続を受け、 # IRCメッセージをやり取りするクラスです。 @@ -563,9 +563,7 @@ } # クライアントに出力。 - # その結果切断されたら関数を抜ける。 $this->flush; - last CONNECTING unless $this->connected; }; my %channels = map { @@ -577,7 +575,6 @@ } values %{$network->channels}; } values %{$this->_runloop->networks}; - CONNECTING: while (1) { # Mask を使って、マッチしたものを出力 foreach ($this->_conf_networks-> @@ -587,6 +584,7 @@ my $ch_name = $_; if (Mask::match($mask, $ch_name)) { $send_channelinfo->(@{$channels{$ch_name}}); + last unless $this->connected; delete $channels{$ch_name}; } } @@ -595,6 +593,7 @@ # のこりを出力 foreach (values %channels) { $send_channelinfo->(@$_); + last unless $this->connected; } last; diff -urN tiarra-20080510/main/IrcIO/Server.pm tiarra-20090206/main/IrcIO/Server.pm --- tiarra-20080510/main/IrcIO/Server.pm 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/main/IrcIO/Server.pm 2009-02-09 22:30:09.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Server.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Server.pm 13832 2008-06-13 14:04:52Z topia $ # ----------------------------------------------------------------------------- # IrcIO::ServerはIRCサーバーに接続し、IRCメッセージをやり取りするクラスです。 # このクラスはサーバーからメッセージを受け取ってチャンネル情報や現在のnickなどを保持しますが、 @@ -172,13 +172,23 @@ sub reload_config { my $this = shift; my $conf = $this->{config} = $this->_conf->get($this->{network_name}); - $this->{server_host} = $conf->host; - $this->{server_port} = $conf->port; + my @servers = $conf->server('all'); + if (!@servers) { + @servers = ([$conf->host, $conf->port]); + } else { + @servers = map {[split(/\s+/, $_)]} @servers; + } + $this->{server_hosts} = [ + map { +{ + host => shift(@$_), + port => $_, + } } @servers, + ]; $this->{server_password} = $conf->password; $this->{initial_nick} = $this->config_local_or_general('nick'); # ログイン時に設定するnick。 $this->{user_shortname} = $this->config_local_or_general('user'); $this->{user_realname} = $this->config_local_or_general('name'); - $this->{prefer_socket_types} = [qw(ipv6 ipv4)]; + #$this->{prefer_socket_types} = [qw(ipv6 ipv4)]; } sub destination { @@ -248,10 +258,7 @@ $this->connect; } -# connect --(resolve)--> stage_1 --> (queueing) --> try_next --> -# each connect method -# ok -> attach -# err -> _connect_error +# connect --(resolve)--> _connect_try_next --> sub connect { my $this = shift; @@ -261,68 +268,50 @@ $this->finalizing(undef); # 初期化すべきフィールドを初期化 + $this->{server_host} = undef; + $this->{server_port} = undef; $this->{nick_retry} = 0; $this->{logged_in} = undef; $this->state_connecting(1); - my $conn_stat = $this->{connection_status} = { - start => time, - tried => [], - }; - Tiarra::Resolver->resolve('addr', $this->{server_host}, sub { - $this->_connect_stage_1(@_); - }); - $this; -} - -sub _connect_stage_1 { - my ($this, $entry) = @_; - - my %addrs_by_types; - my $server_port = $this->{server_port}; - - return if $this->finalizing; - - if ($entry->answer_status eq $entry->ANSWER_OK) { - foreach my $addr (@{$entry->answer_data}) { - if ($addr =~ m/^(?:\d+\.){3}\d+$/) { - push (@{$addrs_by_types{ipv4}}, $addr); - } elsif ($addr =~ m/^[0-9a-fA-F:]+$/) { - push (@{$addrs_by_types{ipv6}}, $addr); - } else { - $this->die("unsupported addr type: $addr"); - } - } - } else { - $this->printmsg("Couldn't resolve hostname: $this->{server_host}"); - $this->_queue_retry; - return; - } + $this->{server_queue} = $this->{server_hosts}; - foreach my $sock_type (@{$this->{prefer_socket_types}}) { - my $struct; - push (@{$this->{connection_queue}}, - map { - $struct = { - type => $sock_type, - addr => $_, - host => $entry->query_data, - port => $server_port, - }; - } @{$addrs_by_types{$sock_type}}); - } $this->_connect_try_next; + $this; } + sub _connect_try_next { my $this = shift; return if $this->finalizing; my $trying = - $this->{connecting} = shift @{$this->{connection_queue}}; + $this->{connecting} = shift @{$this->{server_queue}}; if (defined $trying) { - my $methodname = '_try_connect_' . $this->{connecting}->{type}; - $this->$methodname($trying); + + $this->{connector} = Tiarra::Socket::Connect->new( + host => $this->{connecting}->{host}, + port => $this->{connecting}->{port}, + callback => sub { + my ($subject, $socket, $obj, $errno) = @_; + + if ($subject eq 'sock') { + $this->attach($socket); + } elsif ($subject eq 'error') { + $this->_connect_error($obj); + } elsif ($subject eq 'warn') { + $this->_connect_warn($obj); + } elsif ($subject eq 'progress') { + $this->_connect_warn($obj); + } + }, + hooks => { + before_connect => sub { + $this->_hook_before_connect(@_); + }, + }, + ); + } else { $this->printmsg("Couldn't connect to any host"); $this->_queue_retry; @@ -330,51 +319,21 @@ } } -sub _try_connect_ipv4 { - my ($this, $conn_struct) = @_; +sub _hook_before_connect { + my ($this, $stage, $connecting) = @_; - my %additional; - my $ipv4_bind_addr = - $this->config_local_or_general('ipv4-bind-addr') || - $this->config_local_or_general('bind-addr'); # 下は過去互換性の為に残す。 - if (defined $ipv4_bind_addr) { - $additional{bind_addr} = $ipv4_bind_addr; + my $type = $connecting->{type}; + my $bind_addr; + if ($type eq 'ipv4') { + # 下は過去互換性の為に残す。 + $bind_addr = $this->config_local_or_general('ipv4-bind-addr') || + $this->config_local_or_general('bind-addr'); + } elsif ($type eq 'ipv6') { + $bind_addr = $this->config_local_or_general('ipv6-bind-addr'); } - $this->_try_connect_socket($conn_struct, %additional); -} - -sub _try_connect_ipv6 { - my ($this, $conn_struct) = @_; - - my %additional; - my $ipv6_bind_addr = $this->config_local_or_general('ipv6-bind-addr'); - if (defined $ipv6_bind_addr) { - $additional{bind_addr} = $ipv6_bind_addr; + if (defined $bind_addr) { + $connecting->{bind_addr} = $bind_addr; } - - $this->_try_connect_socket($conn_struct, %additional); -} - -sub _try_connect_socket { - my ($this, $conn_struct, %additional) = @_; - - $this->{connector} = Tiarra::Socket::Connect->new( - host => $conn_struct->{host}, - addr => $conn_struct->{addr}, - port => $conn_struct->{port}, - callback => sub { - my ($subject, $socket, $obj) = @_; - - if ($subject eq 'sock') { - $this->attach($socket); - } elsif ($subject eq 'error') { - $this->_connect_error($obj); - } elsif ($subject eq 'warn') { - $this->_connect_warn($obj); - } - }, - %additional); - $this; } sub attach { @@ -382,7 +341,9 @@ $this->SUPER::attach($connector->sock); $this->{connecting} = undef; + $this->{server_host} = $connector->host; $this->{server_addr} = $connector->current_addr; + $this->{server_port} = $connector->current_port; $this->{proto} = $connector->current_type; $this->state_connected(1); @@ -397,15 +358,14 @@ sub _connect_error { my ($this, $msg) = @_; - # avoid double error message (we don't use timeout) - #$this->printmsg("Couldn't connect to ".$this->destination.": $msg\n"); + $this->printmsg("$msg"); $this->_connect_try_next; } sub _connect_warn { my ($this, $msg) = @_; - $this->printmsg("$msg\n"); + $this->printmsg("$msg"); } sub _send_connection_messages { diff -urN tiarra-20080510/main/LinedINETSocket.pm tiarra-20090206/main/LinedINETSocket.pm --- tiarra-20080510/main/LinedINETSocket.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/main/LinedINETSocket.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: LinedINETSocket.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: LinedINETSocket.pm 11477 2008-05-12 17:29:51Z topia $ # ----------------------------------------------------------------------------- # Lined IO Socket # ----------------------------------------------------------------------------- @@ -44,10 +44,10 @@ } sub disconnect { - my ($this, $errno, $genre, @params) = @_; - $this->SUPER::disconnect($errno, $genre, @params); + my ($this, $genre, $errno, @params) = @_; + $this->SUPER::disconnect($genre, $errno, @params); if (defined $this->{disconnect_callback}) { - $this->{disconnect_callback}->($errno, $genre); + $this->{disconnect_callback}->($genre, $errno); } } diff -urN tiarra-20080510/main/Module.pm tiarra-20090206/main/Module.pm --- tiarra-20080510/main/Module.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/main/Module.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Module.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Module.pm 13831 2008-06-13 14:01:33Z topia $ # ----------------------------------------------------------------------------- # Tiarraモジュール(プラグイン)を表わす抽象クラスです。 # 全てのTiarraモジュールはこのクラスを継承し、 @@ -39,6 +39,22 @@ # 引数は無し。 } +sub config_reload { + my ($this, $old_config) = @_; + # モジュールの設定が変更された時に呼ばれる。 + # 新しい config は $this->config で取得できます。 + + # デフォルト動作。 + eval { + $this->destruct; + }; if ($@) { + $this->_runloop->notify_error( + "Couldn't destruct module on reload config of " . ref($this) + . ".\n$@"); + } + return ref($this)->new($this->_runloop); +} + sub message_arrived { my ($this,$message,$sender) = @_; # サーバーまたはクライアントからメッセージが来た時に呼ばれる。 @@ -175,4 +191,18 @@ $this->_conf->find_module_conf(ref($this),'block'); } +sub _add_object { + my ($this, @disposables) = @_; + + $this->_runloop->mod_manager->add_module_object($this, @disposables); + $this; +} + +sub _remove_object { + my ($this, @disposables) = @_; + + $this->_runloop->mod_manager->remove_module_object($this, @disposables); + $this; +} + 1; diff -urN tiarra-20080510/main/ModuleManager.pm tiarra-20090206/main/ModuleManager.pm --- tiarra-20080510/main/ModuleManager.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/main/ModuleManager.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: ModuleManager.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: ModuleManager.pm 13831 2008-06-13 14:01:33Z topia $ # ----------------------------------------------------------------------------- # このクラスは全てのTiarraモジュールを管理します。 # モジュールをロードし、リロードし、破棄するのはこのクラスです。 @@ -9,7 +9,7 @@ use Carp; use warnings; use UNIVERSAL; -use RunLoop; +use Scalar::Util qw(refaddr weaken); use Tiarra::SharedMixin qw(shared shared_manager); use Tiarra::ShorthandConfMixin; use Tiarra::Utils; @@ -22,12 +22,13 @@ } sub _new { - shift->new(shift || RunLoop->shared); + shift->new(shift || do { require RunLoop && RunLoop->shared }); } sub new { my ($class, $runloop) = @_; croak 'runloop is not specified!' unless defined $runloop; + weaken($runloop); my $obj = { runloop => $runloop, modules => [], # 現在使用されている全てのモジュール @@ -36,6 +37,7 @@ mod_timestamps => {}, # 現在使用されている全モジュールおよびサブモジュールの初めてuseされた時刻 mod_blacklist => {}, # 過去に正常動作しなかったモジュール。 updated_once => 0, # 過去にupdate_modulesが実行された事があるか。 + mod_disposables => {}, # モジュールに関連づけられているタイマーとか。アンロードした後で全部捨てる。 }; bless $obj,$class; } @@ -159,15 +161,77 @@ } } +sub add_module_object { + my $this = shift->_this; + my ($modname, @disposables) = @_; + $modname = ref($modname) || $modname; + + my ($foo); + my (%addrs); + @{$this->{mod_disposables}->{$modname}} = grep { + defined $_ && !($addrs{refaddr($_)}++) + } (@{$this->{mod_disposables}->{$modname}}, + map { weaken($_); $_ } @disposables); + + $this; +} + +sub remove_module_object { + my $this = shift->_this; + my ($modname, @disposables) = @_; + $modname = ref($modname) || $modname; + + return unless $this->{mod_disposables}->{$modname}; + + my $arr = $this->{mod_disposables}->{$modname}; + @$arr = grep { defined $_ } @$arr; + + my %targets = map { refaddr($_) => 1 } @disposables; + @$arr = grep { !$targets{refaddr($_)} } @$arr; + + if (!@$arr) { + delete $this->{mod_disposables}->{$modname}; + } + + $this; +} + +sub _destruct_module_object { + my $this = shift->_this; + my $module = shift; # module instance or module name + my $modname = ref($module) || $module; + + my $arr = delete $this->{mod_disposables}->{$modname}; + + return unless $arr; + + foreach my $object (grep { defined $_ } @$arr) { + eval { + $object->module_destruct($module); + }; if ($@) { + $this->_runloop->notify_error($@); + } + } + + $this; +} + sub update_modules { # +で指定されたモジュール一覧を読み、modulesを再構成する。 # 必要なモジュールがまだロードされていなければロードし、 # もはや必要とされなくなったモジュールがあれば破棄する。 # 二度目以降、つまり起動後にこれが実行された場合は # モジュールのロードや破棄に関して成功時にもメッセージを出力する。 + # check_module_update => 1 を指定するとモジュール自体の更新もチェックする。 my $this = shift->_this; + my %mode = @_; # check_module_update => 1 my $mod_configs = $this->_conf->get_list_of_modules; - my ($new,$deleted,$changed,$not_changed) = $this->_check_difference($mod_configs); + + my (%new,%deleted,%conf_changed,%not_changed); + # (modname => old or new config) + my (%mod_changed); + # (modname => depth) + my $show_msg = sub { if ($this->{updated_once}) { @@ -186,31 +250,151 @@ my %loaded_mods = map { ref($_) => $_; } @{$this->{modules}}; + my %loaded_mods_idx; + for (my $i = 0; $i < @{$this->{modules}}; $i++) { + $loaded_mods_idx{ref $this->{modules}->[$i]} = $i; + } + my %new_mod_configs = map { + $_->block_name => $_; + } @$mod_configs; + + my ($new,$deleted,$changed,$not_changed) = $this->_check_difference($mod_configs); + $new{$_->block_name} = $_ for @$new; + $deleted{$_->block_name} = $_ for @$deleted; + $conf_changed{$_->block_name} = $_ for @$changed; + $not_changed{$_->block_name} = $_ for @$not_changed; + + if ($mode{check_module_update}) { + my $check_used; + $check_used = sub { + my ($modname, $depth) = @_; + ++$depth; + no strict 'refs'; + # このモジュールに%USEDは定義されているか? + my $USED = \%{$modname.'::USED'}; + return unless defined $USED; + + # USEDの全ての要素に対し再帰的にマークを付ける。 + foreach my $used_elem (keys %$USED) { + if (!defined $mod_changed{$used_elem} || + $mod_changed{$used_elem} < $depth) { + $mod_changed{$used_elem} = $depth; + $show_msg->("$used_elem will be reloaded because of ". + "modification of $modname"); + $check_used->($used_elem, $depth); + } + } + }; + + my $check = sub { + my ($modname,$timestamp) = @_; + # 既に更新されたものとしてマークされていれば抜ける。 + return if $mod_changed{$modname}; + + if ($this->check_timestamp_update($modname, $timestamp)) { + # 更新されている。少なくともこのモジュールはリロードされる。 + $mod_changed{$modname} = 1; + $show_msg->("$modname has been modified. It will be reloaded."); + + $check_used->($modname, 1); + } + }; + + while (my ($modname,$timestamp) = each %{$this->{mod_timestamps}}) { + $check->($modname,$timestamp); + } + } + + # 正規化 + foreach my $modname (keys %mod_changed) { + delete $not_changed{$modname}; + delete $new{$modname}; + delete $conf_changed{$modname}; + } + foreach my $modname (grep { !$loaded_mods{$_} } keys %conf_changed) { + $new{$modname} ||= $conf_changed{$modname}; + delete $conf_changed{$modname}; + } + + my %mod_rebuilt_mods; + + # モジュール再読み込み。 + if (%mod_changed) { + my @mods_load_order = map { $_->[0] } + sort { $a->[1] <=> $b->[1] } + map { [$_, $mod_changed{$_}]; } + keys %mod_changed; + + # 先に destruct して回る + foreach my $modname (reverse @mods_load_order) { + my $mod = $loaded_mods{$modname}; + if ($mod) { + eval { + $mod->destruct; + }; if ($@) { + $this->_runloop->notify_error($@); + } + $this->_destruct_module_object($mod); + } else { + eval { + $modname->destruct; + }; if ($@ && $modname->can('destruct')) { + $this->_runloop->notify_error($@); + } + $this->_destruct_module_object($modname); + } + } + + # マークされたモジュールをリロードするが、それが$loaded_mods_idxに登録されていたら + # インスタンスを作り直す。 + foreach my $modname (@mods_load_order) { + my $idx = $loaded_mods_idx{$modname}; + if (defined $idx) { + my $conf_block = $new_mod_configs{$modname}; + # message_io_hook が定義されているモジュールが死ぬと怖いので + # とりあえず undef を入れて無視させる。 + $this->{modules}->[$idx] = undef; + $this->_unload($modname); + $mod_rebuilt_mods{$modname} = $this->_load($conf_block); + # _unload でブラックリストから消えるから大丈夫だと思うが、一応。 + $this->remove_from_blacklist($modname); + } + else { + # アンロード後、use。 + no strict 'refs'; + # その時、%USEDを保存する。@USEは保存しない。 + my %USED = %{$modname.'::USED'}; + $this->_unload($modname); + eval qq{ + use $modname; + }; if ($@) { + $this->_runloop->notify_error($@); + } + %{$modname.'::USED'} = %USED; + } + } + } # 新たに追加されたモジュール、作り直されたモジュール、変更されなかったモジュールを # モジュール名 => Moduleの形式でテーブルにする。 + my $modname; my %new_mods = map { # 新たに追加されたモジュール。 $show_msg->("Module ".$_->block_name." will be loaded newly."); $this->remove_from_blacklist($_->block_name); $_->block_name => $this->_load($_); - } @$new; - my %rebuilt_mods = map { + } values %new; + my %conf_rebuilt_mods = map { # 作り直すモジュール。 - # %loaded_modsに古い物が入っているので、破棄する。 - $show_msg->("Configuration of the module ".$_->block_name." has been changed. It will be restarted."); - eval { - $loaded_mods{$_->block_name}->destruct; - }; if ($@) { - $this->_runloop->notify_error($@); - } - $this->remove_from_blacklist($_->block_name); - $_->block_name => $this->_load($_); - } @$changed; + $modname = $_->block_name; + $show_msg->("Configuration of the module ".$modname." has been changed. It will be restarted."); + $this->remove_from_blacklist($modname); + $_->block_name => $this->_config_reload($loaded_mods{$modname}, $_); + } values %conf_changed; my %not_changed_mods = map { # 設定変更されなかったモジュール。 # %loaded_modsに実物が入っている。 - my $modname = $_->block_name; + $modname = $_->block_name; if (!defined $loaded_mods{$modname} && $this->check_timestamp_update($modname)) { # ロードできてなくて、なおかつアップデートされていたらロードしてみる。 @@ -220,38 +404,41 @@ } else { $modname => $loaded_mods{$modname}; } - } @$not_changed; + } values %not_changed; # $mod_configsに書かれた順序に従い、$this->{modules}を再構成。 # 但しロードに失敗したモジュールはnullになっているので除外。 @{$this->{modules}} = grep { defined $_ } map { - my $modname = $_->block_name; - $not_changed_mods{$modname} || $rebuilt_mods{$modname} || $new_mods{$modname}; - } @$mod_configs; + $not_changed_mods{$_} || $mod_rebuilt_mods{$_} || + $conf_rebuilt_mods{$_} || $new_mods{$_}; + } map { $_->block_name } @$mod_configs; - my $deleted_any = @$deleted > 0; - foreach (@$deleted) { + foreach (keys %deleted) { # 削除されたモジュール。 # %loaded_modsに古い物が入っている場合は破棄した上、アンロードする。 - $show_msg->("Module ".$_->block_name." will be unloaded."); - if (defined $loaded_mods{$_->block_name}) { + my $modname = $_; + my $mod = $loaded_mods{$modname}; + $show_msg->("Module ".$modname." will be unloaded."); + if (defined $mod) { eval { - $loaded_mods{$_->block_name}->destruct; + $mod->destruct; }; if ($@) { $this->_runloop->notify_error($@); } + $this->_destruct_module_object($mod); } $this->_unload($_); } - # gc の前に一度キャッシュクリア - $this->_clear_module_cache; - - if ($deleted_any > 0) { + if (%deleted || %mod_changed) { # 何か一つでもアンロードしたモジュールがあれば、最早参照されなくなったモジュールが # あるかどうかを調べ、一つでもあればmark and sweepを実行。 my $fixed = $this->fix_USED_fields; + if ($fixed) { + # gc の前に一度キャッシュクリア + $this->_clear_module_cache; + $this->gc; } } @@ -265,7 +452,7 @@ sub _check_difference { # 前回の_check_difference実行時から、現在のモジュール設定がどのように変化したか。 # 戻り値は(<新規追加>,<削除>,<変更>,<無変更>) それぞれARRAYへの参照である。 - # 新規追加と変更はそれぞれ新しいConfiguration::Blockが、削除には(新しいものが無いので)古いConfiguration::Blockが返される。 + # 新規追加は新しいConfiguration::Blockが、変更と削除にはそれぞれ古いConfiguration::Blockが返される。 my ($this,$mod_configs) = @_; # まずは新たに登場したモジュールと、設定を変更されたモジュールを探す。 my @new; @@ -281,7 +468,7 @@ } else { # 内容が変わった。 - push @changed,$conf; + push @changed,$old_conf; } } else { @@ -311,128 +498,7 @@ # インスタンスも当然作り直す。 my $this = shift; - my $show_msg = sub { - $this->_runloop->notify_msg($_[0]); - }; - - my $mods_to_be_reloaded = {}; # モジュール名 => 1 - my $check = sub { - my ($modname,$timestamp) = @_; - # 既に更新されたものとしてマークされていれば抜ける。 - return if $mods_to_be_reloaded->{$modname}; - - if ($this->check_timestamp_update($modname, $timestamp)) { - # 更新されている。少なくともこのモジュールはリロードされる。 - $mods_to_be_reloaded->{$modname} = 1; - $show_msg->("$modname has been modified. It will be reloaded."); - - my $trace; - $trace = sub { - my ($modname, $depth) = @_; - ++$depth; - no strict 'refs'; - # このモジュールに%USEDは定義されているか? - my $USED = \%{$modname.'::USED'}; - if (defined $USED) { - # USEDの全ての要素に対し再帰的にマークを付ける。 - foreach my $used_elem (keys %$USED) { - if (!defined $mods_to_be_reloaded->{$used_elem} || - $mods_to_be_reloaded->{$used_elem} < $depth) { - $mods_to_be_reloaded->{$used_elem} = $depth; - $show_msg->("$used_elem will be reloaded because of modification of $modname"); - $trace->($used_elem, $depth); - } - } - } - }; - - $trace->($modname, 1); - } - }; - - while (my ($modname,$timestamp) = each %{$this->{mod_timestamps}}) { - $check->($modname,$timestamp); - } - - # 一つでもマークされたモジュールがあれば、$this->{modules}内の何処に - # 目的のモジュールが在るのかを調べるために、モジュール名 => 位置のテーブルを作る。 - if (keys(%$mods_to_be_reloaded) > 0) { - my $mod2index = {}; - for (my $i = 0; $i < @{$this->{modules}}; $i++) { - $mod2index->{ref $this->{modules}->[$i]} = $i; - } - - my @mods_load_order = map { $_->[0] } - sort { $a->[1] <=> $b->[1] } - map { [$_, $mods_to_be_reloaded->{$_}]; } - keys %$mods_to_be_reloaded; - - # 先に destruct して回る - foreach my $modname (reverse @mods_load_order) { - my $idx = $mod2index->{$modname}; - if (defined $idx) { - eval { - $this->{modules}->[$idx]->destruct; - }; if ($@) { - $this->_runloop->notify_error($@); - } - } else { - eval { - $modname->destruct; - }; if ($@ && $modname->can('destruct')) { - $this->_runloop->notify_error($@); - } - } - } - - # マークされたモジュールをリロードするが、それが$mod2indexに登録されていたら - # インスタンスを作り直す。 - foreach my $modname (@mods_load_order) { - my $idx = $mod2index->{$modname}; - if (defined $idx) { - my $conf_block = $this->{mod_configs}->{$modname}; - # message_io_hook が定義されているモジュールが死ぬと怖いので - # とりあえず undef を入れて無視させる。 - $this->{modules}->[$idx] = undef; - $this->_unload($conf_block); - $this->{modules}->[$idx] = $this->_load($conf_block); # 失敗するとundefが入る。 - # _unload でブラックリストから消えるから大丈夫だと思うが、一応。 - $this->remove_from_blacklist($modname); - } - else { - # アンロード後、use。 - no strict 'refs'; - # その時、%USEDを保存する。@USEは保存しない。 - my %USED = %{$modname.'::USED'}; - $this->_unload($modname); - eval qq{ - use $modname; - }; if ($@) { - $this->_runloop->notify_error($@); - } - %{$modname.'::USED'} = %USED; - } - } - - # 全てのモジュールの%USEDを調べて、その%USEDが指しているモジュールが - # 本当にそのモジュールを参照しているのかどうかをチェック。 - # モジュールの更新で最早参照しなくなっていれば、%USEDから削除する。 - # このような事が起こるのはリロード時に%USEDを保存するためである。 - my $fixed = $this->fix_USED_fields; - - # %USEDの不整合性が見付かったら、もはや必要とされなくなった - # モジュールがあるかも知れない。gcを実行。 - if ($fixed) { - $this->gc; - } - - # $this->{modules}にはundefの要素が入っているかも知れないので、そのような要素は除外する。 - @{$this->{modules}} = grep { - defined $_; - } @{$this->{modules}}; - - $this->_clear_module_cache; - } + $this->update_modules(check_module_update => 1); } sub _load { @@ -505,6 +571,38 @@ return $mod; } +sub _config_reload { + # モジュールをuseしてインスタンスを生成して返す。 + # 失敗したらundefを返す。 + my ($this,$oldmod,$mod_oldconf) = @_; + my $modname = ref($oldmod); + + # インスタンス生成 + my $mod; + eval { + $mod = $oldmod->config_reload($mod_oldconf); + }; if ($@) { + $this->_runloop->notify_error( + "Couldn't reload module config $modname because of exception.\n$@"); + # 念のため古いやつは destruct しておく。エラーは無視。 + eval { $oldmod->destruct }; + $this->_destruct_module_object($oldmod); + return undef; + } + + # このインスタンスは本当に$mod_nameそのものか? + if (ref($mod) ne $modname) { + $this->_runloop->notify_error( + "A thing ".$modname."->new returned was not a instance of $modname."); + # 念のため古いやつは destruct しておく。エラーは無視。 + eval { $oldmod->destruct }; + $this->_destruct_module_object($oldmod); + return undef; + } + + return $mod; +} + sub _unload { # 指定されたモジュールを削除する。 # モジュール名の代わりにConfiguration::Blockを渡しても良い。 @@ -619,6 +717,7 @@ eval { $key->destruct; }; + $this->_destruct_module_object($key); $this->_runloop->notify_msg( "Submodule $key is no longer required. It will be unloaded."); diff -urN tiarra-20080510/main/Multicast.pm tiarra-20090206/main/Multicast.pm --- tiarra-20080510/main/Multicast.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/main/Multicast.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Multicast.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Multicast.pm 13084 2008-06-02 13:56:48Z hio $ # ----------------------------------------------------------------------------- # サーバーからクライアントにメッセージが流れるとき、このクラスはフィルタとして # ネットワーク名を付加します。 @@ -555,10 +555,10 @@ _update_cache() unless $pkg_caller->isa('Multicast'); my @result; - if ((my $sep_index = index($str,$separator)) != -1) { + if ((my $sep_index = rindex($str,$separator)) != -1) { my $before_sep = substr($str,0,$sep_index); my $after_sep = substr($str,$sep_index+length($separator)); - if ((my $colon_pos = index($after_sep,':')) != -1) { + if ((my $colon_pos = rindex($after_sep,':')) != -1) { # #さいたま@taiyou:*.jp → #さいたま:*.jp + taiyou @result = ($before_sep.substr($after_sep,$colon_pos), substr($after_sep,0,$colon_pos), diff -urN tiarra-20080510/main/ReloadTrigger.pm tiarra-20090206/main/ReloadTrigger.pm --- tiarra-20080510/main/ReloadTrigger.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/main/ReloadTrigger.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: ReloadTrigger.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: ReloadTrigger.pm 13831 2008-06-13 14:01:33Z topia $ # ----------------------------------------------------------------------------- # confやモジュールのリロードの引き金。 # ----------------------------------------------------------------------------- @@ -7,8 +7,6 @@ use strict; use warnings; use RunLoop; -use Configuration; -use ModuleManager; use Timer; sub reload_conf_if_updated { @@ -16,26 +14,51 @@ # Tiarra内のそれぞれのクラスにconfの更新を通知する。 # モジュール側で更新された場合になにかの処理をするには、 # Configuration::Hook の reloaded を使ってください。 - if (Configuration->shared_conf->check_if_updated) { - Configuration->shared_conf->load; - RunLoop->shared_loop->update_networks; - ModuleManager->shared_manager->update_modules; + my $runloop = shift; + unless (ref($runloop) && $runloop->isa('RunLoop')){ + $runloop = RunLoop->shared_loop; + } + if ($runloop->config->check_if_updated) { + $runloop->config->load; + $runloop->update_networks; + $runloop->mod_manager->update_modules; } } sub reload_mods_if_updated { - ModuleManager->shared_manager->reload_modules_if_modified; + my $runloop = shift; + unless (ref($runloop) && $runloop->isa('RunLoop')){ + $runloop = RunLoop->shared_loop; + } + $runloop->mod_manager->reload_modules_if_modified; +} + +sub reload_all_if_updated { + my $runloop = shift; + unless (ref($runloop) && $runloop->isa('RunLoop')){ + $runloop = RunLoop->shared_loop; + } + if ($runloop->config->check_if_updated) { + $runloop->config->load; + $runloop->update_networks; + } + $runloop->mod_manager->update_modules( + check_module_update => 1, + ); } sub _install_reload_timer { + my $runloop = shift; + unless (ref($runloop) && $runloop->isa('RunLoop')){ + $runloop = RunLoop->shared_loop; + } Timer->new( Name => __PACKAGE__.'/reload', After => 0, Code => sub { - reload_conf_if_updated; - reload_mods_if_updated; + reload_all_if_updated; } - )->install; + )->install($runloop); } 1; diff -urN tiarra-20080510/main/RunLoop.pm tiarra-20090206/main/RunLoop.pm --- tiarra-20080510/main/RunLoop.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/main/RunLoop.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: RunLoop.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: RunLoop.pm 13831 2008-06-13 14:01:33Z topia $ # ----------------------------------------------------------------------------- # このクラスはTiarraのメインループを実装します。 # select()を実行し、サーバーやクライアントとのI/Oを行うのはこのクラスです。 @@ -147,7 +147,8 @@ utils->define_attr_getter(1, qw(default_network clients), [qw(multi_server_mode_p multi_server_mode)], - [qw(_mod_manager mod_manager)]); + [qw(mod_manager mod_manager)], + [qw(config conf)]); # クライアントから見た、現在のnick。 # このnickは実際に使われているnickとは異なっている場合がある。 @@ -1114,7 +1115,7 @@ $this->unregister_receive_socket($this->{tiarra_server_socket}); # 受信セレクタから登録解除 } undef $this->{control_port}; - $this->_mod_manager->terminate; + $this->mod_manager->terminate; } sub terminate { @@ -1164,7 +1165,7 @@ sub notify_modules { my ($class_or_this,$method,@args) = @_; my $this = $class_or_this->_this; - foreach my $mod (@{$this->_mod_manager->get_modules}) { + foreach my $mod (@{$this->mod_manager->get_modules}) { eval { $mod->$method(@args); }; if ($@) { @@ -1181,7 +1182,7 @@ my $source = $src_messages; my $filtered = []; - foreach my $mod (@{$this->_mod_manager->get_modules}) { + foreach my $mod (@{$this->mod_manager->get_modules}) { # (普通ないはずだが) $mod が undef だったらこのモジュールをとばす。 next unless defined $mod; # sourceが空だったらここで終わり。 @@ -1198,13 +1199,13 @@ my $modname = ref($mod); my $error = $@; # ブラックリストに入れておく - $this->_mod_manager->add_to_blacklist($modname); + $this->mod_manager->add_to_blacklist($modname); $this->notify_error( "Exception in ".$modname.".\n". "This module added to blacklist!\n". "The message was '".$src->serialize."'.\n". " $error"); - $this->_mod_manager->remove_from_blacklist($modname); + $this->mod_manager->remove_from_blacklist($modname); @reply = ($src); } diff -urN tiarra-20080510/main/Tiarra/Encoding.pm tiarra-20090206/main/Tiarra/Encoding.pm --- tiarra-20080510/main/Tiarra/Encoding.pm 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/main/Tiarra/Encoding.pm 2009-02-09 22:30:09.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Encoding.pm 3004 2007-12-10 12:45:39Z topia $ +# $Id: Encoding.pm 29317 2009-01-30 16:02:07Z topia $ # ----------------------------------------------------------------------------- # Tiarra Encoding Manager # ----------------------------------------------------------------------------- @@ -59,7 +59,7 @@ return $tried_providers{$modname} if defined $tried_providers{$modname}; my $retval = eval 'require ' . $modname; warn $@ if $@; - $tried_providers{$modname} = $retval; + $tried_providers{$modname} = $retval || 0; return $retval; } diff -urN tiarra-20080510/main/Tiarra/IRC/Message.pm tiarra-20090206/main/Tiarra/IRC/Message.pm --- tiarra-20080510/main/Tiarra/IRC/Message.pm 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/main/Tiarra/IRC/Message.pm 2009-02-09 22:30:09.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Message.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Message.pm 14132 2008-06-16 13:33:59Z topia $ # ----------------------------------------------------------------------------- # Tiarra::IRC::MessageはIRCのメッセージを表わすクラスです。実際のメッセージはUTF-8で保持します。 # 生のメッセージのパース、シリアライズ、そしてメッセージの生成をサポートします。 @@ -367,9 +367,9 @@ } if ($param ne '') { - if ($this->n_params > MAX_PARAMS && !$param_count_warned) { + if ($this->_n_raw_params >= MAX_PARAMS && !$param_count_warned) { $param_count_warned = 1; - carp 'max param exceeded; please fix upstream server!'; + warn 'max param exceeded; please fix upstream server!'; } if (substr($param,0,1) eq ':') { $param = substr($line, $pos); # これ以降は全て一つの引数。 @@ -726,9 +726,10 @@ =head1 AUTHOR -originally developed by phonohawk Ephonohawk@ps.sakura.ne.jpE. +originally developed by phonohawk Ephonohawk@ps.sakura.ne.jpE +and Topia Etopia@clovery.jpE. -now maintained by Topia Etopia@clovery.jpE. +now maintained by Tiarra Development Team. =head1 LICENSE diff -urN tiarra-20080510/main/Tiarra/IRC/Prefix.pm tiarra-20090206/main/Tiarra/IRC/Prefix.pm --- tiarra-20080510/main/Tiarra/IRC/Prefix.pm 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/main/Tiarra/IRC/Prefix.pm 2009-02-09 22:30:09.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Prefix.pm 3004 2007-12-10 12:45:39Z topia $ +# $Id: Prefix.pm 14132 2008-06-16 13:33:59Z topia $ # ----------------------------------------------------------------------------- # copyright (C) 2005 Topia . all rights reserved. package Tiarra::IRC::Prefix; @@ -10,12 +10,75 @@ use overload '""' => sub { shift->prefix }; -utils->define_array_attr_notify_accessor( - 0, '$this->_update_prefix', qw(nick name host)); -utils->define_array_attr_notify_accessor( - 0, '$this->_parse_prefix', qw(prefix)); +=head1 NAME -*user = \&name; +Tiarra::IRC::Prefix - Tiarra IRC Prefix class + +=head1 SYNOPSIS + + use Tiarra::IRC::Prefix; + my $prefix = Tiarra::IRC::Prefix->new(Prefix => 'foo!~bar@baz') + $prefix = Tiarra::IRC::Prefix->new(Nick => 'foo', + Name => '~bar', + Host => 'baz'); + if ($prefix eq 'foo!~bar@baz') { # stringify + # ... + } + +=head1 DESCRIPTION + +Tiarra IRC Prefix class. + +=head1 CONSTRUCTOR + +=over 4 + +=cut + +=item new + + # parse + my $prefix = Tiarra::IRC::Prefix->new(Prefix => 'foo!~bar@baz') + # construct + $prefix = Tiarra::IRC::Prefix->new(Nick => 'foo', + Name => '~bar', + Host => 'baz'); + +Construct IRC Prefix from string or parts. + +=over 4 + +=item * Construct with parsing + +=over 4 + +=item * Prefix + +prefix to parse. + +=back + +=item * Construct with parts + +=over 4 + +=item * Nick + +Nickname or Server FQDN. + +=item * Name or User + +Username. + +=item * Host + +Hostname. + +=back + +=back + +=cut sub new { my ($class,%args) = @_; @@ -34,10 +97,54 @@ $obj; } + +=back + +=head1 METHODS + +=over 4 + +=item nick + +accessor for nick. + +=item name or user + +accessor for name. + +=item host + +accessor for host. + +=item prefix + +accessor for prefix. + +=cut + +utils->define_array_attr_notify_accessor( + 0, '$this->_update_prefix', qw(nick name host)); +utils->define_array_attr_notify_accessor( + 0, '$this->_parse_prefix', qw(prefix)); + +*user = \&name; + +=item clone + + # deep clone + $deep_clone = $prefix->clone(deep => 1); + # shallow clone + $shallow_clone = $prefix->clone; + +Clone prefix. + +same behavior eithor deep or shallow clone for this class. + +=cut + sub clone { my ($this, %args) = @_; if ($args{deep}) { - # inhibits generator deep clone. require Data::Dumper; eval(Data::Dumper->new([$this])->Terse(1) ->Deepcopy(1)->Purity(1)->Dump); @@ -92,3 +199,25 @@ } 1; + +__END__ +=back + +=head1 SEE ALSO + +L + +=head1 AUTHOR + +originally developed by phonohawk Ephonohawk@ps.sakura.ne.jpE +and Topia Etopia@clovery.jpE. + +now maintained by Tiarra Development Team. + +=head1 LICENSE + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.6 or, +at your option, any later version of Perl 5 you may have available. + +=cut diff -urN tiarra-20080510/main/Tiarra/OptionalModules.pm tiarra-20090206/main/Tiarra/OptionalModules.pm --- tiarra-20080510/main/Tiarra/OptionalModules.pm 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/main/Tiarra/OptionalModules.pm 2009-02-09 22:30:09.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: OptionalModules.pm 11199 2008-05-06 07:35:55Z topia $ +# $Id: OptionalModules.pm 13829 2008-06-13 13:43:03Z topia $ # ----------------------------------------------------------------------------- # Optional Modules Loader # ----------------------------------------------------------------------------- @@ -12,12 +12,30 @@ # failsafe to module-reload our $status = {}; our %modules = ( - 'threads' => [qw(threads threads::shared)], - 'ipv6' => [qw(IO::Socket::INET6 Socket6)], - 'time_hires' => [qw(Time::HiRes)], - 'unix_dom' => [qw(IO::Socket::UNIX)], - 'encode' => [qw(Encode)], - 'base64' => [qw(MIME::Base64)], + 'threads' => { + requires => [qw(threads threads::shared Thread::Queue)], + note => 'for threading dns resolving', + }, + 'ipv6' => { + requires => [qw(IO::Socket::INET6 Socket6)], + note => 'for ipv6 support', + }, + 'time_hires' => { + requires => [qw(Time::HiRes)], + note => 'for hi-resolution timer support', + }, + 'unix_dom' => { + requires => [qw(IO::Socket::UNIX)], + note => 'for control port support', + }, + 'encode' => { + requires => [qw(Encode)], + note => 'for Tiarra::Encoding::Encode encoding driver', + }, + 'base64' => { + requires => [qw(MIME::Base64)], + note => 'for Tiarra::Encoding::Encode\'s base64 support', + }, ); sub _new { @@ -30,22 +48,53 @@ sub repr_modules { my $this = shift->_this; - $this->check_all; - my @enabled = sort grep $this->check($_), keys %modules; - my @disabled = sort grep !$this->check($_), keys %modules; - - ((@enabled ? - ("enabled:", - map { - " - $_ (" . join(', ', map { - "$_ " . $_->VERSION; - } @{$modules{$_}}) . ")" - } @enabled) : ()), - (@disabled ? - ("disabled:", - map { - " - $_ (" . join(', ', @{$modules{$_}}) . ")" - } @disabled) : ())); + my $verbose = shift; + my %status = $this->check_all; + my @enabled = sort grep $status{$_}, keys %status; + my @disabled = sort grep !$status{$_}, keys %status; + + my $repr_module = sub { + my ($modname, $eachmod) = @_; + my $ver; + my $error = $this->{$modname}->{errors}->{$eachmod}; + if (defined $error) { + if ($verbose) { + $error =~ s/ at .*//s; + $error =~ s/ \(\@INC .*\)//g; + $error =~ s/[\r\n]+/ /sg; + $error =~ s/ +$//g; + "[failed: $error]"; + } else { + "[failed to load]"; + } + } else { + eval { + $ver = $eachmod->VERSION; + }; + if (!defined $ver) { + 'unknown'; + } else { + $ver; + } + } + }; + + my $repr_modules = sub { + my $title = shift; + my $modname; + (@_ ? + ("$title:", + map { + $modname = $_; + " - $_ (" . join(', ', map { + "$_ " . $repr_module->($modname, $_); + } @{$modules{$_}->{requires}}) . ") " . + $modules{$_}->{note} + } @_) : ()) + }; + + ($repr_modules->("enabled", @enabled), + $repr_modules->("disabled", @disabled)); } sub check_all { @@ -57,10 +106,19 @@ my ($class_or_this, $name) = @_; my $this = $class_or_this->_this; - return $this->{$name} if defined $this->{$name}; + return $this->{$name}->{status} if defined $this->{$name}; die "module $name spec. not found" unless defined $modules{$name}; - $this->{$name} = eval join(' && ', map { "require $_" } @{$modules{$name}}) . ';'; + my $failed; + for my $mod (@{$modules{$name}->{requires}}) { + if (!eval "require $mod") { + $failed = 1; + }; + if ($@) { + $this->{$name}->{errors}->{$mod} = $@; + } + } + $this->{$name}->{status} = !$failed; } sub AUTOLOAD { diff -urN tiarra-20080510/main/Tiarra/Resolver.pm tiarra-20090206/main/Tiarra/Resolver.pm --- tiarra-20080510/main/Tiarra/Resolver.pm 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/main/Tiarra/Resolver.pm 2009-02-09 22:30:09.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Resolver.pm 11202 2008-05-06 08:13:24Z topia $ +# $Id: Resolver.pm 12926 2008-05-31 14:42:57Z hio $ # ----------------------------------------------------------------------------- # Simple Resolver with multi-thread or blocking. # ----------------------------------------------------------------------------- @@ -65,11 +65,11 @@ our $use_ipv6; our $use_threads_state_checking; BEGIN { - $use_threads = Tiarra::OptionalModules->threads; + $use_threads = !$^C && Tiarra::OptionalModules->threads; if ($use_threads) { - require Thread::Queue; require threads; require threads::shared; + require Thread::Queue; ## threads 1.33 or earlier, not support $thr->is_running() $use_threads_state_checking = threads->can('is_running'); } diff -urN tiarra-20080510/main/Tiarra/Socket/Buffered.pm tiarra-20090206/main/Tiarra/Socket/Buffered.pm --- tiarra-20080510/main/Tiarra/Socket/Buffered.pm 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/main/Tiarra/Socket/Buffered.pm 2009-02-09 22:30:09.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Buffered.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Buffered.pm 11477 2008-05-12 17:29:51Z topia $ # ----------------------------------------------------------------------------- # Buffered Socket # ----------------------------------------------------------------------------- @@ -37,7 +37,7 @@ } sub disconnect { - my ($this, $errno, $genre, @params) = @_; + my ($this, $genre, $errno, @params) = @_; $this->uninstall if $this->installed; $this->close; diff -urN tiarra-20080510/main/Tiarra/Socket/Connect.pm tiarra-20090206/main/Tiarra/Socket/Connect.pm --- tiarra-20080510/main/Tiarra/Socket/Connect.pm 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/main/Tiarra/Socket/Connect.pm 2009-02-09 22:30:09.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Connect.pm 11203 2008-05-06 08:34:36Z topia $ +# $Id: Connect.pm 11479 2008-05-12 17:41:48Z topia $ # ----------------------------------------------------------------------------- # Socket Connector # ----------------------------------------------------------------------------- @@ -15,7 +15,8 @@ use Tiarra::Utils; utils->define_attr_accessor(0, qw(domain host addr port callback), qw(bind_addr prefer timeout), - qw(retry_int retry_count try_count)); + qw(retry_int retry_count), + qw(hooks)); utils->define_attr_enum_accessor('domain', 'eq', qw(tcp unix)); @@ -24,42 +25,91 @@ # tcp: # my $connector = connect->new( # host => [hostname], -# port => [port], +# port => [port] or [ports], # callback => sub { # my ($genre, $connector, $msg_or_sock, $errno) = @_; -# if ($genre eq 'warn') { -# # $msg_or_sock: msg -# # maybe don't have $errno -# warn $msg_or_sock; -# } elsif ($genre eq 'error') { -# # $msg_or_sock: msg -# # maybe has $errno +# if ($genre eq 'error') { +# # error: 重大なエラーが見つかったか、全ての接続に失敗したとき。 +# # これ以降は何もしないので、必要な場合は +# # 新しいインスタンスを作成してください。 +# # $msg_or_sock: なんらかのメッセージ。 +# # $errno: 原因となる errno があるときはセットされています。 # die $msg_or_sock; # } elsif ($genre eq 'sock') { -# # $msg_or_sock: sock -# # maybe don't have $errno +# # sock: 接続に成功したのでソケットを返します。 +# # $msg_or_sock: 対応する IO::Socket のインスタンス。 # attach($connector->current_addr, $connector->current_port, # $msg_or_sock); -# # optional genre -# } elsif ($genre eq 'interrupt') { -# # $msg_or_sock: undef -# # maybe don't have $errno -# die 'interrupted'; +# # timeout を指定したときは実装するようにしてください。 # } elsif ($genre eq 'timeout') { -# # $msg_or_sock: undef -# # maybe don't have $errno +# # timeout: 引数で指定された timeout が経過して、接続が +# # 中断された時に呼ばれます。 +# # $msg_or_sock: undef # die 'timeout'; +# # これ以降は必ずしも実装しなくてもかまいません。 +# } elsif ($genre eq 'warn') { +# # warnings: 個々のホストに対する接続エラーとか。 +# # $msg_or_sock: なんらかのメッセージ。 +# # $errno: 原因となる errno があるときはセットされています。 +# warn $msg_or_sock; +# } elsif ($genre eq 'progress') { +# # progress: 接続の進行状況を示します。 +# # 主に試行開始メッセージが飛んできます。 +# # $msg_or_sock: なんらかのメッセージ。 +# warn $msg_or_sock; +# } elsif ($genre eq 'skip') { +# # skip: before_connect フックにより skip を指示されると +# # 呼び出されます。 +# # $msg_or_sock: skip された接続先を含むメッセージ +# warn $msg_or_sock; +# } elsif ($genre eq 'interrupt') { +# # interrupt: ユーザーにより接続が中断された +# # (interrupt が呼び出された)時に呼び出されます。 +# # $msg_or_sock: undef +# die 'interrupted'; # } # }, -# # optional params +# ## オプション系 +# # 既に正引きしたアドレスを持っている場合に指定します。 # addr => [already resolved addr], +# # bind addr を指定します。必ずアドレスを書くようにしてください。 # bind_addr => [bind_addr (cannot specify host)], -# timeout => [timeout], # didn't test enough, please send report when bugs. -# retry_int => [retry interval], +# # タイムアウトを秒単位で指定します。 +# # 十分にテストされているとは言い難いので、おかしな動作を見つけたら +# # レポートを送ってください。 +# timeout => [timeout], +# # 接続に失敗したあと、時間をおいて再試行する回数を指定します。 +# # 再試行回数は $connector->try_count で取得できます。 # retry_count => [retry count], +# # リトライ時の待ち時間を秒単位で指定します。 +# retry_int => [retry interval], +# # 希望するソケット種類を ['ipv4', 'ipv6'] という形式で指定します。 +# # デフォルトは ipv6 -> ipv4 の順です。 # prefer => [prefer socket type(and order) (ipv4, ipv6) as string's # array ref, default ipv6, ipv4], +# # ソケットドメイン。既定値ですが、必要であれば tcp を指定してください。 # domain => 'tcp', # default +# # フック。 +# hooks => { +# # 接続前フック。 +# before_connect => { # hook before connection attempt +# my ($stage, $connecting) = @_; +# # $stage: 'before_connect' という文字列が渡ります。 +# # $connecting: +# # 接続オプション。このハッシュは書き換えることができます。 +# # { # be able to modify this hash. +# # addr => $addr, port => $port, +# # type => 'ipv4' or 'ipv6', +# # # この接続にのみ適用する bind_addr があれば +# # # 指定してください。 +# # (bind_addr => [connection local bind_addr]), +# # } +# if ( /* not_want_to_connect */ ) { +# # die するとこの接続はスキップされます。 +# die 'skip this connection'; +# } +# } +# } # ); # $connector->interrupt; # unix: @@ -79,7 +129,7 @@ my $this = $class->SUPER::new(%opts); map { $this->$_($opts{$_}); - } qw(host addr port callback bind_addr timeout retry_int retry_count); + } qw(host addr port callback bind_addr timeout retry_int retry_count hooks); if (!defined $this->callback) { croak 'callback closure required'; @@ -122,12 +172,12 @@ my $entry = Tiarra::Resolver::QueueData->new; $entry->answer_status($entry->ANSWER_OK); $entry->answer_data([$this->addr]); - $this->_connect_stage($entry); + $this->_connect_after_resolve($entry); } else { Tiarra::Resolver->resolve( 'addr', $this->host, sub { eval { - $this->_connect_stage(@_); + $this->_connect_after_resolve(@_); }; if ($@) { $this->_connect_error("internal error: $@"); } @@ -136,13 +186,13 @@ $this; } -sub _connect_stage { +sub _connect_after_resolve { my ($this, $entry) = @_; my %addrs_by_types; if ($entry->answer_status ne $entry->ANSWER_OK) { - $this->_connect_error("Couldn't resolve hostname"); + $this->_connect_error("Couldn't resolve hostname: ".$this->host); return undef; # end } @@ -153,14 +203,22 @@ foreach my $sock_type (@{$this->prefer}) { my $struct; - push (@{$this->{queue}}, - map { - $struct = { - type => $sock_type, - addr => $_, - port => $this->port, - }; - } @{$addrs_by_types{$sock_type}}); + my @ports; + if (ref($this->port) eq 'ARRAY') { + @ports = @{$this->port}; + } else { + @ports = $this->port; + } + foreach my $port (@ports) { + push (@{$this->{queue}}, + map { + $struct = { + type => $sock_type, + addr => $_, + port => $port, + }; + } @{$addrs_by_types{$sock_type}}); + } } $this->_connect_try_next; } @@ -170,10 +228,11 @@ $this->{connecting} = shift @{$this->{queue}}; if (defined $this->{connecting}) { + $this->_notify_progress("Connecting to ".$this->destination); my $methodname = '_try_connect_' . $this->{connecting}->{type}; $this->$methodname; } else { - if ($this->retry_int && (++$this->try_count <= $this->retry_count)) { + if ($this->retry_int && (++$this->{try_count} <= $this->retry_count)) { $this->{timer} = Timer->new( After => $this->retry_int, Code => sub { @@ -181,10 +240,10 @@ $this->connect; }); $this->_connect_warn( - 'all dead, ' . + 'all connection attempt failed, ' . utils->to_ordinal_number($this->try_count) . ' retry'); } else { - $this->_connect_error('all dead'); + $this->_connect_error('all connection attempt failed'); } } } @@ -199,10 +258,12 @@ my $this = shift; if (!Tiarra::OptionalModules->ipv6) { - $this->_error( + $this->_warn( qq{Host $this->{host} seems to be an IPv6 address, }. qq{but IPv6 support is not enabled. }. qq{Use IPv4 or install Socket6 or IO::Socket::INET6 if possible.\n}); + $this->_connect_try_next; + return; } $this->_try_connect_tcp('IO::Socket::INET6'); @@ -211,20 +272,31 @@ sub _try_connect_tcp { my ($this, $package, $addr, %additional) = @_; + eval { + $this->_call_hooks('before_connect', $this->{connecting}); + }; if ($@) { + $this->_notify_skip($@); + $this->_connect_try_next; + return; + } + if (defined $this->{hooks}->{before_connect}) { + #eval { $this->{hooks}->{before_connect}->('before_connect', ) } + } if (!eval("require $package")) { - $this->_connect_error("Couldn\'t require socket package: $package"); + $this->_connect_warn("Couldn\'t require socket package: $package"); + $this->_connect_try_next; return; } + my $bind_addr = $this->current_bind_addr; my $sock = $package->new( %additional, - (defined $this->{bind_addr} ? - (LocalAddr => $this->{bind_addr}) : ()), + (defined $bind_addr ? + (LocalAddr => $bind_addr) : ()), Timeout => undef, Proto => 'tcp'); if (!defined $sock) { - $this->_connect_error( - $this->sock_errno_to_msg($!, 'Couldn\'t prepare socket'), - $!); + $this->_connect_warn("Couldn't prepare socket: $@"); + $this->_connect_try_next; return; } if (!defined $sock->blocking(0)) { @@ -272,8 +344,16 @@ qq{Address $this->{addr} seems to be an Unix Domain Socket address, }. qq{but Unix Domain Socket support is not enabled. }. qq{Use other protocol if possible.\n}); + return; } + eval { + $this->_call_hooks('before_connect', $this->{connecting}); + }; if ($@) { + $this->_notify_skip($@); + $this->_connect_try_next; + return; + } require IO::Socket::UNIX; my $sock = IO::Socket::UNIX->new(Peer => $this->{connecting}->{addr}); if (defined $sock) { @@ -331,7 +411,15 @@ utils->get_first_defined( $this->{connecting}->{port}, - $this->port); + ref($this->port) ? join(',', @{$this->port}) : $this->port); +} + +sub current_bind_addr { + my $this = shift; + + utils->get_first_defined( + $this->{connecting}->{bind_addr}, + $this->bind_addr); } sub current_type { @@ -340,6 +428,10 @@ $this->{connecting}->{type}; } +sub try_count { + shift->{try_count} + 1; +} + sub _error { # connection error; and finish ->connect chain my ($this, $msg, $errno) = @_; @@ -354,6 +446,22 @@ $this->callback->('warn', $this, $msg, $errno); } +sub _notify_progress { + # connection progress message. + my ($this, $msg) = @_; + + $this->callback->('progress', $this, $msg); +} + +sub _notify_skip { + # this address/port skipped; but continue trying + my ($this, $str, $errno) = @_; + + $this->callback->('skip', $this, + "skip connection attempt to ".$this->destination.$str, + $errno); +} + sub _call { # connection successful my $this = shift; @@ -361,6 +469,17 @@ $this->callback->('sock', $this, $this->sock); } +sub _call_hooks { + # method may died by callback. + # please cover with eval, if you need. + my $this = shift; + my $genre = shift; + + if (defined $this->{hooks}->{$genre}) { + $this->{hooks}->{$genre}->($genre, @_); + } +} + sub cleanup { my $this = shift; @@ -412,16 +531,10 @@ $this->cleanup; $this->_call; } else { - $this->_warn( - $this->sock_errno_to_msg($!, 'connection try error'), $!); - $this->_handle_sock_error; + $this->_handle_sock_error($!, 'connection try error'); } } elsif (!IO::Select->new($this->sock)->can_write(0)) { - $this->_warn('cannot write socket error'); - my $error = $this->errno; - $this->cleanup; - $this->close; - $this->_connect_warn_try_next($error, "cant write on $state"); + $this->_handle_sock_error(undef, "can't write on $state"); } else { # ignore first ready-to-read if ($state ne 'read' || $this->{unexpected_want_to_read_count}++) { @@ -433,10 +546,12 @@ sub _handle_sock_error { my $this = shift; - my $error = $this->errno; + my $error = shift; + my $msg = shift; + $error = $this->errno unless defined $error; $this->cleanup; $this->close; - $this->_connect_warn_try_next($error); + $this->_connect_warn_try_next($error, $msg); } 1; diff -urN tiarra-20080510/main/Tiarra/Socket.pm tiarra-20090206/main/Tiarra/Socket.pm --- tiarra-20080510/main/Tiarra/Socket.pm 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/main/Tiarra/Socket.pm 2009-02-09 22:30:09.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Socket.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Socket.pm 13831 2008-06-13 14:01:33Z topia $ # ----------------------------------------------------------------------------- # Socket Wrapper # 注意: Win32 環境では Socket 以外のファイルハンドル等に select を使えません。 @@ -262,6 +262,13 @@ $opts; } +sub module_destruct { + my ($this, $module) = @_; + + eval { $this->detach; }; + undef $this->{runloop}; +} + 1; =pod diff -urN tiarra-20080510/main/Tiarra/WrapMainLoop.pm tiarra-20090206/main/Tiarra/WrapMainLoop.pm --- tiarra-20080510/main/Tiarra/WrapMainLoop.pm 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/main/Tiarra/WrapMainLoop.pm 2009-02-09 22:30:09.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: WrapMainLoop.pm 10975 2008-05-02 16:09:53Z topia $ +# $Id: WrapMainLoop.pm 13831 2008-06-13 14:01:33Z topia $ # ----------------------------------------------------------------------------- # MainLoop wrapper for write Portable Module # ----------------------------------------------------------------------------- @@ -139,4 +139,11 @@ $this; } +sub module_destruct { + my ($this, $module) = @_; + + $this->_closure(undef); + $this->lazy_uninstall; +} + 1; diff -urN tiarra-20080510/main/TiarraDoc.pm tiarra-20090206/main/TiarraDoc.pm --- tiarra-20080510/main/TiarraDoc.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/main/TiarraDoc.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------ -# $Id: TiarraDoc.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: TiarraDoc.pm 12336 2008-05-25 10:03:57Z topia $ # ------------------------------------------------------------------------ # tiarra-docのパーサとトランスレータ群。 # ------------------------------------------------------------------------ diff -urN tiarra-20080510/main/Timer.pm tiarra-20090206/main/Timer.pm --- tiarra-20080510/main/Timer.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/main/Timer.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Timer.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Timer.pm 13831 2008-06-13 14:01:33Z topia $ # ----------------------------------------------------------------------------- # RunLoopに登録され、指定された時刻に起動するタイマーです。 # 現在の実装では、精度は秒となっています。 @@ -11,12 +11,14 @@ # # 3秒後にHello, world!と表示する。 # my $timer = Timer->new( +# Module => __PACKAGE__, # After => 3, # Code => sub { print "Hello, world!"; } # )->install; # # 3秒毎にHello, world!と表示する。 # my $timer = Timer->new( +# Module => __PACKAGE__, # After => 3, # Intervalでも良い # Code => sub { print "Hello, world!"; }, # Repeat => 1 @@ -24,6 +26,7 @@ # # 3秒後にHello, world!と表示する。 # my $timer = Timer->new( +# Module => __PACKAGE__, # At => time + 3, # Code => sub { print "Hello, world!"; } # )->install; @@ -85,7 +88,7 @@ if ($args{'Repeat'}) { $obj->{interval} = $args{'After'}; } - + $obj->{fire_time} = time + $args{'After'}; } @@ -93,6 +96,11 @@ $obj->{name} = $args{'Name'}; } + if (defined $args{'Module'}) { + require ModuleManager; + ModuleManager->shared_manager->add_module_object($args{'Module'}, $obj); + } + $obj; } @@ -177,4 +185,11 @@ $this; } +sub module_destruct { + my ($this, $module) = @_; + + undef $this->{code}; + $this->{runloop} && $this->uninstall; +} + 1; diff -urN tiarra-20080510/module/Auto/FetchTitle/Plugin/2ch.pm tiarra-20090206/module/Auto/FetchTitle/Plugin/2ch.pm --- tiarra-20080510/module/Auto/FetchTitle/Plugin/2ch.pm 1970-01-01 09:00:00.000000000 +0900 +++ tiarra-20090206/module/Auto/FetchTitle/Plugin/2ch.pm 2009-02-09 22:30:10.000000000 +0900 @@ -0,0 +1,122 @@ +## ---------------------------------------------------------------------------- +# Auto::FetchTitle::Plugin::2ch. +# ----------------------------------------------------------------------------- +# Mastering programmed by YAMASHINA Hio +# +# Copyright 2008 YAMASHINA Hio +# ----------------------------------------------------------------------------- +# $Id: 2ch.pm 15314 2008-07-06 15:32:46Z hio $ +# ----------------------------------------------------------------------------- +package Auto::FetchTitle::Plugin::2ch; +use strict; +use warnings; +use base 'Auto::FetchTitle::Plugin'; + +our $DEBUG; +*DEBUG = \$Auto::FetchTitle::DEBUG; + +1; + +# ----------------------------------------------------------------------------- +# $pkg->new(\%config). +# +sub new +{ + my $pkg = shift; + my $this = $pkg->SUPER::new(@_); + $this; +} + +# ----------------------------------------------------------------------------- +# $obj->register($context). +# +sub register +{ + my $this = shift; + my $context = shift; + + $context->register_hook($this, { + name => '2ch', + 'filter.prereq' => \&filter_prereq, + 'filter.response' => \&filter_response, + }); +} + +# ----------------------------------------------------------------------------- +# $this->filter_prereq($ctx, $arg); +# (impl:fetchtitle-filter) +# 2ch/prereq. +# +sub filter_prereq +{ + my $this = shift; + my $ctx = shift; + my $arg = shift; + + my $req =$arg->{req}; + + if( $req->{url} =~ m{^http://(\w+)\.2ch\.net/test/read\.(?:html|cgi)/(\w+)/(\d+)/} ) + { + $req->{redirect} = "http://$1.2ch.net/$2/dat/$3.dat"; + } + + $this; +} + +# ----------------------------------------------------------------------------- +# $this->filter_response($ctx, $arg). +# (impl:fetchtitle-filter) +# 2ch/response. +# +sub filter_response +{ + my $this = shift; + my $ctx = shift; + my $arg = shift; + + my $req = $arg->{req}; + + my $response = $req->{response}; + if( !ref($response) ) + { + $DEBUG and $ctx->_debug($req, "debug: - - skip/not ref"); + return; + } + if( $req->{result}{status_code}!=200 ) + { + $DEBUG and $ctx->_debug($req, "debug: - - skip/not success:$req->{result}{status_code}"); + return; + } + + if( $req->{url} !~ m{^http://\w+\.2ch\.net/\w+/dat/\d+\.dat\z} ) + { + $DEBUG and $ctx->_debug($req, "debug: - - skip/not 2ch.dat"); + return; + } + + my ($line) = $req->{result}{decoded_content} =~ /(.*)/ ? $1 : ''; + my ($name, $email, $date_id, $text, $title) = split(/<>/, $line); + my ($date, $id) = $date_id && $date_id =~ /(.*) (.*)/ ? ($1, $2) : ($date_id, ''); + $title or return; + + $req->{result}{result} = $title; +} + +# ----------------------------------------------------------------------------- +# End of Module. +# ----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- +# End of File. +# ----------------------------------------------------------------------------- +__END__ + +=encoding utf8 + +=for stopwords + YAMASHINA + Hio + ACKNOWLEDGEMENTS + AnnoCPAN + CPAN + RT + diff -urN tiarra-20080510/module/Auto/FetchTitle/Plugin/ExtractHeading.pm tiarra-20090206/module/Auto/FetchTitle/Plugin/ExtractHeading.pm --- tiarra-20080510/module/Auto/FetchTitle/Plugin/ExtractHeading.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/Auto/FetchTitle/Plugin/ExtractHeading.pm 2009-02-09 22:30:10.000000000 +0900 @@ -5,7 +5,7 @@ # # Copyright 2008 YAMASHINA Hio # ----------------------------------------------------------------------------- -# $Id: ExtractHeading.pm 10891 2008-05-01 12:18:10Z hio $ +# $Id: ExtractHeading.pm 22785 2008-11-05 13:45:32Z hio $ # ----------------------------------------------------------------------------- package Auto::FetchTitle::Plugin::ExtractHeading; use strict; @@ -25,6 +25,11 @@ { my $pkg = shift; my $this = $pkg->SUPER::new(@_); + *DEBUG = \$Auto::FetchTitle::DEBUG; + + $this->{extra} = undef; + $this->_parse_extra_config(); + $this; } @@ -44,12 +49,106 @@ } # ----------------------------------------------------------------------------- +# $this->_parse_extra_config(). +# parse user config. +# +sub _parse_extra_config +{ + my $this = shift; + my @config; + $this->{extra} = \@config; + + $DEBUG and $this->notice(__PACKAGE__."#_parse_extra_config"); + $DEBUG and $this->notice(">> ".join(", ", map{split(' ', $_)}$this->config->extra('all'))); + + foreach my $token (map{split(' ', $_)}$this->config->extra('all')) + { + $this->notice("extra: $token"); + my $name = "extra-$token"; + my $block = $this->config->$name; + if( !$block ) + { + $this->notice("no such extra config: $name"); + next; + } + if( !ref($block) ) + { + my $literal = $block; + $block = Configuration::Block->new($name); + $block->extract($literal); + } + my $has_param; + my $config = {}; + $config->{name} = $name; + $config->{url} = $block->url; + if( !$config->{url} ) + { + $this->notice("no url on $name"); + next; + } + if( my $recv_limit = $block->get('recv_limit') ) + { + while( $recv_limit =~ s/^\s*(\d+)\*(\d+)/$1*$2/e ) + { + } + $config->{recv_limit} = $recv_limit; + $has_param = 1; + } + my @extract; + foreach my $line ($block->extract('all')) + { + $has_param ||= 1; + my $type; + my $value = $line; + if( $value =~ s/^(\w+)(:\s*|\s+)// ) + { + $type = $1; + } + $type ||= 're'; + if( $type eq 're' ) + { + $value =~ s{^/(.*)/(\w*)\z}{(?$2:$1)}; + my $re = eval{ + local($SIG{__DIE__}) = 'DEFAULT'; + qr/$value/s; + }; + if( my $err = $@ ) + { + chomp $err; + $this->notice("invalid regexp $re on $name, $err"); + next; + } + push(@extract, $re); + }else + { + $this->notice("unknown extract type $type on $name"); + next; + } + } + if( @extract ) + { + $config->{extract} = @extract==1 ? $extract[0] : \@extract; + } + if( keys %$config==1 ) + { + $this->notice("no config on $name"); + next; + } + push(@config, $config); + } + + $this; +} + +# ----------------------------------------------------------------------------- # $this->_config(). # config for extract-heading. # sub _config { + my $this = shift; my $config = [ + @{$this->{extra}}, { # 1. ぷりんと楽譜. url => 'http://www.print-gakufu.com/*', @@ -61,10 +160,11 @@ # 2. zakzak. url => 'http://www.zakzak.co.jp/*', recv_limit => 10*1024, - extract => qr{(.*?)}s, + #extract => qr{(.*?)}s, + extract => qr{
    (.*?)
    }s, }, { - # 3. nikkei. + # 3a. nikkei. url => 'http://www.nikkei.co.jp/*', recv_limit => 16*1024, extract => [ @@ -74,11 +174,23 @@ remove => qr/^NIKKEI NET:/, }, { - # 4. nhkニュース. + # 3b. nikkei. + url => 'http://release.nikkei.co.jp/*', + recv_limit => 18*1024, + extract => qr{

    (.*)

    }s, + }, + { + # 4a. nhkニュース. url => 'http://www*.nhk.or.jp/news/*', extract => qr{

    (.*?)

    }, }, { + # 4b. nhk関西のニュース. + url => 'http://www*.nhk.or.jp/*/lnews/*', + recv_limit => 8*1024, + extract => qr{

    (.*?)

    }, + }, + { # 5. creative (timeout). url => 'http://*.creative.com/*', timeout => 5, @@ -109,6 +221,7 @@ # 9. ニコニコ動画 (メンテ画面). status => 503, url => 'http://www.nicovideo.jp/*', + recv_limit => 10*1024, extract => sub{ if( m{
    \s*

    現在ニコニコ動画は(メンテナンス中)です。

    \s*

    (.*?)
    }s ) { @@ -146,6 +259,223 @@ url => 'http://www.tv-asahi.co.jp/ann/news/*', extract => qr{(.*?)}s, }, + { + # 15. game? + url => 'http://splax.net/jun.html?p=*', + extract => sub{ + my $req = shift; + if( $req->{url} =~ /\?p=([^&;=#]+)/ ) + { + my $q = $1; + $q =~ s/%([0-9A-F]{2})/pack("H*",$1)/gie; + $q =~ s/\*([0-9A-F]{2})/pack("H*",$1)/gie; + $q = Unicode::Japanese->new($q, "sjis")->utf8; + $q =~ s/\*.*//; + $q = "「$qの唄」"; + }else + { + return; + } + }, + }, + { + # 16. recordchina. + url => 'http://www.recordchina.co.jp/group/*', + recv_limit => 12*1024, + extract => qr{

    (.*?)
    }s, + }, + { + # 17. oricon.groumet. + url => 'http://gourmet.oricon.co.jp/*', + recv_limit => 15*1024, + extract => qr{

    (.*?)

    }s, + }, + { + # 18a. biglobe.news. + url => 'http://news.biglobe.ne.jp/*', + recv_limit => 30*1024, + extract => qr{

    (.*?)(?: .*)?

    }s, + }, + { + # 18b. biglobe. + url => 'http://soudan1.biglobe.ne.jp/*', + recv_limit => 8*1024, + extract => qr{
    (.*?)
    }s, + }, + { + # 19. i-revo. + url => 'http://www.i-revo.jp/corporate/news/*', + extract => qr{

    .*?

    (.*?)

    }s, + }, + { + # 20. dq-status + url => 'http://u-enterprise.com/dqstatus/*', + extract => qr{

    (.*?)

    }s, + }, + { + # 21. emily. + url => 'http://shop-emily.com/shopdetail/*/order/', + recv_limit => 20*1024, + extract => qr{> (.*?)}s, + }, + { + # 22. wikipedia. + url => 'http://ja.wikipedia.org/wiki/*', + extract => sub{ + my $req = shift; + if( my $anchor = $req->{anchor} ) + { + $anchor =~ s/\.([0-9A-F]{2})/pack("H*",$1)/ge; + $anchor; + }else + { + return; + } + }, + }, + { + # 23. cure-maid. + url => 'http://www.curemaid.jp/index.php*', + recv_limit => 30*1024, + extract => qr{

    (.*?)

    }s, + timeout => 5, + }, + { + # 24. kyoto-np. + url => 'http://www.kyoto-np.co.jp/article.php?*', + recv_limit => 20*1024, + extract => qr{(.*?)}s, + #timeout => 5, + }, + { + # 25. fukuishimbun. + url => 'http://www.fukuishimbun.co.jp/modules/news2/article.php?storyid=*', + recv_limit => 20*1024, + extract => qr{

    (.*)

    }s, + }, + { + # 26. royce. + url => 'http://www.e-royce.com/items/other/*', + recv_limit => 50*1024, + }, + { + # 27. nintendo. + url => 'http://www.nintendo.co.jp/corporate/release/*', + extract => qr{
    (.*?)
    }s, + }, + { + # 28a. subeshi. + url => 'http://seizo.inte.co.jp/beshi/r/?k=*', + extract => sub{ + my ($p1,$p2,$p3,$p4,$p5) = m{\Q(.*?) さんのヒト型は
    }; + if( $name && m{/typeText/(\d+).gif} ) + { + my $type = $1; + my $typenames = [qw( + ひょっとこ + 捨て猫 + 暴君 + ひまわり + ノリノリ + ガラス彫刻 + 評論家 + ハードボイルド + 勇者 + リーダー + 全知全能 + みのむし + )]; + if( my $typename = $typenames->[$type] ) + { + my $params = "生き様=$p1,素直さ=$p2,積極性=$p3,心理=$p4,タフさ=$p5"; + return "$name さんのヒト型は「$typename」型です ($params)"; + }else + { + return; + } + }else + { + return; + } + }, + }, + { + # 28b. subeshi (aishou). + url => 'http://seizo.inte.co.jp/beshi/aishoResult/*', + extract => sub{ + my ($type1) = m{/img/aishoType/(\d+)-1.gif}; + my ($type2) = m{/img/aishoType/(\d+)-2.gif}; + my ($name1) = m{
    (.*?)
    }; + my ($name2) = m{
    (.*?)
    }; + my $keylabels = { + love => '恋愛', + work => '仕事', + friend => '友情', + }; + my $data = {}; + foreach my $key (qw(love work friend)) + { + my ($pt) = m{
    (\d+点)
    }; + my ($area) = m{
    (.*?)
    }s; + defined($pt) or $pt = '?'; + my @marks = $area =~ m{
    (.*?)
    }g; + my $label = $keylabels->{$key}; + my $marks = join('', @marks); + $data->{$key} = { + label => $label, + pt => $pt, + marks => $marks, + data => "$label=$pt/$marks", + }; + } + if( !grep{!defined($_)} ($type1, $type2, $name1, $name2) ) + { + my $typenames = [qw( + ひょっとこ + 捨て猫 + 暴君 + ひまわり + ノリノリ + ガラス彫刻 + 評論家 + ハードボイルド + 勇者 + リーダー + 全知全能 + みのむし + )]; + my $typename1 = $typenames->[$type1] || '?'; + my $typename2 = $typenames->[$type2] || '?'; + my $pair = "$name1\[$typename1]/$name2\[$typename2]"; + my $params = join(", ", map{$data->{$_}{data}} qw(love work friend)); + return "$pair ($params)"; + }else + { + return; + } + }, + }, + { + # 29. godiva. + url => 'http://www.godiva-l.com/recipes/drink/recipes*.html', + extract => qr{(.*?)}s, + }, + { + # 30a. 血液型ゲノム. + url => 'http://blood-genome.com/d/92008/*', + extract => qr{

    (.*?)}s, + }, + { + # 30b. 血液型ゲノム(相性). + url => 'http://blood-genome.com/c/92008/*', + extract => sub{ + my ($point) = m{

    (.*?)

    }s; + my ($compat) = m{
    (.*?)
    }s; + $compat =~ s/^(.*?。).*。(.*?。)/$1...$2/; + "$point, $compat"; + }, + }, ]; $config; } @@ -218,6 +548,11 @@ } my $extract_list = $conf->{extract}; + if( !$extract_list ) + { + $DEBUG and $ctx->_debug($req, "debug: - - no extract"); + next; + } if( ref($extract_list) ne 'ARRAY' ) { $extract_list = [$extract_list]; @@ -249,7 +584,7 @@ my $remove_list = $conf->{remove}; if( ref($remove_list) ne 'ARRAY' ) { - $remove_list = [$remove_list]; + $remove_list = defined($remove_list) ? [$remove_list] : []; } foreach my $_remove (@$remove_list) { @@ -291,3 +626,26 @@ CPAN RT +=begin tiarra-doc + +info: 本文から見出しを抽出するFetchTitleプラグイン. +default: off + +# Auto::FetchTitle { ... } での設定. +# + Auto::FetchTitle { +# plugins { +# ExtractHeading { +# extra: name1 name2 ... +# extra-name1 { +# url: http://www.example.com/* +# recv_limit: 10*1024 +# extract: re:
    (.*?)
    +# } +# } +# } +# } + +=end tiarra-doc + +=cut + diff -urN tiarra-20080510/module/Auto/FetchTitle/Plugin/Mixi.pm tiarra-20090206/module/Auto/FetchTitle/Plugin/Mixi.pm --- tiarra-20080510/module/Auto/FetchTitle/Plugin/Mixi.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/Auto/FetchTitle/Plugin/Mixi.pm 2009-02-09 22:30:10.000000000 +0900 @@ -5,7 +5,7 @@ # # Copyright 2008 YAMASHINA Hio # ----------------------------------------------------------------------------- -# $Id: Mixi.pm 10908 2008-05-01 16:57:24Z hio $ +# $Id: Mixi.pm 24729 2008-11-24 07:01:17Z hio $ # ----------------------------------------------------------------------------- package Auto::FetchTitle::Plugin::Mixi; use strict; @@ -28,6 +28,10 @@ our $DEBUG; *DEBUG = \$Auto::FetchTitle::DEBUG; +our $MSG_NOPERM_EN = "requested page in mixi is not permitted"; +our $MSG_NOPERM_JA = "mixi内の指定されたページは許可リストにありませんでした"; +our $MSG_NOPERM = $MSG_NOPERM_JA; + 1; # ----------------------------------------------------------------------------- @@ -57,6 +61,81 @@ } # ----------------------------------------------------------------------------- +# $this->not_permitted($ctx, $req, $block). +# +sub not_permitted +{ + my $this = shift; + my $ctx = shift; + my $req = shift; + my $block = shift; + + my $txt = $block->mixi_noperm_msg || $MSG_NOPERM; + my $type = $txt =~ s/^(\w+):\s*// ? $1 : 'noerror'; + + $txt = Tools::HashTools::replace_recursive( + $txt, [{url => $req->{url}}] + ); + + if( !$req->{response} ) + { + # prereq. + if( $type eq 'noerror' ) + { + $req->{response} = $this->response_noerror($txt); + }else + { + # scalar response means error message. + $req->{response} = $txt; + } + }else + { + if( $type ne 'noerror' ) + { + $txt = "(エラー) $txt"; + } + $req->{result}{result} = $txt; + } +} + +# ----------------------------------------------------------------------------- +# $res = $this->response_noerror($msg). +# +sub response_noerror +{ + my $this = shift; + my $txt = shift; + + my $txt_esc = $this->_escapeHTML($txt); + my $content = "$txt_esc\n"; + + my $res = { + Protocol => 'HTTP/1.0', + Code => 200, + Message => 'Success', + Header => { + 'Content-Type' => 'text/html; charset=utf-8', + 'Content-Length' => length($content), + }, + Content => $content, + StreamState => 'finished', + }; + $res; +} + +sub _escapeHTML +{ + my $this = shift; + my $txt = shift; + $txt =~ s/&/&/g; + $txt =~ s//>/g; + $txt =~ s/"/"/g; + $txt =~ s/'/'/g; + $txt; +} + +# ----------------------------------------------------------------------------- # $this->detect_page($ctx, $req, $block). # 取得できるページか確認. # @@ -158,25 +237,34 @@ foreach my $page (@allow_pages) { $DEBUG and $ctx->_debug($req, "- check $page->{name}."); - my @values = $req->{url} =~ $page->{re}; + my $values = [$req->{url} =~ $page->{re}]; my $keys = $page->{keys} || ['']; - if( @values != @$keys) + if( @$values != @$keys) { $DEBUG and $ctx->_debug($req, "- - not match."); next; } - $page->{values} = \@values; foreach my $idx (0..$#$keys) { my $key = $keys->[$idx]; $key or next; - my $val = $page->{values}[$idx]; + my $val = $values->[$idx]; my $conf_key = "mixi_$key"; my $allowed; - foreach my $conf_val ($block->$conf_key('all')) + foreach my $_conf_val ($block->$conf_key('all')) { - $allowed ||= $conf_val && $conf_val == $val; + my $conf_val = $_conf_val; # copy (unalias). + $conf_val =~ s/\s*#.*//s; + $conf_val or next; + foreach my $item (split(/[,\s]+/, $conf_val)) + { + $item or next; + $item eq '*' and $allowed=1, last; + $item =~ /^0*(\d+)\z/ or next; + $1 == $val and $allowed=1, last; + } + $allowed and last; } if( !$allowed ) { @@ -185,6 +273,7 @@ } } $DEBUG and $ctx->_debug($req, "- - match."); + $page->{values} = $values; return $page; } return undef; @@ -223,10 +312,10 @@ } $prev = $prev->{old}; } - if( $seen->{login} >= 2 || $seen->{news_login} >= 2 ) + if( $seen->{login} >= 2 || $seen->{news_login} >= 3 ) { - my $msg = "mixi multiple login pages (login=$seen->{login},news_login=$seen->{news_login})"; - $ctx->_debug($req, $msg); + my $msg = "login pages (login=$seen->{login},news_login=$seen->{news_login})"; + #$ctx->_debug($req, $msg); #$req->{response} = "mixi multiple login pages (login=$seen->{login},news_login=$seen->{news_login})"; #return; } @@ -234,11 +323,11 @@ my $allowed = $this->detect_page($ctx, $req, $block); if( !$allowed ) { - $req->{response} = "requested page in mixi is not permitted"; + $this->not_permitted($ctx, $req, $block); return; } - $ctx->_apply_recv_limit($req, 12*1024); + $ctx->_apply_recv_limit($req, 1000*1024); $ctx->_add_cookie_header($req, $this->{cookie_jar}); } @@ -269,6 +358,14 @@ } my $result = $req->{result}; + if( $result->{decoded_content} =~ m{
    (.*?)
    }s ) + { + my $txt = $1; + $txt = $ctx->_fixup_title($txt); + $req->{result}{result} = $txt . ' - ' . $req->{result}{result}; + return; + } + if( $result->{decoded_content} =~ m{
    (.*)
    }s ) { my $path = $1; @@ -280,7 +377,7 @@ my $page = $this->detect_page($ctx, $req, $block); if( !$page ) { - $req->{response} = "requested page in mixi is not permitted"; + $this->not_permitted($ctx, $req, $block); return; } } @@ -356,3 +453,51 @@ CPAN RT +=begin tiarra-doc + +info: Mixiにログインして見出し抽出出来るようにするFetchTitleプラグイン. +default: off + +# Auto::FetchTitle { ... } での設定. +# +# + Auto::FetchTitle { +# mask: #* &mixi http://* +# plugins { +# Mixi { +# mixi-user: xxx +# mixi-pass: yyy +# } +# } +# conf-mixi { +# filter-mixi { +# url: http://mixi.jp/* +# url: http://news.mixi.jp/* +# type: mixi +# timeout: 10 +# #閲覧可能なコミュニティの指定. +# #mixi-community: 0 +# #閲覧可能なユーザの指定. +# #指定したユーザには足跡踏んで見に行きます. +# #mixi-friend: 0 +# #閲覧可能にしていないページを表示したときのメッセージ. +# #要求されたページを #(url) で展開できます. +# #mixi-noperm-msg: not permitted #(url). +# } +# } +# } +# +# アカウント情報は plugins Mixi に記述. +# mixi-pass には {B}bbbb でBASE64エンコード値も可能. +# +# newsだけしか使わない場合でも, ログイン処理が必要なので +# mixi.jp 内のいくつかのURLはこのプラグインで処理する必要があります. +# url: http://news.mixi.jp/* +# url: http://mixi.jp/issue_ticket.pl?* +# url: http://mixi.jp/login.pl +# url: http://mixi.jp/check.pl?* +# (それぞれ, ニュースページ, ログイン処理, エラー検出, 途中経路になります.) + +=end tiarra-doc + +=cut + diff -urN tiarra-20080510/module/Auto/FetchTitle/Plugin/TouhouReplay.pm tiarra-20090206/module/Auto/FetchTitle/Plugin/TouhouReplay.pm --- tiarra-20080510/module/Auto/FetchTitle/Plugin/TouhouReplay.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/Auto/FetchTitle/Plugin/TouhouReplay.pm 2009-02-09 22:30:10.000000000 +0900 @@ -5,7 +5,7 @@ # # Copyright 2008 YAMASHINA Hio # ----------------------------------------------------------------------------- -# $Id: TouhouReplay.pm 10891 2008-05-01 12:18:10Z hio $ +# $Id: TouhouReplay.pm 20013 2008-09-27 02:24:28Z hio $ # ----------------------------------------------------------------------------- package Auto::FetchTitle::Plugin::TouhouReplay; use strict; @@ -72,7 +72,7 @@ return; } - $ctx->_apply_recv_limit($req, 100*1024); + $ctx->_apply_recv_limit($req, 500*1024); $this; } diff -urN tiarra-20080510/module/Auto/FetchTitle/Plugin.pm tiarra-20090206/module/Auto/FetchTitle/Plugin.pm --- tiarra-20080510/module/Auto/FetchTitle/Plugin.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/Auto/FetchTitle/Plugin.pm 2009-02-09 22:30:10.000000000 +0900 @@ -5,7 +5,7 @@ # # Copyright 2008 YAMASHINA Hio # ----------------------------------------------------------------------------- -# $Id: Plugin.pm 10891 2008-05-01 12:18:10Z hio $ +# $Id: Plugin.pm 11369 2008-05-10 15:56:26Z hio $ # ----------------------------------------------------------------------------- package Auto::FetchTitle::Plugin; use strict; @@ -29,6 +29,27 @@ } # ----------------------------------------------------------------------------- +# $pkg->config(). +# (util) +# +sub config +{ + shift->{config} +} + +# ----------------------------------------------------------------------------- +# $pkg->notice($msg). +# (util) +# +sub notice +{ + my $this = shift; + my $msg = shift; + defined($msg) or $msg = ''; + RunLoop->shared_loop->notify_msg($msg); +} + +# ----------------------------------------------------------------------------- # $obj->register($context). # sub register diff -urN tiarra-20080510/module/Auto/FetchTitle.pm tiarra-20090206/module/Auto/FetchTitle.pm --- tiarra-20080510/module/Auto/FetchTitle.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/Auto/FetchTitle.pm 2009-02-09 22:30:10.000000000 +0900 @@ -5,7 +5,7 @@ # # Copyright 2008 YAMASHINA Hio # ----------------------------------------------------------------------------- -# $Id: FetchTitle.pm 11015 2008-05-03 06:42:38Z hio $ +# $Id: FetchTitle.pm 29425 2009-02-02 06:17:32Z drry $ # ----------------------------------------------------------------------------- package Auto::FetchTitle; use strict; @@ -22,7 +22,7 @@ use Tiarra::Encoding; use Tools::HTTPClient; -our $VERSION = '0.02'; +our $VERSION = '0.04'; # 全角空白. our $U_IDEOGRAPHIC_SPACE = "\xe3\x80\x80"; @@ -50,11 +50,25 @@ $LATIN1_MAP[0x82-0x80] = ','; $LATIN1_MAP[0xa0-0x80] = ' '; +our $TOP_LEVEL_DOMAINS = { + map{ $_=>1 } qw(.biz .com .edu .gov .info .int .mil .net .org + .aero .asia .cat .coop .jobs .mobi .museum .tel .travel) +}; + our $HAS_IMAGE_EXIFTOOL = do{ eval{ local($SIG{__DIE__}) = 'DEFAULT'; require Image::ExifTool; }; !$@; }; +our $HAS_TOOLS_ID3TAG = do{ + eval { + local($SIG{__DIE__}) = 'DEFAULT'; + require Tools::ID3Tag; + Module::Use->import('Tools::ID3Tag'); + }; + !$@; +}; + our $MAX_ACTIVE_REQUESTS = 3; our $MAX_URLS_IN_SINGLE_MESSAGE = 3; our $DEFAULT_RECV_LIMIT = 4*1024; # 4K bytes. @@ -81,7 +95,7 @@ # $obj->_process_command(..). # return. # -# $obj->extract_urls(..). +# $obj->_extract_urls(..). # $obj->_check_mask($url). # $obj->_create_request(..). # $obj->_add_request($req). @@ -116,8 +130,9 @@ $this->{loaded_at} = time; $this->{debug} = $this->config->debug; + $this->{old_config} = $this->config; - $this->{request_queue} = {}; # { $ch_full => [] }. + $this->{request_queue} = {}; # { $ch_full => [] }. $this->{reply_queue} = undef; $this->{reply_timer} = undef; @@ -132,6 +147,9 @@ weaken($weaken_this); $this->{conf_hook} = Configuration::Hook->new(sub{ my $this = $weaken_this or return; + local($DEBUG) = $this->{debug}; + $this->_reload_check() or return; + $DEBUG = $this->{debug}; # refresh from new config. $this->_reload_config(); $this->_reload_plugins(); })->install('reloaded'); @@ -160,6 +178,24 @@ } # ----------------------------------------------------------------------------- +# $obj->_reload_check(). +# +sub _reload_check +{ + my $this = shift; + if( !$this->{old_config} ) + { + return 1; + } + if( $this->{old_config}->equals($this->config) ) + { + $DEBUG and RunLoop->shared->notify_msg(__PACKAGE__."#_reload_config, not changed."); + return undef; + } + return 1; +} + +# ----------------------------------------------------------------------------- # $obj->_reload_config(). # sub _reload_config @@ -168,7 +204,7 @@ $this->_runloop->notify_msg(__PACKAGE__.", reload config."); $this->{debug} = $this->config->debug; - if( $this->{debug} && $this->{debug} =~ /^(off|no|false)/i ) + if( $this->{debug} && $this->{debug} =~ /^(?:off|no|false)/i ) { $this->{debug} = undef; } @@ -438,9 +474,9 @@ } $DEBUG and $this->_debug($full_ch_name, "debug: check $_url"); my $url = $_url; - $url =~ s{^ttp(s?)://}{http$1://}; + $url =~ s{^(?=ttps?://)}{h}; $url =~ m{^https?://} or next; - $url =~ s{^https?://[-\w.\x80-\xff]+\z}{$url/}; + $url =~ s{^https?://[-\w.\x80-\xff]+\z}{$&/}; $DEBUG && $url ne $_url and $this->_debug($full_ch_name, "debug: fixed url is $url"); # 処理対象か確認. @@ -475,13 +511,21 @@ my $url = shift; my $mask_conf = shift; + my $url_orig = $url; + $url =~ s/([^ -~])/'%'.uc(unpack("H*",$1))/ge; + if( $url ne $url_orig ) + { + $this->_debug($full_ch_name, "url will be encoded: $url"); + $this->_debug($full_ch_name, "original is: $url_orig"); + } + my $anchor; ($url, $anchor) = split(/#/, $url, 2); my $req = { old => undef, # undef for first (non-redirect) request. ini_req => undef, # undef for first (non-redirect) request. redirected => undef, # nr of redirects (integer). - applied_filters => undef, # array-ref. + applied_filters => undef, # array-ref. url => $url, anchor => $anchor, @@ -527,7 +571,7 @@ foreach my $mask (@{$this->{mask}}) { - my $chan_match = Mask::match($mask->{ch_mask}, $full_ch_name); + my $chan_match = Mask::match($mask->{ch_mask}, $full_ch_name); if( !$chan_match ) { defined($chan_match) or next; @@ -567,10 +611,9 @@ my @urls = map{ m{ (\S?\w+://\S+) }gx } @tokens; foreach my $url (@urls) { - my $par_begin = substr($url, 0, 1); + my $par_begin = $url =~ s/^(\W)// ? $1 : ''; my $par_end = $pars->{$par_begin}; $par_end or next; - $url =~ s/^\Q$par_begin\E//; $url =~ s/\Q$par_end\E\z//; } @@ -643,7 +686,7 @@ my $queue = ($this->{request_queue}{$full_ch_name} ||= []); push(@$queue, $req); - + # drop orphan requests if exists. $this->_close_request($full_ch_name); @@ -670,7 +713,7 @@ if( $val && $val =~ s/^\{(.*?)\}// ) { my $type = $1; - if( $type =~ /^(B|B64|BASE64)\z/ ) + if( $type =~ /^B(?:(?:ASE)?64)?\z/ ) { eval { require MIME::Base64; }; if( $@ ) @@ -878,7 +921,7 @@ if( $when eq 'prereq' ) { - $req->{headers}{'User-Agent'} ||= "FetchTitle/$VERSION (tiarra)"; + $req->{headers}{'User-Agent'} ||= "FetchTitle/$VERSION (Tiarra)"; if( $url =~ m{https?://\w+\.2ch\.net(?:/|$)} ) { $DEBUG and $this->_debug($req, "debug: change user-agent for 2ch"); @@ -924,7 +967,7 @@ { push(@types, 'basic'); } - if( $block->cookie && !grep {$_ eq 'cookie'} @types) + if( $block->cookie && !grep {$_ eq 'cookie'} @types) { push(@types, 'cookie'); } @@ -1153,7 +1196,7 @@ if( my $addr = !$req->{addr_checked} && $req->{httpclient}->{addr} ) { - my $desc = $this->_addr_check($addr); + my $desc = $this->_addr_check($addr); if( !$desc ) { $req->{addr_checked} = 'not local'; @@ -1209,7 +1252,7 @@ my $addr = shift; my @digits = $addr =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)\z/; - @digits or return undef; + @digits or return undef; grep{ $_>255 || /^0./ } @digits and return undef; my $addr_num = ($digits[0] << 24) | ($digits[1] << 16) | ($digits[2] << 8) | $digits[3]; @@ -1264,7 +1307,7 @@ } } } - + return undef; } @@ -1589,17 +1632,24 @@ } # detect refresh tag. - if( $content =~ m{}i ) + my $content2 = $content; + $content2 =~ s///g; + if( $content2 =~ m{ + ]*?)?\s + (?:HTTP-EQUIV\s*=\s*(["'])refresh\1(?:\s[^>]*?)?\sCONTENT\s*=\s*(["'])(\d+)\s*;\s*URL=([^"'<]+)\2| + CONTENT\s*=\s*(["'])(\d+)\s*;\s*URL=([^"'<]+)\5(?:\s[^>]*?)?\sHTTP-EQUIV\s*=\s*(["'])refresh\8) + (?:\s[^>]*|/)?> + }ix ) { - my $after = $1; - my $url = $2; + my $after = $3 || $6; + my $url = $4 || $7; $DEBUG and $this->_debug($full_ch_name, "debug: meta.refresh found: $after; $url"); $result->{redirect} = $url; } # detect encoding. my $enc = 'auto'; - if( $content =~ m{}i ) + if( $headers->{'Content-Type'} && $headers->{'Content-Type'} =~ /;\s*charset=(\S+)/ ) { my $e = lc($1); $enc = $e =~ /s\w*jis/ ? 'sjis' @@ -1608,18 +1658,23 @@ : $e =~ /iso-2022-jp/ ? 'jis' : $e =~ /\bjis\b/ ? 'jis' : $enc; - $DEBUG and $this->_debug($full_ch_name, "debug: charset $enc from meta ($e)"); + $DEBUG and $this->_debug($full_ch_name, "debug: charset $enc from http-header ($e)"); } - if( $enc eq 'auto' && $headers->{'Content-Type'} && $headers->{'Content-Type'} =~ /;\s*charset=(\S+)/ ) - { - my $e = lc($1); + if( $enc eq 'auto' && $content2 =~ m{ + ]*?)?\s + (?:http-equiv\s*=\s*(["'])Content-Type\1(?:\s[^>]*?)?\scontent\s*=\s*(["'])\w+/\w+(?:\+\w+)*\s*;\s*charset=([-\w]+)\2| + content\s*=\s*(["'])\w+/\w+(?:\+\w+)*\s*;\s*charset=([-\w]+)\4(?:\s[^>]+?)?\shttp-equiv\s*=\s*(["'])Content-Type\6) + (?:\s[^>]*|/)?> + }ix ) + { + my $e = lc($3 || $5); $enc = $e =~ /s\w*jis/ ? 'sjis' : $e =~ /euc/ ? 'euc' : $e =~ /utf-?8/ ? 'utf8' : $e =~ /iso-2022-jp/ ? 'jis' : $e =~ /\bjis\b/ ? 'jis' : $enc; - $DEBUG and $this->_debug($full_ch_name, "debug: charset $enc from http-header ($e)"); + $DEBUG and $this->_debug($full_ch_name, "debug: charset $enc from meta ($e)"); } if( $enc eq 'auto' ) { @@ -1634,13 +1689,15 @@ $DEBUG and $this->_debug($full_ch_name, "debug: broken utf-8 found and fixed"); my $url = $req->{url}; $this->_log("broken utf-8 on $url (enc=$enc)"); + $DEBUG and $this->_debug($req, "broken utf-8 on $url (enc=$enc)"); } # decode. - $content = $ENCODER->new($content, $enc)->utf8; + $content = $ENCODER->new($content, $enc)->utf8; + $content2 = $ENCODER->new($content2, $enc)->utf8; $result->{decoded_content} = $content; - my ($title) = $content =~ m{\s*(.*?)\s*}is; + my ($title) = $content2 =~ m{]*)?>\s*(.*?)\s*}is; $DEBUG && !$title and $this->_debug($full_ch_name, "debug: no title elements in document"); if( defined($title) ) @@ -1653,7 +1710,7 @@ } my ($ctype) = split(/[ ;]/, $headers->{'Content-Type'}, 2); - $ctype ||= 'unknown/unkown'; + $ctype ||= 'unknown/unkown'; $result->{content_type} = $ctype; $DEBUG and $this->_debug($full_ch_name, "debug: content-type: $ctype"); @@ -1661,11 +1718,11 @@ if( $reply eq '' ) { $DEBUG and $this->_debug($full_ch_name, "debug: check icecast"); - if( my $icy_name = $headers->{'icy-name'} ) + if( my $icy_name = $headers->{'Icy-Name'} ) { # Icecast. - my $desc = $headers->{'icy-description'}; - my $bitrate = $headers->{'icy-br'}; + my $desc = $headers->{'Icy-Description'}; + my $bitrate = $headers->{'Icy-Br'}; $reply = $icy_name; if( defined($bitrate) ) { @@ -1678,13 +1735,35 @@ $reply = $ENCODER->new($reply,'auto')->utf8; } } - if( $ctype eq 'audio/x-mpegurl' && $res->{StreamState} eq 'finished' ) + if( $ctype eq 'audio/x-mpegurl' && ($res->{StreamState} eq 'finished' || $res->{StreamState} eq 'body') ) { - if( $content =~ m{^(\w+://[-.\w]+\S*)\s*$}m ) + if( $content =~ m{^(\w+://[-.\w:]+\S*)\s*\z} ) { $result->{redirect} = substr($content, 0, length($1)); # keep taintness. } } + if( !$reply && $ctype eq 'audio/mpeg' && ($res->{StreamState} eq 'finished' || $res->{StreamState} eq 'body') ) + { + if( $content =~ m{^ID3} && $HAS_TOOLS_ID3TAG ) + { + # from raw content. + my $info = Tools::ID3Tag->extract($res->{Content}); + #$DEBUG and $this->_debug($req, "ID3Tag.size = ".($info->{size} || '-')."/".length($content)); + #$DEBUG and $this->_debug($req, "ID3Tag.version = ".($info->{version} || '-')); + #$DEBUG and $this->_debug($req, "ID3Tag.title = ".($info->{title} || '-')); + #$DEBUG and $this->_debug($req, "ID3Tag.album = ".($info->{album} || '-')); + #$DEBUG and $this->_debug($req, "ID3Tag.artist = ".($info->{artist} || '-')); + $reply = $info->{title} || 'no title'; + if( $info->{album} ) + { + $reply .= " / $info->{album}"; + } + if( $info->{artist} ) + { + $reply .= " ($info->{artist})"; + } + } + } if( $reply eq '' || $ctype !~ /html/ ) { @@ -1749,7 +1828,7 @@ my $url = shift; ref($url) and $url = $url->{url}; - my ($scheme, $domain, $path) = $url =~ m{^(http|https)://(?:[^/]+\@)?([^/]+)(.*)}; + my ($scheme, $domain, $path) = $url =~ m{^(https?)://(?:[^/]+\@)?([^/]+)(.*)}; if( !$scheme ) { return; @@ -1872,10 +1951,24 @@ $attrs{lc $key} = defined($val) ? $val : 1; } } - if( $attrs{domain} && $attrs{domain} ne $domain ) + if( $attrs{domain} ) { - $this->_error("_parse_cookies, domain $attrs{domain} does not match with one in url $domain, ignore this cookie"); - next; + my $pass; + if( $attrs{domain} eq $domain ) + { + $pass = 1; + }elsif( $domain =~ /\Q$attrs{domain}\E\z/ && $attrs{domain} =~ /^\./ ) + { + my $nr_dots = $attrs{domain} =~ tr/././; + $pass = $nr_dots >= 3; + $pass ||= $attrs{domain} =~ /\.[^.]{3,}\.jp\z/; + $pass ||= $attrs{domain} =~ /(\.\w+)\z/ && $TOP_LEVEL_DOMAINS->{$1}; + } + if( !$pass ) + { + $this->_error("_parse_cookies, domain $attrs{domain} does not match with one in url $domain, ignore this cookie"); + next; + } } $attrs{_scheme} = $scheme; $attrs{domain} ||= $domain; @@ -1894,7 +1987,7 @@ $tm[5] += 1900, $tm[4] += 1; sprintf('%04d-%02d-%02d %02d:%02d:%02d', reverse @tm[0..5]); }; - $attrs{_is_expired} = $cmp lt $now_cmp; + $attrs{_is_expired} = $cmp lt $now_cmp; } push(@parsed_cookies, \%attrs); #$DEBUG and print "new cookie: ".Dumper(\%attrs); @@ -1941,24 +2034,26 @@ # ----------------------------------------------------------------------------- # $txt = $this->_unescapeHTML($html). -# HTML中の実際参照をデリファレンス. (ってHTMLもそういうのかな?) +# HTML中の文字参照をデリファレンス. (ってHTMLもそういうのかな?) # sub _unescapeHTML { my $this = shift; my $html = shift; my $map = { - nbsp => ' ', - lt => '<', - gt => '>', - amp => '&', - quot => '"', + nbsp => ' ', # "\xc2\xa0" + lt => '<', + gt => '>', + amp => '&', + quot => '"', + laquo => "\xc2\xab", + raquo => "\xc2\xbb", }; $html =~ s{&#(\d+);|&#x([0-9a-fA-F]+);|&(\w+);}{ if( defined($1) || defined($2) ) { my $ch = defined($1) ? $1 : hex($2); - $ch && $ch < 127 ? chr($ch) : "[$ch]"; + $ch && $ch < 127 ? chr($ch) : $ENCODER->new(pack("n",$ch), 'ucs2')->utf8; }else { $map->{$3} || "[$3]"; @@ -2001,8 +2096,8 @@ if( !$this->{reply_timer} ) { $this->{reply_timer} = Timer->new( - After => -1, # immediately. - Code => sub{ $this->_reply_timer_handler() }, + After => -1, # immediately. + Code => sub{ $this->_reply_timer_handler() }, )->install(); } } @@ -2240,9 +2335,9 @@ #debug-dumpfile: fetchtitle.log # NOTE: -# 利用するにはcodereposから +# 利用するにはCodeReposから # module/Tools/HTTPClient.pm rev.8220 -# main/Tiarra/Socket/Buffered.pm rev.8219 +# main/Tiarra/Socket/Buffered.pm rev.8219 # 以降が必要です. =end tiarra-doc @@ -2260,7 +2355,7 @@ + Auto::FetchTitle { reply-prefix: "(FetchTitle) " reply-suffix: " [AR]" - + mask: * http://* } diff -urN tiarra-20080510/module/Auto/Im.pm tiarra-20090206/module/Auto/Im.pm --- tiarra-20080510/module/Auto/Im.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/Auto/Im.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,32 +1,41 @@ # ----------------------------------------------------------------------------- -# $Id: Im.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Im.pm 28633 2009-01-18 19:49:53Z takano32 $ # ----------------------------------------------------------------------------- package Auto::Im; use strict; use warnings; use base qw(Module); -use Module::Use qw(Auto::AliasDB Tools::HTTPClient); +use Module::Use qw(Auto::AliasDB Tools::HTTPClient Auto::Utils); use Auto::AliasDB; use Tools::HTTPClient; # >= r11345 +use Auto::Utils; use HTTP::Request::Common; sub new { my ($class) = shift; my $this = $class->SUPER::new(@_); + $this->config_reload(undef); + + return $this; +} + +sub config_reload { + my ($this, $old_config) = @_; + if ($this->config->secret) { - # signature required - require Digest::SHA; + # signature required + require Digest::SHA; } my $regex = join '|', ( - (map { "(?:$_)" } $this->config->regex_keyword('all')), - (map { "(?i:\Q$_\E)" } map { split /,/ } $this->config->keyword('all')), - ); + (map { "(?:$_)" } $this->config->regex_keyword('all')), + (map { "(?i:\Q$_\E)" } map { split /,/ } $this->config->keyword('all')), + ); eval { - $this->{regex} = qr/$regex/; + $this->{regex} = qr/$regex/; }; if ($@) { - $this->_runloop->notify_error($@); + $this->_runloop->notify_error($@); } return $this; @@ -44,7 +53,8 @@ my $full_ch_name = $msg->param(0); if ($text =~ $this->{regex} && Mask::match_deep_chan( - [$this->config->mask('all')],$msg->prefix,$full_ch_name)) { + [Mask::array_or_all_chan($this->config->mask('all'))], + $msg->prefix,$full_ch_name)) { my $url = "http://im.kayac.com/api/post/" . $this->config->user; my $text = Auto::AliasDB->stdreplace( @@ -52,6 +62,7 @@ $this->config->format || '[tiarra][#(channel):#(nick.now)] #(text)', $msg, $sender, channel => $full_ch_name, + raw_channel => Auto::Utils::get_raw_ch_name($msg, 0), text => $text, ); my @data = (message => $text); @@ -99,6 +110,7 @@ # im.kayac.com に送るメッセージのフォーマットを指定します。 # デフォルト値: [tiarra][#(channel):#(nick.now)] #(text) +# #(channel) のかわりに #(raw_channel) を利用するとネットワーク名がつきません。 format: [tiarra][#(channel):#(nick.now)] #(text) # im.kayac.comで登録したユーザ名を入力します。 diff -urN tiarra-20080510/module/Auto/MesMail.pm tiarra-20090206/module/Auto/MesMail.pm --- tiarra-20080510/module/Auto/MesMail.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/Auto/MesMail.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: MesMail.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: MesMail.pm 12336 2008-05-25 10:03:57Z topia $ # ----------------------------------------------------------------------------- # copyright (C) 2003 Topia . all rights reserved. package Auto::MesMail; diff -urN tiarra-20080510/module/Auto/Outputz.pm tiarra-20090206/module/Auto/Outputz.pm --- tiarra-20080510/module/Auto/Outputz.pm 1970-01-01 09:00:00.000000000 +0900 +++ tiarra-20090206/module/Auto/Outputz.pm 2009-02-09 22:30:10.000000000 +0900 @@ -0,0 +1,163 @@ +# ----------------------------------------------------------------------------- +# $Id: Outputz.pm 24519 2008-11-20 15:49:39Z topia $ +# ----------------------------------------------------------------------------- +package Auto::Outputz; +use strict; +use warnings; +use base qw(Module); +use Multicast; +use Module::Use qw(Auto::AliasDB Tools::HTTPClient Auto::Utils); +use Auto::AliasDB; +use Tools::HTTPClient; # >= r11345 +use Auto::Utils; +use HTTP::Request::Common; + +sub new { + my ($class) = shift; + my $this = $class->SUPER::new(@_); + + $this->config_reload(undef); + + return $this; +} + +sub config_reload { + my ($this, $old_config) = @_; + + if (!$this->config->key) { + die __PACKAGE__.": key must be filled."; + } + + $this->{channels} = []; + foreach ($this->config->channel('all')) { + my ($dirname,$mask) = split /\s+/; + if (!defined($dirname) || $dirname eq '' || + !defined($mask) || $mask eq '') { + die 'Illegal definition in '.__PACKAGE__."/channel : $_\n"; + } + push @{$this->{channels}},[$dirname,$mask]; + } + $this->{matching_cache} = {}; + + my $cmds; + if ($this->config->commands) { + $cmds = join('|', map quotemeta, split /\s+/, $this->config->commands); + } else { + $cmds = 'PRIVMSG'; + } + $this->{commands} = qr/^$cmds$/; + + return $this; +} + +## from Log::Channel +sub _mangle_string { + my ($this, $str) = @_; + + $str =~ s/![0-9A-Z]{5}/!/; + $str =~ s{([^-\w@#%!+&.\x80-\xff])}{ + sprintf('=%02x', unpack("C", $1)); + }ge; + + $str; +} + +sub _channel_match { + # 指定されたチャンネル名にマッチするログ保存ファイルのパターンを定義から探す。 + # 一つもマッチしなければundefを返す。 + # このメソッドは検索結果を$this->{matching_cache}に保存して、後に再利用する。 + my ($this,$channel,$chan_short,$network) = @_; + + my $cached = $this->{matching_cache}->{$channel}; + if (defined $cached) { + if ($cached eq '') { + # マッチするエントリは存在しない、という結果がキャッシュされている。 + return undef; + } + else { + return $cached; + } + } + + foreach my $ch (@{$this->{channels}}) { + if (Mask::match($ch->[1],$channel)) { + my $name = Tools::HashTools::replace_recursive( + $ch->[0], [{ + channel => $this->_mangle_string($channel), + channel_short => $this->_mangle_string($chan_short), + network => $this->_mangle_string($network)}]); + + $this->{matching_cache}->{$channel} = $name; + return $name; + } + } + $this->{matching_cache}->{$channel} = ''; + undef; +} + +sub message_arrived { + my ($this,$msg,$sender) = @_; + + # クライアントからのメッセージか? + if ($sender->isa('IrcIO::Client')) { + if ($msg->command =~ $this->{commands}) { + my $text = $msg->param(1); + my $full_ch_name = $msg->param(0); + my ($target, $network) = Multicast::detach($full_ch_name); + if (Multicast::nick_p($target)) { + $target = 'priv'; + $full_ch_name = Multicast::attach($target, $network); + } + + # calc size + my $len = $text =~ tr/\x00-\x7f\xc0-\xf7/\x00-\x7f\xc0-\xf7/; + + my $name = $this->_channel_match($full_ch_name, $target, $network); + if ($name) { + my $url = "http://outputz.com/api/post/"; + my @data = (key => $this->config->key, + uri => $name, + size => $len); + my $runloop = $this->_runloop; + Tools::HTTPClient->new( + Request => POST($url, \@data), + )->start( + Callback => sub { + my $stat = shift; + $runloop->notify_warn(__PACKAGE__." post failed: $stat") + unless ref($stat); + ## FIXME: check response (should check 'error') + }, + ); + } + } + } + + return $msg; +} + +1; + +=pod +info: チャンネルの発言文字数を outputz に送信する +default: off + +# 復活の呪文。 +key: some secret + +# 送信対象にするコマンドの設定。 +# 省略された場合は PRIVMSG 。 +# パラメータ1が送信先、パラメータ2が本文でなければ動作しないので、 +# 動作するコマンドは PRIVMSG/NOTICE/TOPIC/PART 程度。 +-command: PRIVMSG + +# 各チャンネルのURIの設定。 +# 記述された順序で検索されるので、全てのチャンネルにマッチする"*"などは最後に書かなければならない。 +# フォーマットは次の通り。 +# channel: (<チャンネル名> / 'priv')@<ネットワーク名> +# #(channel) はチャンネル名に、 #(channel_short) はネットワークなしの +# チャンネル名に、 #(network) はネットワーク名にそれぞれ置き換えられる。 +# また、危険な文字は自動的にエスケープされる。 +channel: http://#(network).irc.example.com/#(channel_short) * + +=cut diff -urN tiarra-20080510/module/Auto/Reply.pm tiarra-20090206/module/Auto/Reply.pm --- tiarra-20080510/module/Auto/Reply.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/Auto/Reply.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,6 +1,6 @@ # -*- cperl -*- # ----------------------------------------------------------------------------- -# $Id: Reply.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Reply.pm 12851 2008-05-30 13:50:00Z hio $ # ----------------------------------------------------------------------------- # copyright (C) 2003 Topia . all rights reserved. package Auto::Reply; @@ -13,39 +13,82 @@ use Tools::HashDB; use Mask; +our $DEFAULT_BLOCK_NAME = 'std'; +our $DEFAULT_MUILTILINE_LIMIT = 10; + sub new { my $class = shift; my $this = $class->SUPER::new(@_); $this->{config} = []; - $this->_load; + eval{ + $this->_load; + }; + if( $@ ) + { + $this->_error("$@"); + } return $this; } +sub _error +{ + my $this = shift; + my $msg = shift; + + $this->_runloop->notify_error(__PACKAGE__." -- ".$msg); +} + sub _load { my $this = shift; my $BLOCKS_NAME = 'blocks'; + my @block_names = $this->config->get($BLOCKS_NAME, 'all'); + if( !@block_names ) + { + @block_names = $DEFAULT_BLOCK_NAME; + if( !$this->config->get($DEFAULT_BLOCK_NAME) ) + { + $this->_("Both blocks: records and std block are not defined"); + return; + } + } - foreach my $blockname ($this->config->get($BLOCKS_NAME, 'all')) { - die "$blockname block name is reserved!" if $blockname eq $BLOCKS_NAME; + foreach my $blockname (@block_names) { + if( $blockname eq $BLOCKS_NAME ) + { + $this->_error("block name $blockname is reserved!"); + next; + } my $block = $this->config->get($blockname); - die "$blockname isn't block!" unless UNIVERSAL::isa($block, 'Configuration::Block'); + if( !$block ) + { + $this->_error("block $blockname is not defined"); + next; + } + if( !UNIVERSAL::isa($block, 'Configuration::Block') ) + { + $this->_error("$blockname isn't block!"); + next; + } push(@{$this->{config}}, { - mask => [Mask::array_or_all_chan($block->mask('all'))], - request => [$block->request('all')], - reply_format => [$block->reply_format('all')], - max_reply => $block->max_reply, - rate => $block->rate, - count_query => [$block->count_query('all')], - count_format => [$block->count_format('all')], - add => [$block->get('add', 'all')], - added_format => [$block->added_format('all')], - remove => [$block->remove('all')], + mask => [Mask::array_or_all_chan($block->mask('all'))], + request => [$block->request('all')], + reply_format => [$block->reply_format('all')], + max_reply => $block->max_reply, + rate => $block->rate, + count_query => [$block->count_query('all')], + count_format => [$block->count_format('all')], + add => [$block->get('add', 'all')], + added_format => [$block->added_format('all')], + remove => [$block->remove('all')], removed_format => [$block->removed_format('all')], - modifier => [$block->modifier('all')], - use_re => $block->use_re, - database => Tools::HashDB->new( + modifier => [$block->modifier('all')], + use_re => $block->use_re, + multivalue => $block->multivalue, + multivalue_limit => $block->multivalue_limit, + multivalue_seq => 0, # updated internally. + database => Tools::HashDB->new( $block->file, $block->file_encoding, $block->use_re, @@ -132,7 +175,31 @@ if (Mask::match_deep_chan($block->{mask}, $msg->prefix, $get_full_ch_name->())) { my $key = (_search($block, $msg->param(1), 1, $block->{rate}))[0]; if (defined $key) { - $reply_anywhere->($block->{database}->get_value_random($key)); + my $multivalue = $block->{multivalue} || 'random'; + if( $multivalue eq 'all' ) + { + my $limit = $block->{multivalue_limit} || $DEFAULT_MUILTILINE_LIMIT; + my $values = $block->{database}->get_array($key); + if( @$values > $limit ) + { + $values = [ @$values[0..$limit-1] ]; + } + $reply_anywhere->($values); + }elsif( $multivalue eq 'seq' || $multivalue eq 'sequence' ) + { + my $values = $block->{database}->get_array($key); + my $seq = $block->{multivalue_seq} || 0; + if( $seq < 0 || $seq >= @$values ) + { + $seq = 0; + } + $reply_anywhere->($values->[$seq]); + $block->{multivalue_seq} = ($seq + 1) % @$values; + }else + { + my $value = $block->{database}->get_value_random($key); + $reply_anywhere->($value); + } } } } @@ -176,58 +243,110 @@ # Auto::Aliasを有効にしていれば、エイリアス置換を行ないます。 # 使用するブロックの定義。 +# 省略すると std を使用. +# 複数個の blocks の指定も可能. blocks: std std { + # 1つの応答ブロックの定義. + # 一応全ての項目が省略可能ではあるけれど, + # 通常は最低限 file と file-encoding を使用する. + # IRCで応答の追加削除等を行いたいときにはそれに更に設定を追加する形. + # (IRC上で応答の追加削除は行うが保存はしない時に限ってfileを省略可能.) + + # 機能: + # - 通常応答(mask) + # - 登録数確認(count-query/mask) + # - 反応確認(request/modifier) + # - 反応追加(add/modifier) + # - 反応削除(remove/modifier) + # 通常応答以外は設定を省略することで機能を無効にできます。 + # データファイルと文字コードを指定します。 - # ファイルの中では一行に一つの"反応:メッセージ"を書いて下さい。 + # ファイルの中では一行に一つの"反応マスク:メッセージ"を書いて下さい。 file: reply.txt file-encoding: euc - # 反応チェックを行うキーワードを指定します。 - # 実際の指定方法は、「 <チェックしたい発言>」です。 - request: 反応チェック + # 1つの発言で複数の反応マスクにマッチする場合, + # どれにマッチするかは未定義です. + # ただ, どちらか1つにのみマッチします. + + # 同じ反応マスクに複数個のメッセージが記述してあった場合の処理. + # multivalue: random #==> ランダムに1つ選択. + # multivalue: all #==> 全て返す. + # multivalue: seq #==> 順番に1つずつ返す. + # 省略時及び認識できなかったときは random. + -multivalue: random + # 返す最大行数. + # multivalue: all の時のみ有効. + # (それ以外の時は1行しか返さない) + # デフォルトは 5 行まで. + -multivalue-limit: 5 - # request に反応するときのフォーマットを指定します。 - # #(key) がキーワード、 #(message) が発言に置換されます。 - reply-format: 「#(key)」という発言に「#(message)」と反応します。 + # 反応する人のマスク。 + # 通常応答と登録数の返答時にチェックされる。 + mask: * *!*@* + # plum: mask: *!*@* - # request に反応する最大個数を指定します。 - # あまり大きな値を指定すると、アタックが可能になったり、ログが流れて邪魔なので注意してください。 - max-reply: 5 + # マッチした1つの反応マスクが実際に発言に反応する確率を指定します。 + # 百分率です。省略された場合は100と見做されます。 + rate: 100 # メッセージの登録数を返答するキーワードを指定します。 + # 省略するとこの機能は無効になります。 + # 指定したときだけこの機能が有効になります。 + # mask で許可された人(通常応答を返す人)が使えます。 count-query: 反応登録数 # メッセージの登録数を返答するときの反応を指定します。 # formatで指定できるものと同じです。#(count)は登録数になります。 + # count-query を指定したときのみ必要。 count-format: 反応は#(count)件登録されています。 - # 反応する人のマスク。 - mask: * *!*@* - # plum: mask: *!*@* + # メッセージを追加するキーワードを指定します。 + # ここで指定したキーワードを発言すると、新しいメッセージを追加します。 + # 実際の追加方法は「 <追加するメッセージ>」です。 + # 省略するとこの機能は無効になります。 + # 指定したときだけこの機能が有効になります。 + # modifier で許可された人だけ使えます。 + -add: 反応追加 # 反応が追加されたときの反応を指定します。 # formatで指定できるものと同じです。#(message)は追加されたメッセージになります。 added-format: #(name|nick.now): #(key) に対する反応 #(message) を追加しました。 + # メッセージを削除するキーワードを指定します。 + # 実際の削除方法は「 <削除するキーワード>」です。 + # 省略するとこの機能は無効になります。 + # 指定したときだけこの機能が有効になります。 + # modifier で許可された人だけ使えます。 + -remove: 反応削除 + # メッセージが削除されたときの反応を指定します。 # formatで指定できるものと同じです。#(message)は削除されたメッセージになります。 removed-format: #(name|nick.now): #(key) #(message;に対する反応 %s|;) を #(count) 件削除しました。 - # 発言に反応する確率を指定します。百分率です。省略された場合は100と見做されます。 - rate: 100 + # 反応の確認を行うためのキーワードを指定します。 + # 通常応答と違って, multivalue-limit の制限を受けずに全てのマッチした応答を返します。 + # 実際の指定方法は、「 <チェックしたい発言>」です。 + # 省略するとこの機能は無効になります。 + # 指定したときだけこの機能が有効になります。 + # modifier で許可された人だけ使えます。 + request: 反応チェック - # メッセージを追加するキーワードを指定します。 - # ここで指定したキーワードを発言すると、新しいメッセージを追加します。 - # 実際の追加方法は「 <追加するメッセージ>」です。 - add: 反応追加 + # request に反応するときのフォーマットを指定します。 + # #(key) がキーワード、 #(message) が発言に置換されます。 + # request を指定したときのみ必要。 + reply-format: 「#(key)」という発言に「#(message)」と反応します。 - # メッセージを削除するキーワードを指定します。 - # 実際の削除方法は「 <削除するキーワード>」です。 - remove: 反応削除 + # request に反応する最大個数(反応マスクの数)を指定します。 + # (1つの反応マスクに対応するメッセージの数は制限されません。) + # あまり大きな値を指定すると、アタックが可能になったり、ログが流れて邪魔なので注意してください。 + # 通常の反応には関与しません。また、応答の行数ではありません。 + max-reply: 5 - # addとremoveを許可する人。省略された場合は「* *!*@*」と見做します。 + # 編集系コマンド, add とremove と request を許可する人。 + # 省略された場合は「* *!*@*」(全員許可)と見做します。 modifier: * *!*@* # 正規表現拡張を許可するか。省略された場合は禁止します。 diff -urN tiarra-20080510/module/CTCP/DCC/RewriteAddress.pm tiarra-20090206/module/CTCP/DCC/RewriteAddress.pm --- tiarra-20080510/module/CTCP/DCC/RewriteAddress.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/CTCP/DCC/RewriteAddress.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: RewriteAddress.pm 11164 2008-05-05 08:59:02Z topia $ +# $Id: RewriteAddress.pm 15771 2008-07-13 23:55:21Z drry $ # ----------------------------------------------------------------------------- # Rewrite ip address of CTCP DCC issued by client # ----------------------------------------------------------------------------- @@ -100,26 +100,27 @@ resolver => sub{ my ($this, $actions, $sender, $conf, $msg, $addr, $port) = @_; - my $regex = "".$conf->regex; + my $regex = "".$conf->regex; # regex to string + my $callback = sub { + my $resp = shift; + $actions->{step}->( + sub { + return undef unless ref($resp); + if ($resp->{Content} !~ /$regex/) { + $this->_runloop->notify_warn( + __PACKAGE__." http: regex not match: $regex"); + #::printmsg("http: content: $resp->{Content}"); + return undef; + } + $actions->{callback}->($1, $port); + 1; + }); + }; Tools::HTTPClient->new( Method => 'GET', Url => $conf->url, Debug => 1, - )->start( - sub { - my $resp = shift; - $actions->{step}->( - sub { - return undef unless ref($resp); - if ($resp->{Content} !~ /$regex/) { - ::printmsg("http: regex: $regex"); - ::printmsg("http: content: $resp->{Content}"); - return undef; - } - $actions->{callback}->($1, $port); - 1; - }); - }); + )->start($callback); 1; }, }, @@ -168,9 +169,11 @@ my $ret = eval { shift->(@_) }; if (!defined $ret) { if ($@) { - ::printmsg("$resolver: error occurred: $@"); + $this->_runloop->notify_warn( + __PACKAGE__." $resolver: error occurred: $@"); } - ::printmsg("$resolver: cannot resolved. try next method."); + $this->_runloop->notify_warn( + __PACKAGE__." $resolver: cannot resolved. try next method."); $next->(); } else { $ret; @@ -195,17 +198,20 @@ my $resolved = shift; my $ret = eval { if ($resolved->answer_status ne $resolved->ANSWER_OK) { - ::printmsg("resolver: $type/$data: return not OK"); - ::printmsg("resolver: ". $resolved->answer_data); - return undef; # next method + $this->_runloop->notify_warn( + __PACKAGE__." resolver: $type/$data: return not OK"); + undef; # next method + } else { + $callback->(@args, $resolved, @_); } - $callback->(@args, $resolved, @_); }; if (!defined $ret) { if ($@) { - ::printmsg("$resolver: error occurred: $@"); + $this->_runloop->notify_warn( + __PACKAGE__." $resolver: error occurred: $@"); } - ::printmsg("$resolver: cannot resolved. try next method."); + $this->_runloop->notify_warn( + __PACKAGE__." $resolver: cannot resolved. try next method."); $next->(); } else { $ret; @@ -223,7 +229,8 @@ $next = sub { if (!@resolvers) { ## FIXME: on cannot resolve - ::printmsg(__PACKAGE__."/rewrite_dcc: cannot resolve address at all"); + $this->_runloop->notify_warn( + __PACKAGE__." cannot resolve address at all"); $callback->(); } $resolver = shift(@resolvers); @@ -271,7 +278,7 @@ default: off section: important -# CTCP DCC に指定されているアドレスを、 tiarra で取得したものに +# CTCP DCC に指定されているアドレスを、 Tiarra で取得したものに # 書き換えます。(EXPERIMENTAL) # # IPv4 のみサポートしています。 @@ -298,7 +305,7 @@ client-socket { # クライアントソケットのリモートアドレスを取ります。 - # client [this address]<-> tiarra <-> server + # client [this address]<-> Tiarra <-> server } dns { @@ -318,16 +325,16 @@ # リゾルバの選び方 # -# * tiarra を動作させているサーバとインターネットの間にルータ等があり、 +# * Tiarra を動作させているサーバとインターネットの間にルータ等があり、 # グローバルアドレスがない場合 # *-socket は役に立ちません。 http を利用してください。 # 適当な DDNS を持っていればdns も良いでしょう。 # -# * tiarra がレンタルサーバなどLAN上にないサーバで動作している場合 +# * Tiarra がレンタルサーバなどLAN上にないサーバで動作している場合 # server-socket, http は役に立ちません。 # client-socket がお勧めです。 # -# * tiarra がLAN上にあり、グローバルアドレスのついているホストで +# * Tiarra がLAN上にあり、グローバルアドレスのついているホストで # 動作している場合 # client-socket は役に立ちません。 # server-socket がお勧めです。 diff -urN tiarra-20080510/module/Channel/Ignore.pm tiarra-20090206/module/Channel/Ignore.pm --- tiarra-20080510/module/Channel/Ignore.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/Channel/Ignore.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Ignore.pm 3004 2007-12-10 12:45:39Z topia $ +# $Id: Ignore.pm 15771 2008-07-13 23:55:21Z drry $ # ----------------------------------------------------------------------------- # copyright (C) 2005 Topia . all rights reserved. package Channel::Ignore; @@ -149,7 +149,7 @@ # 注意点 # - この機能はまだ実装途中です。いろいろな不具合があるかもしれません。むしろきっとあります。 # - サーバがわとの通信に割り込みますのでログにもとられません。 -# - この機能を使っている tiarra より上流に multi-server-mode な tiarra を置かないでください。 +# - この機能を使っている Tiarra より上流に multi-server-mode な Tiarra を置かないでください。 # チャンネルの定義。 # また、privの場合は「priv@ネットワーク名」という文字列をチャンネル名の代わりとしてマッチングを行なう。 diff -urN tiarra-20080510/module/Channel/Mode/Oper/Grant.pm tiarra-20090206/module/Channel/Mode/Oper/Grant.pm --- tiarra-20080510/module/Channel/Mode/Oper/Grant.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/Channel/Mode/Oper/Grant.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Grant.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Grant.pm 15318 2008-07-06 15:34:51Z hio $ # ----------------------------------------------------------------------------- package Channel::Mode::Oper::Grant; use strict; @@ -8,6 +8,7 @@ use Mask; use Multicast; use Timer; +use base qw(Tiarra::Mixin::NewIRCMessage); sub new { my $class = shift; diff -urN tiarra-20080510/module/Client/List.pm tiarra-20090206/module/Client/List.pm --- tiarra-20080510/module/Client/List.pm 1970-01-01 09:00:00.000000000 +0900 +++ tiarra-20090206/module/Client/List.pm 2009-02-09 22:30:10.000000000 +0900 @@ -0,0 +1,135 @@ +# ----------------------------------------------------------------------------- +# $Id: List.pm 12473 2008-05-26 14:50:36Z hio $ +# ----------------------------------------------------------------------------- +# Client::List +# ----------------------------------------------------------------------------- +package Client::List; +use strict; +use warnings; +use base qw(Module); +use base qw(Tiarra::IRC::NewMessageMixin); +use Scalar::Util qw(weaken); + +our $DEFAULT_COMMAND = 'clientlist'; + +sub new +{ + my $pkg = shift; + my $this = $pkg->SUPER::new(@_); + + $this; +} + +sub destruct +{ + my $this = shift; +} + +sub message_arrived +{ + my ($this, $msg, $sender) = @_; + + my @result = $msg; + eval { + $this->_message_arrived(\@result, $sender); + }; + if( $@ ) + { + $this->_runloop->notify_error("$@"); + } + + @result = grep{ $_ } @result; + @result; +} + +sub _message_arrived +{ + my ($this, $result, $sender) = @_; + my $msg = $result->[0]; + + if( !$sender->isa("IrcIO::Client") ) + { + return; + } + + my $cmd = uc($this->config->command || $DEFAULT_COMMAND); + + if( $msg->command eq $cmd ) + { + $this->_reply_client_list($msg, $sender); + @$result = (); + } +} + +sub _reply_client_list +{ + my $this = shift; + my $msg = shift; + my $sender = shift; + + my $tmpl = __PACKAGE__->construct_irc_message( + Command => 'NOTICE', + Params => [$this->_runloop->current_nick, undef], + ); + my $reply = sub{ + my $res = $tmpl->clone(); + $res->param(1, "*** ".$_[0]); + $sender->send_message($res); + }; + + my @list; + my $runloop = $this->_runloop; + if( ref($runloop->{clients}) eq 'ARRAY' ) + { + @list = @{$runloop->{clients}}; + } + my $nr_clients = @list; + + $reply->( "$nr_clients ".($nr_clients==1?"client":"clients") ); + my $i = 0; + foreach my $client (@list) + { + ++$i; + my $cl; + if( !$client ) + { + $cl = '-'; + }else + { + my $sock = $client->isa("Tiarra::Socket") && $client->sock; + if( $sock ) + { + my $addr = $sock->peerhost; + my $port = $sock->peerport; + $cl = "$addr:$port"; + }else + { + $cl = "$client"; + } + } + $reply->( "[$i] $cl"); + } +} + +1; + +=begin tiarra-doc + +info: Clientの一覧を取得. +default: off +#section: important + +# 接続しているクライアントを一覧. +# /clientlist を投げると, その時に接続しているクライアントの一覧を返す. +# 出力例: +# clientlist +# *** 1 client +# *** [1] 127.0.0.1:23695 + + +# 一覧を返すトリガーとするコマンド. +-command: clientlist + +=end tiarra-doc + +=cut diff -urN tiarra-20080510/module/Log/Channel.pm tiarra-20090206/module/Log/Channel.pm --- tiarra-20080510/module/Log/Channel.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/Log/Channel.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Channel.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Channel.pm 12214 2008-05-22 11:34:11Z topia $ # ----------------------------------------------------------------------------- package Log::Channel; use strict; @@ -12,10 +12,14 @@ use Tools::DateConvert; use Log::Logger; use Log::Writer; +use Module::Use qw(Tools::HashTools); +use Tools::HashTools; use ControlPort; use Mask; use Multicast; +our $DEFAULT_FILENAME_ENCODING = $^O eq 'MSWin32' ? 'sjis' : 'utf8'; + sub new { my $class = shift; my $this = $class->SUPER::new(@_); @@ -106,10 +110,14 @@ $message; } -*S_PRIVMSG = \&PRIVMSG_or_NOTICE; -*S_NOTICE = \&PRIVMSG_or_NOTICE; -*C_PRIVMSG = \&PRIVMSG_or_NOTICE; -*C_NOTICE = \&PRIVMSG_or_NOTICE; +{ + no warnings qw(once); + *S_PRIVMSG = \&PRIVMSG_or_NOTICE; + *S_NOTICE = \&PRIVMSG_or_NOTICE; + *C_PRIVMSG = \&PRIVMSG_or_NOTICE; + *C_NOTICE = \&PRIVMSG_or_NOTICE; +} + sub PRIVMSG_or_NOTICE { my ($this,$msg,$sender) = @_; my $target = Multicast::detatch($msg->param(0)); @@ -179,7 +187,15 @@ if (Mask::match($ch->[1],$channel)) { # マッチした。 my $fname_format = $this->config->filename || '%Y.%m.%d.txt'; - my $fpath_format = $ch->[0]."/$fname_format"; + # あまり好ましくなさそうな文字はあらかじめエスケープ. + my $chan_filename = $channel; + $chan_filename =~ s/![0-9A-Z]{5}/!/; + $chan_filename =~ s{([^-\w@#%!+&.\x80-\xff])}{ + sprintf('=%02x', unpack("C", $1)); + }ge; + my $chan_dir = Tools::HashTools::replace_recursive( + $ch->[0], [{channel => $chan_filename}]); + my $fpath_format = "$chan_dir/$fname_format"; $this->{matching_cache}->{$channel} = $fpath_format; return $fpath_format; @@ -210,6 +226,14 @@ Tools::DateConvert::replace($abstract_fpath); } }; + my $filename_encoding = $this->config->filename_encoding || $DEFAULT_FILENAME_ENCODING; + if( $filename_encoding ne 'ascii' ) + { + $concrete_fpath = Tiarra::Encoding->new($concrete_fpath)->conv($filename_encoding); + }else + { + $concrete_fpath =~ s/([^ -~])/sprintf('=%02x', unpack("C", $1))/ge; + } my $header = Tools::DateConvert::replace( $this->config->header || '%H:%M' ); @@ -383,6 +407,16 @@ # channel: others * # この例では、#IRC談話室@ircnetのログはIRCDanwasitu/%Y.%m.%d.txtに、 # それ以外(privも含む)のログはothers/%Y.%m.%d.txtに保存される。 +# #(channel) はチャンネル名に展開される。 +# (古いバージョンだと展開されずにそのままディレクトリ名になってしまいます。) channel: priv priv -channel: others * +channel: #(channel) * +-channel: others * + +# ファイル名のエンコーディング. +# 指定可能な値は, utf8, sjis, euc, jis, ascii. +# ascii は実際には utf8 と同等で8bit部分が全てquoted-printableされる. +# デフォルトはWindowsではsjis, それ以外では utf8. +-filename-encoding: utf8 + =cut diff -urN tiarra-20080510/module/Skelton.pm tiarra-20090206/module/Skelton.pm --- tiarra-20080510/module/Skelton.pm 2008-05-11 00:25:26.000000000 +0900 +++ tiarra-20090206/module/Skelton.pm 2009-02-09 22:30:11.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Skelton.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Skelton.pm 29425 2009-02-02 06:17:32Z drry $ # ----------------------------------------------------------------------------- # モジュールのスケルトン。 # ----------------------------------------------------------------------------- @@ -27,6 +27,22 @@ # 引数は無し。 } +sub config_reload { + my ($this, $old_config) = @_; + # モジュールの設定が変更された時に呼ばれる。 + # 新しい config は $this->config で取得できます。 + + # 定義されていない場合は destruct と new をそれぞれ呼ぶ。 + eval { + $this->destruct; + }; if ($@) { + $this->_runloop->notify_error( + "Couldn't destruct module on reload config of " . ref($this) + . ".\n$@"); + } + return ref($this)->new($this->_runloop); +} + sub message_arrived { my ($this,$msg,$sender) = @_; # サーバーまたはクライアントからメッセージが来た時に呼ばれる。 @@ -149,7 +165,7 @@ =begin tiarra-doc -info: Skelton for tiarra-module. +info: Skeleton for tiarra-module. default: off #section: important diff -urN tiarra-20080510/module/System/LivePatch.pm tiarra-20090206/module/System/LivePatch.pm --- tiarra-20080510/module/System/LivePatch.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/System/LivePatch.pm 2009-02-09 22:30:10.000000000 +0900 @@ -5,7 +5,7 @@ # # Copyright 2008 YAMASHINA Hio # ----------------------------------------------------------------------------- -# $Id: LivePatch.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: LivePatch.pm 12295 2008-05-24 15:27:51Z hio $ # ----------------------------------------------------------------------------- # main/モジュールに対する動的パッチ. # ----------------------------------------------------------------------------- @@ -15,7 +15,7 @@ use base qw(Module); use BulletinBoard; -our $VERSION = '0.02'; +our $VERSION = '0.03'; our $DEBUG = 0; @@ -86,7 +86,7 @@ $this->_apply(-check); }elsif( $param0 eq 'apply' ) { - if( @$params==1 ) + if( @$params==0 ) { $this->_apply(-apply => -all); }else @@ -129,8 +129,8 @@ $runloop->notify_msg(__PACKAGE__.", $nr_history ".($nr_history==1?'entry':'entries')." in history"); my $base = shift @$params; - my $limit = 3; - if( !$base || $base !~ /^0*\d+\z/ ) + my $limit = 5; + if( !defined($base) || $base !~ /^0*\d+\z/ ) { $base = @$history - $limit + 1; } @@ -224,7 +224,7 @@ pkg => $patch->{pkg}, subname => $patch->{subname}, installed => undef, - install => (reverse sort keys %{$patch->{revs}})[0], + install => (reverse sort {$a<=>$b} keys %{$patch->{revs}})[0], }); } } @@ -272,10 +272,25 @@ my $patch = $patches_hashref->{$pkg}{$subname}; $patch or $runloop->notify_msg(" no such patch, [$pkg] [$subname]."), next; - my @revs = reverse sort keys %{$patch->{revs}}; + my @revs = reverse sort {$a<=>$b} keys %{$patch->{revs}}; $runloop->notify_msg(" pkg => $pkg"); $runloop->notify_msg(" sub => $subname"); - $runloop->notify_msg(" revs => ".join(", ", @revs)); + $runloop->notify_msg(" revs => ".join(", ", map{"r$_"} @revs)); + my $filter; + if( $patch->{constants} ) + { + $runloop->notify_msg(" constants => "); + foreach my $key (sort keys %{$patch->{constants}}) + { + $runloop->notify_msg(" $key => $patch->{constants}{$key}"); + } + my $keys = join('|', map{quotemeta($_)} keys %{$patch->{constants}}); + my $re = qr/\b(?:$keys)\b/; + $filter = sub{ + my $ref = shift; + $$ref =~ s/($re)/$patch->{constants}{$1}/g; + }; + } my $cursub = $pkg->can($patch->{subname}); if( !defined(&$cursub) ) { @@ -284,6 +299,7 @@ next; } my $curtext = $deparse->coderef2text($cursub); + $filter and $filter->(\$curtext, undef); my $curmd5 = $digest->($curtext); $runloop->notify_msg(" current => $curmd5"); $DEBUG and print "<>\n$curtext\n"; @@ -299,6 +315,7 @@ next; } my $dump = $deparse->coderef2text($sub); + $filter and $filter->(\$dump, $rev); $DEBUG and print "<<$rev>>\n$dump\n"; my $md5 = $digest->($dump); $lastest ||= {rev=>$rev,'sub'=>$sub,md5=>$md5}; @@ -418,7 +435,7 @@ pkg => 'ModuleManager', subname => 'reload_modules_if_modified', revs => { - r3004 => <<'EOF', + 3004 => <<'EOF', # package ModuleManager. # sub _reload_modules_if_modified_r8809. sub { @@ -539,7 +556,7 @@ } } EOF - r8809 => <<'EOF' + 8809 => <<'EOF' # package ModuleManager. # sub _reload_modules_if_modified_r8809. sub { @@ -677,7 +694,7 @@ pkg => 'LinedINETSocket', subname => 'connect', revs => { - r3004 => <<'EOF', + 3004 => <<'EOF', # package LinedINETSocket # sub _connect_r3004 no strict 'refs'; # by SelfLoader. @@ -694,7 +711,7 @@ $this->attach($sock); } EOF - r8930 => <<'EOF', + 8930 => <<'EOF', # package LinedINETSocket # sub _connect_r8930 no strict 'refs'; # by SelfLoader. @@ -719,6 +736,136 @@ EOF }, }, + { + pkg => 'Configuration::Block', + subname => 'equals', + constants => { + BLOCK_NAME => Configuration::Block::BLOCK_NAME(), + TABLE => Configuration::Block::TABLE(), + }, + revs => { + 3004 => <<'EOF', +sub { + # 二つのConfiguration::Blockが完全に等価なら1を返す。 + my ($this,$that) = @_; + # ブロック名 + if ($this->[BLOCK_NAME] ne $that->[BLOCK_NAME]) { + return undef; + } + # キーの数 + my @this_keys = keys %{$this->[TABLE]}; + my @that_keys = keys %{$that->[TABLE]}; + if (@this_keys != @that_keys) { + return undef; + } + # 各要素 + my $size = @this_keys; + for (my $i = 0; $i < $size; $i++) { + # キー + if ($this_keys[$i] ne $that_keys[$i]) { + return undef; + } + # 値の型 + my $this_value = $this->[TABLE]->{$this_keys[$i]}; + my $that_value = $that->[TABLE]->{$that_keys[$i]}; + if (ref($this_value) ne ref($that_value)) { + return undef; + } + # 値 + if (ref($this_value) eq 'ARRAY') { + # 配列なので要素数と全要素を比較。 + if (@$this_value != @$that_value) { + return undef; + } + my $valsize = @$this_value; + for (my $j = 0; $j < $valsize; $j++) { + if ($this_value->[$j] ne $that_value->[$j]) { + return undef; + } + } + } + elsif (UNIVERSAL::isa($this_value,'Configuration::Block')) { + # ブロックなので再帰的に比較。 + return $this_value->equals($that_value); + } + else { + if ($this_value ne $that_value) { + return undef; + } + } + } + return 1; +} +EOF + 11868 => <<'EOF', +sub { + # 二つのConfiguration::Blockが完全に等価なら1を返す。 + my ($this,$that) = @_; + # ブロック名 + if ($this->[BLOCK_NAME] ne $that->[BLOCK_NAME]) { + return undef; + } + # キーの数 + my @this_keys = keys %{$this->[TABLE]}; + my @that_keys = keys %{$that->[TABLE]}; + if (@this_keys != @that_keys) { + return undef; + } + # 各要素 + my $size = @this_keys; + for (my $i = 0; $i < $size; $i++) { + # キー + if ($this_keys[$i] ne $that_keys[$i]) { + return undef; + } + # 値の型 + my $this_value = $this->[TABLE]->{$this_keys[$i]}; + my $that_value = $that->[TABLE]->{$that_keys[$i]}; + if (ref($this_value) ne ref($that_value)) { + return undef; + } + # 値 + if (ref($this_value) eq 'ARRAY') { + # 配列なので要素数と全要素を比較。 + if (@$this_value != @$that_value) { + return undef; + } + my $valsize = @$this_value; + for (my $j = 0; $j < $valsize; $j++) { + if ($this_value->[$j] ne $that_value->[$j]) { + return undef; + } + } + } + elsif (UNIVERSAL::isa($this_value,'Configuration::Block')) { + # ブロックなので再帰的に比較。 + my $ret = $this_value->equals($that_value); + if (!$ret) { + return undef; + } + } + else { + if ($this_value ne $that_value) { + return undef; + } + } + } + return 1; +} +EOF + }, + }, +# { +# pkg => 'Tiarra::XXX', +# subname => 'xxx', +# #filter => sub {}, +# revs => { +# 3004 => <<'EOF', +#EOF +# 9999 => <<'EOF', +#EOF +# }, +# }, ]; } @@ -750,6 +897,8 @@ # 対応している箇所. # ModuleManager / reload_modules_if_modified / r3004 => r8809 +# LinedINETSocket / connect / r3004 => r8930 +# Configuration::Block / equals / r3004 => r11868 # /livepatch check で確認. # /livepatch apply で適用. diff -urN tiarra-20080510/module/System/NotifyIcon/Win32.pm tiarra-20090206/module/System/NotifyIcon/Win32.pm --- tiarra-20080510/module/System/NotifyIcon/Win32.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/System/NotifyIcon/Win32.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Win32.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Win32.pm 11470 2008-05-12 15:06:02Z topia $ # ----------------------------------------------------------------------------- # use shell notify-icon # based on win32::TaskTray.pm (超ベータVer) by Noboruhi @@ -97,10 +97,14 @@ substr($tooltip,$tooltip_length - 1) = ''; } if (!$can_use_win32api || $this->{notifyicondata_version} <= 1) { - # This is internal API! - Win32::GUI::NotifyIcon::Modify($this->{notify_icon}->{-handle}, - -id => $this->{notify_icon}->{-id}, - -tip => $tooltip); + eval { + $this->{notify_icon}->Change(-tip => $tooltip); + }; if ($@) { + # This is internal API! + Win32::GUI::NotifyIcon::Modify($this->{notify_icon}->{-handle}, + -id => $this->{notify_icon}->{-id}, + -tip => $tooltip); + } } else { my ($struct,$ret); diff -urN tiarra-20080510/module/System/WebClient.pm tiarra-20090206/module/System/WebClient.pm --- tiarra-20080510/module/System/WebClient.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/System/WebClient.pm 2009-02-09 22:30:10.000000000 +0900 @@ -5,7 +5,7 @@ # # Copyright 2008 YAMASHINA Hio # ----------------------------------------------------------------------------- -# $Id: WebClient.pm 11017 2008-05-03 06:45:50Z hio $ +# $Id: WebClient.pm 20009 2008-09-27 02:20:10Z hio $ # ----------------------------------------------------------------------------- package System::WebClient; use strict; @@ -18,15 +18,58 @@ use Auto::Utils; use BulletinBoard; use Module::Use qw(Tools::HTTPServer Tools::HTTPParser Log::Logger Auto::Utils); +use Unicode::Japanese; use IO::Socket::INET; use Scalar::Util qw(weaken); +our $VERSION = '0.06'; + our $DEBUG = 0; our $DEFAULT_MAX_LINES = 100; -our $DEFAULT_NAME = '???'; our $DEFAULT_SHOW_LINES = 20; +our $DEFAULT_SITE_NAME = "Tiarra::WebClient"; +our $DEFAULT_SESSION_EXPIRE = 7 * 24 * 60*60; + +=begin COMMENT + +System::WebClient - ブラウザ上でログを見たり発言したりできるようにする Tiarra モジュール. + + / + #==> [POST/_post_list] ENTER. + #==> [GET/_gen_list] /log/*/* を一覧. + /log/// + #==> [POST/_post_log] 発言. + #==> [GET/_gen_log] ログの閲覧. + #==> ?r=XXX ==> ここまでは見たからこれの次から表示. + #==> ?x=XXX ==> 最新を表示するけれど,ここまではみたから表示しない. + /log///info + #==> [POST/_post_chan_info] TOPIC/JOIN/PART/DELETE. + #==> [GET/_gen_chan_info] チャンネル情報表示. + /config + #==> [POST/_post_config] NAME + #==> [GET/_gen_config] shared時の名前設定 + /style/style.css + #==> 空のCSSファイル. + <それ以外> + #==> 404. + +session 情報: + $req->{session}{seen} -- 未読管理. + $req->{session}{seen}{$netname}{$ch_short} = $recent内のオブジェクト. + $req->{session}{name} -- shared用の名前. + +(*) 存在するけれど閲覧許可のないページであっても, + 403 (Forbidden) ではなく 404 (Not Found) を返す. +(*) ENTER: チャンネル情報を作成. この情報はそこにチャンネルがある(あった)ということを + 保持していて, PART後も残るため過去ログが閲覧できる. +(*) DELETE: 保持しているチャンネル情報を削除. + そのチャンネルの情報がもういらないのなら, 存在していたことを削除できる. + +=end COMMENT + +=cut 1; @@ -56,8 +99,10 @@ ); # トップ何行かのキャッシュ. - $this->{bbs_val} = undef; - $this->{cache} = undef; + $this->{bbs_val} = undef; + $this->{cache} = undef; + $this->{max_lines} = undef; + $this->{session_master} = undef; $this->_load_cache(); my $config = $this->config; @@ -102,7 +147,7 @@ $this->{logger} = undef; $this->{bbs_val}{unloaded_at} = time; - $this->_debug(__PACKAGE__."->destruct(), done."); + $DEBUG and $this->_debug(__PACKAGE__."->destruct(), done."); } # ----------------------------------------------------------------------------- @@ -123,12 +168,15 @@ inited_at => time, unloaded_at => 0, cache => {}, + session => {}, }; BulletinBoard->shared->set($BBS_KEY, $BBS_VAL); } + $BBS_VAL->{session} ||= {}; $this->{bbs_val} = $BBS_VAL; $this->{cache} = $BBS_VAL->{cache}; + $this->{session_master} = $BBS_VAL->{session}; $runloop->notify_msg(__PACKAGE__."#_load_cache, bbs[$BBS_KEY].inited_at ".localtime($BBS_VAL->{inited_at})); $runloop->notify_msg(__PACKAGE__."#_load_cache, bbs[$BBS_KEY].unloaded_at ".($BBS_VAL->{unloaded_at}?localtime($BBS_VAL->{unloaded_at}):'-')); @@ -144,8 +192,21 @@ { my $channame = $channel->name; $this->{cache}{$netname}{$channame} ||= $this->_new_cache_entry($netname, $channame); + my $cache = $this->{cache}{$netname}{$channame}; + + # old version does not have these entries. + $this->{cache}{$netname}{$channame}{netname} ||= $netname; + $this->{cache}{$netname}{$channame}{ch_short} ||= $channame; } } + + my $limit = $this->config->max_lines || 0; + $limit =~ s/^0+//; + if( !$limit || $limit !~ /^[1-9]\d*\z/ ) + { + $limit = $DEFAULT_MAX_LINES; + } + $this->{max_lines}{''} = $limit; } @@ -156,6 +217,8 @@ my $ch_short = shift; +{ recent => [], + netname => $netname, + ch_short => $ch_short, }; } @@ -334,12 +397,7 @@ $info->{lineno} = $prev && $prev->{ymd} eq $info->{ymd} ? $prev->{lineno} + 1 : 1; push(@$recent, $info); - my $limit = $this->config->max_lines || 0; - $limit =~ s/^0+//; - if( !$limit || $limit !~ /^[1-9]\d*\z/ ) - { - $limit = $DEFAULT_MAX_LINES; - } + my $limit = $this->{max_lines}{''}; @$recent > $limit and @$recent = @$recent[-$limit..-1]; } @@ -412,10 +470,13 @@ client => $cli, peer => $peer, conflist => $conflist, - need_auth => 1, + authid => undef, # login token (basic, etc.) + authtoken => undef, # session token (sid). ua_type => undef, cgi_hash => undef, # generated on demand. - req_param => undef, # generated on demand. + req_param => undef, # config params, generated on demand. + session => undef, + path => undef, # path under $config->{path}. }; if( my $ua = $req->{Header}{'User-Agent'} ) { @@ -433,7 +494,7 @@ if( $req->{Method} !~ /^(GET|POST|HEAD)\z/ ) { - $this->_debug("$peer: method not allowed: $req->{Method}"); + $DEBUG and $this->_debug("$peer: method not allowed: $req->{Method}"); # 405 Method Not Allowed $this->_response($req, 405); return; @@ -441,45 +502,54 @@ if( !@$conflist ) { - $this->_debug("$peer: Forbidden by no conf"); + $DEBUG and $this->_debug("$peer: Forbidden by no conf"); # 403 Forbidden. $this->_response($req, 403); return; } - my ($user, $pass); - if( my $line = $req->{Header}{Authorization} ) - { - my ($type, $val) = split(' ', $line, 2); - if( $type eq 'Basic' ) + $DEBUG and $this->_debug("$peer: check auth ..."); + my $accepted_list = $this->auth($conflist, $req); + #$DEBUG and $this->_debug("$peer: auth=".Dumper($accepted_list));use Data::Dumper; + my $authid_list; + my $authtoken_list; + if( @$accepted_list ) + { + $DEBUG and $this->_debug("$peer: has auth"); + # update @$conflist. + @$conflist = map{ $_->{conf} } @$accepted_list; + + # extract authtoken list. + $authid_list = []; + $authtoken_list = []; + foreach my $auth (@$accepted_list) { - require MIME::Base64; - my $dec = MIME::Base64::decode($val); - ($user,$pass) = split(/:/, $dec, 2); + if( !grep { $_ eq $auth->{token} } @$authtoken_list ) + { + # unique. + push(@$authtoken_list, $auth); + } + if( !grep { $_ eq $auth->{id} } @$authid_list ) + { + # unique. + push(@$authid_list, $auth); + } } - } - my $need_auth = 1; - if( !$user ) - { - @$conflist = grep{ !$_->{auth} } @$conflist; }else { - @$conflist = grep{ - my $auth = $_->{auth}; - my ($auth_user,$auth_pass) = split(' ', $auth); - $auth_pass = _decode_value($auth_pass); - $auth_user eq $user && $auth_pass eq $pass; - } @$conflist; + $DEBUG and $this->_debug("$peer: no auth"); + @$conflist = grep{ !$_->{auth} } @$conflist; + $DEBUG and $this->_debug("$peer: has guest entry ".(@$conflist?"yes":"no")); } - $need_auth = @$conflist==0; + my $need_auth = @$conflist == 0; if( $req->{Path} =~ /\?auth(?:=|[&;]|$)/ ) { $need_auth = 1; } if( $need_auth ) { - $this->_debug("$peer: response: Authenticate Required"); + $DEBUG and $this->_debug("$peer: response: Authenticate Required"); my $realm = 'Authenticate Required'; # 401 Unauthorized my $res = { @@ -492,23 +562,535 @@ return; } - $this->_debug("$peer: accept."); - $this->_dispatch($req); + $req->{authid} = ($authid_list && @$authid_list) ? $authid_list ->[0]->{id} : undef; + $req->{authtoken} = ($authtoken_list && @$authtoken_list) ? $authtoken_list->[0]->{atoken} : undef; + if( !$req->{authtoken} ) + { + $DEBUG and $this->_debug("$peer: no authtoken, check cookie"); + CHECK_COOKIE:{ + my $cookies = $req->{Header}{Cookie} || ''; + my @cookies = split(/\s*[;,]\s*/, $cookies); + my $ck = shift @cookies || ''; + my ($key, $val) = split(/=/, $ck); + $key && $val or last CHECK_COOKIE; + $val =~ s/%([0-9a-f]{2})/pack("H*",$1)/ge; + $DEBUG and $this->_debug("$peer: cookie: [$key]=[$val]"); + if( $val !~ /^sid:(\d+):(\d+):(\d+)(?::|\z)/ ) + { + last CHECK_COOKIE; + } + my ($seed, $seq, $check) = ($1, $2, $3); + my $sid = "sid:$seed:$seq:$check"; + $req->{authtoken} = $sid; + $DEBUG and $this->_debug("$peer: $sid"); + } + } + + my $path = $req->{Path}; + if( $path !~ s{\Q$this->{path}}{/} ) + { + $this->_debug("$peer: outside request, [$path] is not contened in [$this->{path}]"); + $this->_response($req, 404); + return; + } + $path =~ s/\?.*//; + $req->{path} = $path; + + my $mode = $this->_get_req_param($req, 'mode'); + if( $mode eq 'owner' ) + { + $req->{authtoken} ||= "owner:*"; + } + $this->_update_session($req); + + my $need_name; + if( $mode eq 'owner' ) + { + $need_name = undef; + }elsif( $req->{session}{name} ) + { + $need_name = undef; + }elsif( $path eq '/style/style.css' ) + { + $need_name = undef; + }else + { + $need_name = 1; + } + + if( $need_name ) + { + $this->_debug("$peer: login required (no name). [vpath=$req->{path}]"); + eval{ + $this->_login($req); + }; + }else + { + my $aid = $req->{authid} || '-'; + my $sid = $req->{authtoken} || '-'; + my $name = $req->{session}{name} || '-'; + $this->_debug("$peer: accept: auth=$aid, name=$name, sid=$sid"); + eval { + $this->_dispatch($req); + }; + } + $@ and $this->_debug("$peer: error: $@"); + $DEBUG and $this->_debug("$peer: done"); } +# ----------------------------------------------------------------------------- +# my $sess = $this->_new_session(). +# Set-Cookie も設定される. +# +sub _new_session +{ + my $this = shift; + my $req = shift; -sub _dispatch + our $seed ||= int(rand(0xFFFF_FFFF)); + our $seq ||= 0; + $seq ++; + my $rnd = int(rand(0xFFFF_FFFF)); + my $sid = "sid:$seed:$seq:$rnd"; + $DEBUG and $this->_debug("$req->{peer}: _new_session: $sid"); + + $req->{authtoken} = $sid; + my $sess = $this->_update_session($req); + $req->{cookies}{SID} = $sess->{_sid}; + $sess; +} + +# ----------------------------------------------------------------------------- +# my $sess = $this->_delete_session($req). +# 削除用の Set-Cookie も設定される. +# +sub _delete_session { my $this = shift; my $req = shift; + my $sess = $req->{session} || {}; + my $sid = $sess->{_sid} || ''; + my $deleted = delete $this->{session_master}{$sid}; + if( $deleted ) + { + $deleted->{_deleted} = 1; + } + if( $sid ) + { + $req->{cookies}{SID} = undef; + } + $deleted; +} - my $path = $req->{Path}; - if( $path !~ s{\Q$this->{path}}{/} ) +# ----------------------------------------------------------------------------- +# $this->_update_session($req); +# 指定のsessionを取得. +# なかったら生成される. +# +sub _update_session +{ + my $this = shift; + my $req = shift; + + my $sid = $req->{authtoken}; + if( !$sid ) + { + # check weak session. + my $cgi = $this->_get_cgi_hash($req); + my $w_sid = $cgi->{SID}; + my $sess = $w_sid && $this->{session_master}{$w_sid}; + if( $sess && $sess->{_weak} ) + { + # accept weak session. + $sid = $w_sid; + $req->{authtoken} = $w_sid; + }else + { + $DEBUG and $this->_debug("$req->{peer}: _update_session: no sid"); + $req->{session} = {}; + return; + } + } + + my $sess = ($this->{session_master}{$sid} ||= {}); + my $now = time; + if( $sess->{_updated_at} ) + { + if( $sess->{_updated_at} + $DEFAULT_SESSION_EXPIRE < $now ) + { + # clean up. + $sess = {}; + $this->{session_master}{$sid} = $sess; + } + } + + if( !$sess->{_sid} ) + { + $sess->{_sid} = $sid; + $sess->{_created_at} = $now; + $sess->{_expire} = $DEFAULT_SESSION_EXPIRE; + $sess->{_weak} = undef; + } + $sess->{_updated_at} = $now; + + # $sess->{seen} = \%seen; + # $sess->{name} = $name; + $DEBUG and $this->_debug("$req->{peer}: _get_session: $sess->{_sid}"); + #$DEBUG and $this->_debug("$req->{peer}: _get_session: ".Dumper($sess));use Data::Dumper; + + $req->{session} = $sess; + + $sess; +} + +sub auth +{ + my $this = shift; + my $conflist = shift; + my $req = shift; + my @accepts; + + # 認証関数. + # $val = $sub->($this, \@param, $req). + # \%hashref #==> accept. + # undef #==> ignore. + # '' #==> deny. + our $AUTH ||= { + ':basic' => \&_auth_basic, + ':softbank' => \&_auth_softbank, + ':au' => \&_auth_au, + }; + + foreach my $conf (@$conflist) + { + $DEBUG and $this->_debug("$req->{peer}: check auth for $conf->{name}"); + my $authlist = $conf->{auth} or next; + foreach my $auth (@$authlist) + { + my @param = split(' ', $auth || ''); + if( !@param ) + { + $DEBUG and ::printmsg("$req->{peer}: - skip: empty value"); + next; + } + $param[0] =~ /^:/ or unshift(@param, ':basic'); + my $auth_sub = $AUTH->{$param[0]}; + if( !$auth_sub ) + { + $DEBUG and ::printmsg("$req->{peer}: - skip: unsupported: $param[0]"); + next; + } + my $val = $this->$auth_sub(\@param, $req); + if( $val ) + { + $DEBUG and $this->_debug("$req->{peer}: - $conf->{name} accepted ($param[0])"); + push(@accepts, { + atoken => $val->{atoken}, # auth token, string or undef. + id => $val->{id}, # auth id, string. not undef. + conf => $conf, + }); + }elsif( defined($val) ) + { + $DEBUG and $this->_debug("$req->{peer}: auth denied by $conf->{name}"); + return undef; + } + } + } + \@accepts; +} + +sub _auth_basic +{ + my $this = shift; + my $param = shift; + my $req = shift; + + my $line = $req->{Header}{Authorization}; + if( !$line ) + { + $DEBUG and ::printmsg("$req->{peer}: no Authorization: header"); + return; + } + + my ($type, $val) = split(' ', $line, 2); + if( $type ne 'Basic' ) + { + $DEBUG and ::printmsg("$req->{peer}: not Basic Authorization (got $type)"); + return; + } + + require MIME::Base64; + my $dec = MIME::Base64::decode($val); + my ($user,$pass) = split(/:/, $dec, 2); + + if( !_verify_value($param->[1], $user) ) + { + defined($user) or $user = ''; + $DEBUG and ::printmsg("$req->{peer}: $param->[0] user $param->[1] does not match with '$user' (user)"); + return; + } + if( !_verify_value($param->[2], $pass) ) + { + defined($pass) or $pass = ''; + $DEBUG and ::printmsg("$req->{peer}: $param->[0] pass $param->[2] does not match with '$pass' (pass)"); + return; + } + + # accept. + $DEBUG and ::printmsg("$req->{peer}: accept user $param->[0] pass $param->[2] with '$user' '$pass'"); + +{ + id => "basic:$user", + atoken => undef, + }; +} + +sub _auth_softbank +{ + my $this = shift; + my $param = shift; + my $req = shift; + + #TODO: carrier ip-addresses range. + + # UIDはhttp領域若しくはsecure.softbank.ne.jp経由. + # SNは端末の設定. + my $uid = $req->{Header}{'X-JPHONE-UID'}; + my $sn = do{ + my ($ua1) = split(' ', $req->{Header}{'User-Agent'} || ''); + my @ua = split('/', $ua1 || ''); + my $carrier = uc($ua[0] || ''); + my $sn = $carrier eq 'J-PHONE' ? $ua[3] + : $carrier eq 'VODAFONE' ? $ua[4] + : $carrier eq 'SOFTBANK' ? $ua[4] + : undef; + $sn; + }; + if( _verify_value($param->[1], $uid) ) + { + # accept. + my $id = "softbank:$uid"; + return +{ + id => $id, + atoken => $id, + }; + } + if( _verify_value($param->[1], $sn) ) + { + # accept. + my $id = "softbank:$sn"; + return +{ + id => $id, + atoken => $id, + }; + } + defined($uid) or $uid = ''; + defined($sn) or $sn = ''; + $DEBUG and ::printmsg("$req->{peer}: $param->[0] pass $param->[1] does not match with '$uid' (uid), '$sn' (sn)"); + return; +} + +sub _auth_au +{ + my $this = shift; + my $param = shift; + my $req = shift; + + #TODO: carrier ip-addresses range. + # http://www.au.kddi.com/ezfactory/tec/spec/ezsava_ip.html + my $subno = $req->{Header}{'X-UP-SUBNO'}; + if( !_verify_value($param->[1], $subno) ) + { + defined($subno) or $subno = ''; + $DEBUG and ::printmsg("$req->{peer}: $param->[0] pass $param->[1] does not match with '$subno' (subno)"); + return; + } + my $id = return "au:$subno"; + return +{ + id => $id, + atoken => $id, + }; +} + +# ----------------------------------------------------------------------------- +# $this->_login($req). +# special case for _dispatch(). +# +sub _login +{ + my $this = shift; + my $req = shift; + my $path = $req->{path}; + + $DEBUG and $this->_debug("$req->{peer}: login: process login dispatcher"); + + if( $path eq '/' ) + { + $this->_location($req, "/login"); + }elsif( $path eq '/login' ) + { + my $done = $req->{Method} eq 'POST' && $this->_post_login($req); + if( !$done ) + { + my $html = $this->_gen_login($req); + $this->_new_session($req); + $this->_response($req, [html=>$html]); + } + }elsif( $path eq '/logout' ) + { + # but not loged in. + $this->_location($req, "/login"); + }elsif( $path eq '/style/style.css' ) + { + $this->_response($req, [css=>'']); + }else { $this->_response($req, 404); return; } - $path =~ s/\?.*//; +} + +sub _post_login +{ + my $this = shift; + my $req = shift; + + my $cgi = $this->_get_cgi_hash($req); + if( my $name = $cgi->{n} ) + { + if( $req->{Header}{Cookie} ) + { + $DEBUG and $this->_debug("$req->{peer}: _post_login: name=$name"); + $this->_new_session($req); # regen. + $req->{session}{name} = $name; + my $path = $cgi->{path} || "/"; + $this->_location($req, $path); + return 1; + } + if( $cgi->{weak} ) + { + $DEBUG and $this->_debug("$req->{peer}: _post_login: name=$name (weak session)"); + $this->_new_session($req); # regen. + $req->{session}{name} = $name; + $req->{session}{_weak} = 1; + my $path = $cgi->{path} || "/"; + $this->_location($req, $path); + return 1; + } + $DEBUG and $this->_debug("$req->{peer}: _post_login: name=$name (no cookie)"); + return $this->_form_session($req); + } + + $DEBUG and $this->_debug("$req->{peer}: _post_login: skip"); + undef; +} +sub _gen_login +{ + my $this = shift; + my $req = shift; + + my $tmpl = $this->_gen_login_html(); + $this->_expand($req, $tmpl, { + NAME => $this->_escapeHTML($req->{session}{name} || '' ), + PATH => '', + }); +} +sub _gen_login_html +{ + < + + + + + + + + login + + +
    +
    + +

    Login

    + +
    +名前:
    +
    + +
    + +
    +
    + + +HTML +} + +sub _form_session +{ + my $this = shift; + my $req = shift; + + my $cgi = $this->_get_cgi_hash($req); + my $name = $cgi->{n}; + defined($name) or die "no cgi.name"; + + my $tmpl = $this->_gen_form_session_html(); + my $html = $this->_expand($req, $tmpl, { + NAME => $this->_escapeHTML($name), + PATH => '', + }); + $this->_response($req, [html=>$html]); + 1; +} +sub _gen_form_session_html +{ + < + + + + + + + + login (weak session) + + +
    +
    + +

    Login

    + +
    +名前:
    +
    + +() +
    + +

    +(※) 携帯等Cookieの機能が使えない場合, +代替手段としてクエリにセッションIDを埋め込みます.
    +ログインしてもこのページが繰り返し表示されてしまう場合にのみ +チェックを入れてください. +

    + +
    +
    + + +HTML +} + +# ----------------------------------------------------------------------------- +# $this->_dispatch($req). +# +sub _dispatch +{ + my $this = shift; + my $req = shift; + my $path = $req->{path}; if( $path eq '/' ) { @@ -534,21 +1116,16 @@ } $ch_short =~ s/%([0-9a-f]{2})/pack("H*",$1)/gie; - $ch_short =~ s/^=// or $ch_short = '#'.$ch_short; - if( !$this->{cache}{$netname}{$ch_short} ) + my $ch_short_orig = $ch_short; + my $netname_orig = $netname; + ($ch_short, $netname) = $this->_detect_channel($ch_short, $netname); + if( !$ch_short ) { - use Unicode::Japanese; - my $ch2 = Unicode::Japanese->new($ch_short,'sjis')->utf8; - if( $this->{cache}{$netname}{$ch2} ) - { - $ch_short = $ch2; - }else - { - RunLoop->shared_loop->notify_msg(__PACKAGE__."#_dispatch($path), not in cache ($netname/$ch_short)"); - $this->_response($req, 404); - return; - } + RunLoop->shared_loop->notify_msg(__PACKAGE__."#_dispatch($path), not in cache ($netname_orig/$ch_short_orig)"); + $this->_response($req, 404); + return; } + if( !$this->_can_show($req, $ch_short, $netname) ) { #RunLoop->shared_loop->notify_msg(__PACKAGE__."#_dispatch($path), could not show ($netname/$ch_short)"); @@ -566,30 +1143,142 @@ } }elsif( $param eq 'info' ) { - my $done = $req->{Method} eq 'POST' && $this->_post_chan_info($req, $netname, $ch_short); - if( !$done ) + my $done = $req->{Method} eq 'POST' && $this->_post_chan_info($req, $netname, $ch_short); + if( !$done ) + { + my $html = $this->_gen_chan_info($req, $netname, $ch_short); + $this->_response($req, [html=>$html]); + } + }else + { + $this->_response($req, 404); + } + }elsif( $path eq '/style/style.css' ) + { + $this->_response($req, [css=>'']); + }elsif( $path eq '/login' ) + { + $this->_login($req); + }elsif( $path eq '/logout' ) + { + $this->_delete_session($req); + $this->_location($req, "/"); + }elsif( $path eq '/config' ) + { + my $done = $req->{Method} eq 'POST' && $this->_post_config($req); + if( !$done ) + { + my $html = $this->_gen_config($req); + $this->_response($req, [html=>$html]); + } + }else + { + $this->_response($req, 404); + } +} + +# ($ch_short, $netname) = $this->_detect_channel($ch_short, $netname). +sub _detect_channel +{ + my $this = shift; + my $ch_short = shift; + my $netname = shift; + + if( $ch_short =~ s/^=// ) + { + # priv or special channels. + if( $this->{cache}{$netname}{$ch_short} ) + { + return wantarray ? ($ch_short, $netname) : $ch_short; + } + foreach my $extract_line ( $this->config->extract_network('all') ) + { + my ($extract, $sep) = split(' ', $extract_line); + $sep ||= '@'; + my $ch_long = $this->_attach($ch_short, $netname, $sep); + if( $this->{cache}{$extract}{$ch_long} ) + { + return wantarray ? ($ch_long, $extract) : $ch_short; + } + } + # not found. + return undef; + } + + if( $ch_short =~ s/^!// ) + { + foreach my $key (keys %{$this->{cache}{$netname}}) + { + $key =~ /^![0-9A-Z]{5}/ or next; + substr($key, 6) eq $ch_short or next; + return wantarray ? ($key, $netname) : $key; + } + # try decode from sjis. + my $ch2 = Unicode::Japanese->new($ch_short,'sjis')->utf8; + foreach my $key (keys %{$this->{cache}{$netname}}) + { + $key =~ /^![0-9A-Z]{5}/ or next; + substr($key, 6) eq $ch2 or next; + return wantarray ? ($key, $netname) : $key; + } + + foreach my $extract_line ( $this->config->extract_network('all') ) + { + my ($extract, $sep) = split(' ', $extract_line); + $sep ||= '@'; + my $ch_long = $this->_attach($ch_short, $netname, $sep); + my $ch_long2 = $this->_attach($ch2, $netname, $sep); + foreach my $key (keys %{$this->{cache}{$extract}}) { - my $html = $this->_gen_chan_info($req, $netname, $ch_short); - $this->_response($req, [html=>$html]); + $key =~ /^![0-9A-Z]{5}/ or next; + my $subkey = substr($key, 6); + if( $subkey eq $ch_long || $subkey eq $ch_long2 ) + { + return wantarray ? ($key, $extract) : $key; + } } - }else - { - $this->_response($req, 404); } - }elsif( $path eq '/style/style.css' ) + + # not found. + return undef; + } + + # normal channels. + $ch_short = '#'.$ch_short; + if( $this->{cache}{$netname}{$ch_short} ) { - $this->_response($req, [css=>'']); - }else + # found. + return wantarray ? ($ch_short, $netname) : $ch_short; + } + + foreach my $extract_line ( $this->config->extract_network('all') ) { - $this->_response($req, 404); + my ($extract, $sep) = split(' ', $extract_line); + $sep ||= '@'; + my $ch_long = $this->_attach($ch_short, $netname, $sep); + if( $this->{cache}{$extract}{$ch_long} ) + { + return wantarray ? ($ch_long, $extract) : $ch_short; + } + } + + # try decode from sjis. + my $ch2 = Unicode::Japanese->new($ch_short,'sjis')->utf8; + if( $this->{cache}{$netname}{$ch2} ) + { + return wantarray ? ($ch2, $netname) : $ch2; } + + # not found. + return undef; } sub _response { my $this = shift; my $req = shift; - my $res = shift; + my $res = shift; # number or hash-ref or array-ref. + if( ref($res) eq 'ARRAY' ) { my $spec = $res; @@ -620,9 +1309,44 @@ die "unkown response spec: $spec->[0]"; } } + if( $req->{cookies} ) + { + my @cookies; + foreach my $key (sort keys %{$req->{cookies}}) + { + my $val = $req->{cookies}{$key}; + $key =~ /^[a-zA-Z]\w+\z/ or die "invalid cookie name: $key"; + if( defined($val) ) + { + $val =~ s/([^-.\w])/'%'.unpack("H*",$1)/ge; + length($val) >= 100 and die "value of cookies.$key is too long"; + }else + { + # delete. + $val = "x; expires=Sun, 10-Jun-2001 12:00:00 GMT"; + } + my $cookie = "$key=$val; path=$this->{path}"; + push(@cookies, $cookie); + } + if( @cookies ) + { + ref($res) or $res = { + Code => $res, + }; + $res->{Header}{'Set-Cookie'} = $cookies[0]; + @cookies >= 2 and die "currently multiple cookies are not supported"; + } + } + + if( $req->{session}{_weak} && ref($res) ) + { + # HTTPコードだけの場合はLocation等もナイので書き換えの必要はなし. + $this->_rewrite_html($req, $res); + } my $cli = $req->{client}; $cli->response($res); + #$DEBUG and $this->_debug( Tools::HTTPParser->to_string($res) ); # no Keep-Alive. $req->{client}->disconnect_after_writing(); @@ -630,14 +1354,30 @@ return; } +# ----------------------------------------------------------------------------- +# $this->_location($path). +# サイト内リダイレクト. +# weak-sessionであればSIDの付与は自動で行われる. +# sub _location { my $this = shift; my $req = shift; my $path = shift; + $DEBUG and $this->_debug("$req->{peer}: location: $path"); $path = $this->{path} . $path; $path =~ s{//+}{/}g; + if( $req->{session}{_weak} ) + { + if( $path =~ /\?/ ) + { + $path .= "&SID=$req->{session}{_sid}"; + }else + { + $path .= "?SID=$req->{session}{_sid}"; + } + } my $res = { Code => 302, Header => { @@ -648,6 +1388,65 @@ } # ----------------------------------------------------------------------------- +# $this->_rewrite_html($req, $res). +# weak-session時のHTML書き換え. +# +sub _rewrite_html +{ + my $this = shift; + my $req = shift; + my $res = shift; + + $req->{session}{_weak} or return; + + my $sid_enc = $this->_escapeHTML($req->{session}{_sid}); + + $res->{Content} =~ s{(<.*?>)}{ + my $tag = $1; + if( $tag =~ /^}; + }else + { + $tag =~ s{\b(href|src)\s*=\s*(".*?"|[^\s>]*)}{ + my ($key, $val) = ($1, $2); + my $quoted = $val =~ s/^"(.*)"\z/$1/s; + my $fragment = $val =~ s/(#.*)//s ? $1 : ''; + my ($path, $query) = split(/\?/, $val, 2); + my @params; + if( defined($query) ) + { + @params = split(/[&;]/, $query); + foreach my $pair (@params) + { + my ($k, $v) = split(/=/, $pair, 2); + $k =~ tr/+/ /; + $k =~ s/%([0-9a-f]{2})/pack("H*",$1)/ge; + if( $k eq 'SID' ) + { + $pair = undef; + } + } + @params = grep{ defined($_) } @params; + } + push(@params, "SID=$sid_enc"); + my $new_val = $path . '?' . join('&', @params); + if( $quoted ) + { + $new_val = qq{"$new_val"}; + } + "$key=$new_val"; + }ges; + } + $tag; + }ges; + + $res->{Header}{'Content-Length'} = length($res->{Content}); + + $res; +} + +# ----------------------------------------------------------------------------- # $conflist = $this->_find_conf($req). # $conflist: この接続元に対して利用可能な allow 情報の一覧. # この時点ではまだ接続元IPアドレスでのチェックのみ. @@ -673,7 +1472,7 @@ name => $name, block => $block, masks => [$block->mask('all')], # 公開するチャンネルの一覧. - auth => $block->auth, + auth => [$block->auth('all')], }; push(@conflist, $allow); } @@ -682,29 +1481,74 @@ } # ----------------------------------------------------------------------------- -# $val = _decode_value($val). -# "{B}xxx" とかのデコード. +# $match = _verify_value($enc, $plain). +# パスワードの比較検証. +# "{MD5}xxx" (MD5) +# "{SMD5}xxx" (Salted MD5, hex(md5(pass+salt)+salt) +# "{B}xxx" (BASE64) +# "{RAW}xxx" (生パスワード) +# "{CRYPT}xxx" (cryptパスワード) +# "xxx" (生パスワード) # -sub _decode_value +sub _verify_value { - my $val = shift; - if( $val && $val =~ s/^\{(.*?)\}// ) + my $enc = shift; + my $plain = shift; + if( !defined($enc) || !defined($plain) ) + { + return undef; + } + my $type = $enc =~ s/^\{(.*?)\}// ? $1 : 'RAW'; + + if( $type =~ /^(B|B64|BASE64)\z/ ) { - my $type = $1; - if( $type =~ /^(B|B64|BASE64)\z/ ) + eval { require MIME::Base64; }; + if( $@ ) { - eval { require MIME::Base64; }; - if( $@ ) + die "no MIME::Base64"; + } + my $cmp = MIME::Base64::encode($plain, ''); + return $enc eq $cmp; + }elsif( $type =~ /^(MD5)\z/ ) + { + eval { require Digest::MD5; }; + if( $@ ) + { + die "no Digest::MD5"; + } + my $cmp = Digest::MD5::md5_hex($plain); + return $cmp eq lc($enc); + }elsif( $type =~ /^(SMD5)\z/ ) + { + eval { require Digest::MD5; }; + if( $@ ) + { + die "no Digest::MD5"; + } + my $enc_hex = substr($enc, 0, 32); + my $enc_salt = pack("H*",substr($enc, 32)); + my $cmp = Digest::MD5::md5_hex($plain.$enc_salt); + return $cmp eq lc($enc_hex); + }elsif( $type =~ /^(RAW)\z/ ) + { + return $enc eq $plain; + }elsif( $type =~ /^(CRYPT)\z/ ) + { + my $cmp = crypt($plain,substr($enc,0,2)); + if( length($plain) > 8 ) + { + my $cmp2 = crypt(substr($plain, 0, 8),substr($enc,0,2)); + if( $cmp eq $cmp2 ) { - die "no MIME::Base64"; + die "CRYPT supports upto 8 bytes"; + return; } - $val = MIME::Base64::decode($val); - }else - { - die "unsupported packed value, type=$type"; } + return $cmp eq $enc; + }else + { + die "unsupported packed value, type=$type"; } - $val; } # ----------------------------------------------------------------------------- @@ -724,7 +1568,7 @@ foreach my $allow (@$conflist) { my $ok = Mask::match_deep($allow->{masks}, $ch_full); - $this->_debug("- $netname / $ch_short = ".($ok?"ok":"ng")." ".join(", ",@{$allow->{masks}})); + $DEBUG and $this->_debug("- can_show: $netname / $ch_short = ".($ok?"ok":"ng")." mask: ".join(", ",@{$allow->{masks}})); if( $ok ) { return $ok; @@ -744,6 +1588,14 @@ my $peerhost = $req->{peerhost}; my $conflist = $req->{conflist}; + my $show_all; + if( my $show = $this->_get_cgi_hash($req)->{show} ) + { + $show_all = $show eq 'all'; + } + + # 表示できるネットワーク&チャンネルを抽出. + # my %channels; foreach my $netname (keys %{$this->{cache}}) { @@ -752,44 +1604,163 @@ my $ok = $this->_can_show($req, $ch_short, $netname); if( $ok ) { - push(@{$channels{$netname}}, $ch_short); + my $cache = $this->{cache}{$netname}{$ch_short}; + my $pack = { + disp_netname => $netname, + disp_ch_short => $ch_short, + anchor => undef, + unseen => undef, + unseen_plus => undef, + }; + + my $recent = $cache->{recent} || []; + my $seen = $req->{session}{seen}{$netname}{$ch_short} || 0; + my $nr_unseen = 0; + foreach my $r (reverse @$recent) + { + $r == $seen and last; + ++$nr_unseen; + } + + $pack->{unseen} = $nr_unseen; + if( $nr_unseen == $this->{max_lines}{''} && $recent->[0] != $seen ) + { + $pack->{unseen_plus} = 1; + } + + if( $seen ) + { + $pack->{anchor} = "L.$seen->{ymd}.$seen->{lineno}"; + } + + if( $nr_unseen > 0 || $show_all ) + { + push(@{$channels{$netname}}, $pack); + } + } + } + } + # 別のTiarraさんのネットワークを解凍(設定があったとき). + my %new_channels; + foreach my $extract_line ( $this->config->extract_network('all') ) + { + my ($extract, $sep) = split(' ', $extract_line); + $sep ||= '@'; + my $list = delete $channels{$extract} or next; + foreach my $pack (@$list) + { + my $ch_long = $pack->{disp_ch_short}; + my ($ch_short, $netname, $is_explicit) = $this->_detach($ch_long, $sep); + if( !$is_explicit ) + { + # wrong separator? + next; + } + if( $channels{$netname} && !$new_channels{$netname} ) + { + # no merge. + next; } + $pack->{disp_netname} = $netname; + $pack->{disp_ch_short} = $ch_short; + push(@{$new_channels{$netname}}, $pack); } } + %channels = (%channels, %new_channels); + # ネットワーク&チャンネルの一覧をHTML化. + # + my $is_pc = $req->{ua_type} eq 'pc'; my $content = ""; - $content .= "
      \n"; + $content .= $is_pc ? "
        \n" : "
        \n"; if( keys %channels ) { foreach my $netname (sort keys %channels) { - $content .= "
      • $netname\n"; - $content .= "
          \n"; + if( $is_pc ) + { + $content .= "
        • $netname\n"; + $content .= "
            \n"; + }else + { + $content .= "[$netname]
            \n"; + } my @channels = @{$channels{$netname}}; - @channels = sort @channels; - foreach my $channame (@channels) + @channels = sort {$a->{disp_ch_short} cmp $b->{disp_ch_short}} @channels; + my $seqno = 0; + foreach my $pack (@channels) { + my $channame = $pack->{disp_ch_short}; + ++$seqno; my $link_ch = $channame; - $link_ch =~ s/^#// or $link_ch = "=$link_ch"; + if( $link_ch =~ s/^#// ) + { + # normal channels. + }elsif( $link_ch =~ s/^![0-9A-Z]{5}/!/ ) + { + # channel = ( "#" / "+" / ( "!" channelid ) / "&" ) chanstring [ ":" chanstring ] + # channelid = 5( %x41-5A / digit ) ; 5( A-Z / 0-9 ) + # (RFC2812) + }else + { + $link_ch = "=$link_ch"; + } my $link = "log\0$netname\0$link_ch\0"; - $link =~ s{/}{%2F}g; + $link =~ s{/}{%252F}g; $link =~ tr{\0}{/}; $link = $this->_escapeHTML($link); - $content .= qq{
          • $channame
          • \n}; + + my $unseen; + if( !$pack->{unseen} ) + { + $unseen = ''; + }else + { + my $nr_unseen = $pack->{unseen}; + my $plus = $pack->{unseen_plus} ? '+' : ''; + $unseen = " ($nr_unseen$plus)"; + } + + my $channame_label = $this->_escapeHTML($channame); + $channame_label =~ s/^![0-9A-Z]{5}/!/; + my $ref = $pack->{anchor} ? "?x=$pack->{anchor}" : ''; + if( $is_pc ) + { + $content .= qq{
          • $channame_label$unseen
          • \n}; + }else + { + $content .= qq{$seqno. $channame_label$unseen
            \n}; + } + } + if( $is_pc ) + { + $content .= "
          \n"; + $content .= "
        • \n"; } - $content .= "
        \n"; - $content .= "
      • \n"; } }else { - $content = "
      • no channels
      • \n"; + $content = $is_pc ? "
      • no channels
      • \n" : "no channels\n"; } - $content .= "
      \n"; + $content .= $is_pc ? "
    \n" : ""; + my $shared_box = ''; + my $mode = $this->_get_req_param($req, 'mode'); + if( $mode ne 'owner' ) + { + $shared_box .= "
    \n"; + $shared_box .= "[\n"; + $shared_box .= qq{設定\n}; + $shared_box .= "|\n"; + $shared_box .= qq{ログアウト\n}; + $shared_box .= "]\n"; + } my $tmpl = $this->_gen_list_html(); - $this->_expand($tmpl, { + $this->_expand($req, $tmpl, { CONTENT => $content, - UA_TYPE => $req->{ua_type}, + SHOW_TOGGLE_LABEL => $show_all ? 'MiniList' : 'ShowAll', + SHOW_TOGGLE_VALUE => $show_all ? 'updated' : 'all', + SHARED_BOX => $shared_box, }); } sub _gen_list_html @@ -813,15 +1784,17 @@ <&CONTENT> -
    + ENTER:

    [ -再表示[0] +再表示[0] | +<&SHOW_TOGGLE_LABEL>[#] ] +<&SHARED_BOX>

    @@ -848,7 +1821,7 @@ if( $network ) { $this->{cache}{$netname}{$ch_short} ||= $this->_new_cache_entry($netname, $ch_short); - $this->_debug("enter: $netname/$ch_short"); + $DEBUG and $this->_debug("enter: $netname/$ch_short"); my $link_ch = $ch_short; $link_ch =~ s/^#// or $link_ch = "=$link_ch"; my $link = "log\0$netname\0$link_ch\0"; @@ -864,15 +1837,25 @@ sub _expand { my $this = shift; + my $req = shift; my $tmpl = shift; my $vars = shift; - my $top_path_esc = $this->_escapeHTML($this->{path}); - my $css_esc = $this->_escapeHTML($this->config->css || "$this->{path}style/style.css"); + my $top_path_esc = $this->_escapeHTML($this->{path}); + my $css_esc = $this->_escapeHTML($this->config->css || "$this->{path}style/style.css"); + my $site_name_esc = $this->_escapeHTML($this->config->site_name || $DEFAULT_SITE_NAME); + $req->{ua_type} =~ /^\w+\z/ or die "invalid ua_type: [$req->{ua_type}]"; my $common_vars = { - TOP_PATH => $top_path_esc, - CSS => $css_esc, + TOP_PATH => $top_path_esc, + CSS => $css_esc, + UA_TYPE => $req->{ua_type}, + SITE_NAME => $site_name_esc, + VERSION => '-', }; + if( $req->{session}{name} ) + { + $common_vars->{VERSION} = $VERSION; + } $tmpl =~ s{<&(.*?)>}{ my $key = $1; @@ -910,24 +1893,11 @@ if( my $chan = $net->channel($ch_short) ) { my $topic = $chan->topic || '(no-topic)'; - my $names = $chan->names || {}; - $names = [ values %$names ]; - @$names = map{ - my $pic = $_; # $pic :: PersonInChannel. - my $nick = $pic->person->nick; - my $sigil = $pic->priv_symbol; - "$sigil$nick"; - } @$names; - @$names = sort @$names; my $topic_esc = $this->_escapeHTML($topic); - my $names_esc = $this->_escapeHTML(join(' ', @$names)); $content .= "

    \n"; $content .= "TOPIC: $topic_esc
    \n"; - #$content .= "member: $names_esc
    \n"; $content .= "

    \n"; } - }else - { } my $cache = $this->{cache}{$netname}{$ch_short}; @@ -970,6 +1940,26 @@ } $rindex ||= 0; # $rindex も含めてindex系は [0..$#$recent] の範囲の値. + if( my $xtoken = $cgi->{x} ) + { + my $re = qr/\Q$xtoken\E\z/; + foreach my $i ($rindex..$#$recent ) + { + my $info = $recent->[$i]; + my $anchor = "L.$info->{ymd}.$info->{lineno}"; + if( $anchor =~ $re ) + { + if( $i < $#$recent ) + { + $rindex = $i + 1; + }else + { + $rindex = $#$recent; + } + last; + } + } + } my $last; if( $rindex + $show_lines > @$recent ) @@ -980,15 +1970,35 @@ $last = $rindex + $show_lines - 1; } + # 既読情報の更新. + my $last_seen_index; + if( my $cur = $req->{session}{seen}{$netname}{$ch_short} ) + { + foreach my $i ($last+1 .. $#$recent) + { + if( $recent->[$i] == $cur ) + { + $last_seen_index = $i; + last; + } + } + } + if( !defined($last_seen_index) ) + { + $last_seen_index = $last; + my $last_seen = @$recent ? $recent->[$last] : undef; + $req->{session}{seen}{$netname}{$ch_short} = $last_seen; + } + my $next_index = $last < $#$recent ? $last + 1 : $#$recent; my $prev_index = $rindex < $show_lines ? 0 : ($rindex - $show_lines); - my ($next_rtoken, $prev_rtoken) = map { + my ($next_rtoken, $prev_rtoken, $last_seen_rtoken) = map { my $i = $_; - my $info = $recent->[$i]; + my $info = @$recent ? $recent->[$i] : {ymd=>'-00',lineno=>0}; my $anchor = "L.$info->{ymd}.$info->{lineno}"; $anchor =~ s/.*-//; $anchor; - } $next_index, $prev_index; + } $next_index, $prev_index, $last_seen_index; my $nr_cached_lines = @$recent; my $lines2 = $nr_cached_lines==1 ? 'line' : 'lines'; @@ -998,7 +2008,7 @@ if( @$recent ) { my $sort_order = $this->_get_req_param($req, 'sort-order'); - $this->_debug("sort_order = $sort_order"); + $DEBUG and $this->_debug("sort_order = $sort_order"); if( $sort_order ne 'asc' ) { @$recent = reverse @$recent; @@ -1030,7 +2040,13 @@ my $anchor = "L.$ymd.$info->{lineno}"; my $rtoken = $anchor; $rtoken =~ s/.*-//; - $content .= qq{$info->{lineno}/$line_html\n}; + + my $a_tag = qq{}; + if( $line_html !~ s{^(\d\d:\d\d(?::\d\d)?)}{$a_tag$1} ) + { + $line_html = "$a_tag$info->{lineno}/" . $line_html; + } + $content .= $line_html . "\n"; } $content .= "\n"; }else @@ -1041,27 +2057,38 @@ } my $ch_long = Multicast::attach($ch_short, $netname); + $ch_long =~ s/^![0-9A-Z]{5}/!/; my $ch_long_esc = $this->_escapeHTML($ch_long); - my $name_esc = $this->_escapeHTML($cgi->{n} || ''); + my $name_esc = $this->_escapeHTML($req->{session}{name} || ''); my $mode = $this->_get_req_param($req, 'mode'); - my $name_input_raw = ''; + my $name_marker_raw = ''; if( $mode ne 'owner' ) { - $name_input_raw = qq{name:
    }; + $name_marker_raw = qq{$name_esc> }; + } + + my $h1_ch_long_raw; + if( $req->{ua_type} eq 'pc' ) + { + $h1_ch_long_raw = "

    $ch_long_esc

    "; + }else + { + $h1_ch_long_raw = "$ch_long_esc"; } my $tmpl = $this->_gen_log_html(); - $this->_expand($tmpl, { + $this->_expand($req, $tmpl, { CONTENT_RAW => $content, - UA_TYPE => $req->{ua_type}, NAVI_RAW => $navi_raw, CH_LONG => $ch_long_esc, + H1_CH_LONG_RAW => $h1_ch_long_raw, NAME => $name_esc, - NAME_INPUT_RAW => $name_input_raw, + NAME_MARKER_RAW => $name_marker_raw, RTOKEN => $next_rtoken, NEXT_RTOKEN => $next_rtoken, PREV_RTOKEN => $prev_rtoken, + LAST_SEEN_RTOKEN => $last_seen_rtoken, }); } sub _gen_log_html @@ -1081,16 +2108,15 @@
    -

    <&CH_LONG>

    +<&H1_CH_LONG_RAW> <&CONTENT_RAW> -
    +

    -talk: +talk:<&NAME_MARKER_RAW>
    -<&NAME_INPUT_RAW> - +

    @@ -1098,7 +2124,7 @@

    [ -更新[*] | +更新[*] | List[0] | info[#] ] @@ -1138,7 +2164,7 @@ if( $key eq 'mode' ) { $val ||= 'owner'; - if( $val !~ /^(owner|shared)\z/ ) + if( $val !~ /^(?:owner|shared)\z/ ) { $val = 'owner'; } @@ -1146,7 +2172,7 @@ if( $key eq 'sort-order' ) { $val ||= 'asc'; - $val = $val =~ /^(desc|rev)/ ? 'desc' : 'asc'; + $val = $val =~ /^(?:desc|rev)/ ? 'desc' : 'asc'; } $req->{req_param}{$key} = $val; @@ -1173,6 +2199,7 @@ foreach my $pair (split(/[&;]/, $query)) { my ($key, $val) = split(/=/, $pair, 2); + $val =~ tr/+/ /; $val =~ s/%([0-9a-f]{2})/pack("H*",$1)/gie; $cgi->{$key} = $val; } @@ -1184,6 +2211,7 @@ foreach my $pair (split(/[&;]/, $req->{Content})) { my ($key, $val) = split(/=/, $pair, 2); + $val =~ tr/+/ /; $val =~ s/%([0-9a-f]{2})/pack("H*",$1)/gie; $cgi->{$key} = $val; } @@ -1203,12 +2231,12 @@ my $mode = $this->_get_req_param($req, 'mode'); my $cgi = $this->_get_cgi_hash($req); - my $name = $cgi->{n} || ''; if( my $m = $cgi->{m} ) { if( $mode ne 'owner' ) { - $m = ($name || $this->config->name_default || $DEFAULT_NAME) . "> " . $m; + my $name = $req->{session}{name} or die "no session.name"; + $m = "$name> $m"; } $m =~ s/[\r\n].*//s; my $network = RunLoop->shared_loop->network($netname); @@ -1296,12 +2324,12 @@ } my $ch_long = Multicast::attach($ch_short, $netname); + $ch_long =~ s/^![0-9A-Z]{5}/!/; my $ch_long_esc = $this->_escapeHTML($ch_long); my $tmpl = $this->_tmpl_chan_info(); - $this->_expand($tmpl, { + $this->_expand($req, $tmpl, { CONTENT_RAW => $content_raw, - UA_TYPE => $req->{ua_type}, CH_LONG => $ch_long_esc, TOPIC => $topic_esc, IN_TOPIC => $in_topic_esc, @@ -1330,7 +2358,7 @@ <&CONTENT_RAW> -

    + TOPIC: <&TOPIC>

    @@ -1340,18 +2368,18 @@ NAMES: <&NAMES>

    - + PART:
    -
    -JOIN + +JOIN
    -
    -DELETE + +DELETE
    @@ -1447,6 +2475,82 @@ } # ----------------------------------------------------------------------------- +# $html = $this->_gen_config($req). +# +sub _gen_config +{ + my $this = shift; + my $req = shift; + + my $name_esc = $this->_escapeHTML( $req->{session}{name} || '' ); + + my $tmpl = $this->_tmpl_config(); + $this->_expand($req, $tmpl, { + NAME => $name_esc, + }); +} +sub _tmpl_config +{ + < + + + + + + + + 設定 + + +
    +
    + +

    設定

    + +
    +名前:
    +
    +
    + +

    +System::WebClient version <&VERSION>. +

    + +

    +[ +戻る[*] | +List[0] | +再表示[#] +] +

    + +
    +
    + + +HTML +} + +sub _post_config +{ + my $this = shift; + my $req = shift; + + my $cgi = $this->_get_cgi_hash($req); + if( $cgi->{n} ) + { + my $old = $req->{session}{name} || '-'; + $this->_debug("$req->{peer}: rename: $old => $cgi->{n}"); + + $req->{session}{name} = $cgi->{n}; + } + + return undef; +} + + +# ----------------------------------------------------------------------------- # $txt = $this->_escapeHTML($html). # sub _escapeHTML @@ -1455,6 +2559,74 @@ Tools::HTTPParser->escapeHTML(@_); } +# ($ch_short, $net_name, $explicit) = $this->_detach($ch_long, $sep); +# $ch_short = $this->_detach($ch_long, $sep); +sub _detach { + my $this = shift; + my $str = shift; + my $sep = shift; + + if (!defined $str) { + die "Arg[0] was undef.\n"; + } + elsif (ref($str) ne '') { + die "Arg[0] was ref.\n"; + } + + my @result; + if ((my $sep_index = index($str,$sep)) != -1) { + my $before_sep = substr($str,0,$sep_index); + my $after_sep = substr($str,$sep_index+length($sep)); + if ((my $colon_pos = index($after_sep,':')) != -1) { + # #さいたま@taiyou:*.jp → #さいたま:*.jp + taiyou + @result = ($before_sep.substr($after_sep,$colon_pos), + substr($after_sep,0,$colon_pos), + 1); + } + else { + # #さいたま@taiyou → #さいたま + taiyou + @result = ($before_sep,$after_sep,1); + } + } + else { + @result = ($str,$this->_runloop->default_network,undef); + } + return wantarray ? @result : $result[0]; +} + +sub _attach { + # $strはChannelInfoのオブジェクトでも良い。 + # $network_nameは省略可能。IrcIO::Serverのオブジェクトでも良い。 + my $this = shift; + my $str = shift; + my $network_name = shift; + my $separator = shift; + + if (ref($str) eq 'ChannelInfo') { + $str = $str->name; + } + if (ref($network_name) eq 'IrcIO::Server') { + $network_name = $network_name->network_name; + } + + if (!defined $str) { + die "Arg[0] was undef.\n"; + } + elsif (ref($str) ne '') { + die "Arg[0] was ref.\n"; + } + + $network_name = $this->_runloop->default_network if $network_name eq ''; + if ((my $pos_colon = index($str,':')) != -1) { + # #さいたま:*.jp → #さいたま@taiyou:*.jp + $str =~ s/:/$separator.$network_name.':'/e; + } + else { + # #さいたま → #さいたま@taiyou + $str .= $separator.$network_name; + } + $str; +} # ----------------------------------------------------------------------------- # End of Module. # ----------------------------------------------------------------------------- @@ -1483,9 +2655,9 @@ # WebClient を起動させる場所の指定. bind-addr: 127.0.0.1 -bind-port: 8668 +bind-port: 8667 path: /irc -css: /style/irc-style.css +css: /irc/style/style.css # 上の設定をapacheでReverseProxyさせる場合, httpd.conf には次のように設定. # ProxyPass /irc/ http://localhost:8667/irc/ # ProxyPassReverse /irc/ http://localhost:8667/irc/ @@ -1495,6 +2667,7 @@ # ReverseProxy 利用時の追加設定. # 接続元が全部プロキシサーバになっちゃうのでその対応. +# ReverseProxy 使わず直接公開の場合は不要. -extract-forwarded-for: 127.0.0.1 # 利用する接続設定の一覧. @@ -1515,7 +2688,14 @@ # 接続元IPアドレスの制限. host: 127.0.0.1 # 認証設定. - auth: user pass + # auth: + # auth: :basic + # auth: :softbank <端末ID> + # auth: :softbank + # auth: :au + # 各値(等)には {MD5}xxxx や {B}xxx や {CRYPT}xxx を利用可能. + # そのままべた書きも出来るけれど. + auth: :basic user pass # 公開するチャンネルの指定. mask: #*@* mask: *@* @@ -1540,9 +2720,20 @@ # asc (旧->新) か desc (新->旧). - sort-order: asc -# 発言BOXで名前指定しなかったときのデフォルトの名前. -# mode: shared の時に使われる. --name-default: (noname) +# name-default 設定は VERSION 0.05 で廃止されました. +# # 発言BOXで名前指定しなかったときのデフォルトの名前. +# # mode: shared の時に使われる. +# -name-default: (noname) + +# 外部にTiarraさんを使っているときに, そこのネットワークを切り出して表示する. +# exteact-network: +# ::= このTiarraさんから見たときの外部Tiarraさんのネットワーク名. +# (このtiarra.confで指定しているネットワーク名) +# ::= 外部Tiarraさんで使っているセパレータ. +# (こっちはこのtiarra.confのではないです) +# 省略すると @ と仮定. +-exteact-network: tiarra +-exteact-network: tiarra @ =end tiarra-doc diff -urN tiarra-20080510/module/Tools/HTTPClient/SSL.pm tiarra-20090206/module/Tools/HTTPClient/SSL.pm --- tiarra-20080510/module/Tools/HTTPClient/SSL.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/Tools/HTTPClient/SSL.pm 2009-02-09 22:30:10.000000000 +0900 @@ -11,7 +11,7 @@ # # Copyright 2008 YAMASHINA Hio # ----------------------------------------------------------------------------- -# $Id: SSL.pm 10954 2008-05-02 13:24:15Z hio $ +# $Id: SSL.pm 15585 2008-07-09 17:01:45Z topia $ # ----------------------------------------------------------------------------- package Tools::HTTPClient::SSL; use strict; @@ -216,7 +216,7 @@ { my $e = $ssl->errstr; #print "read: $e\n"; - if( $e =~ /SSL wants a read first!/ ) + if( $e =~ /SSL wants a read first/ ) { last; } diff -urN tiarra-20080510/module/Tools/HTTPClient.pm tiarra-20090206/module/Tools/HTTPClient.pm --- tiarra-20080510/module/Tools/HTTPClient.pm 2008-05-11 00:25:25.000000000 +0900 +++ tiarra-20090206/module/Tools/HTTPClient.pm 2009-02-09 22:30:10.000000000 +0900 @@ -1,6 +1,6 @@ # -*- cperl -*- # ----------------------------------------------------------------------------- -# $Id: HTTPClient.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: HTTPClient.pm 12479 2008-05-26 15:42:00Z drry $ # ----------------------------------------------------------------------------- # HTTP/1.1非対応。 # ----------------------------------------------------------------------------- @@ -148,7 +148,7 @@ $this->{header}{Host} = $host; } - #googleさん関係でいまいち動かない? + #Googleさん関係でいまいち動かない? #if (!$this->{header}{Connection}) { # # HTTP/1.1 だと Keep-Alive がデフォルトだけど, # # ひとまず気にしない…. @@ -256,7 +256,7 @@ alarm(0); }; $@ && !$timedout and die; - + return $addr; } @@ -342,7 +342,7 @@ #$DEBUG and print "<>\n".$this->{socket}->sendbuf."<>\n";; $this->{socket}->eol( pack("C*", map{rand(256)}1..32) ); - #googleさん関係でいまいち動かない? + #Googleさん関係でいまいち動かない? #$this->{shutdown_wr_after_writing} = !$this->{header}{Connection} || $this->{header}{Connection} =~ /close/i || $this->{header}{Connection} !~ /Keep-Alive/i; #if( $this->{host} =~ /google|gmail/ ) #{ @@ -361,7 +361,7 @@ # $obj->_main(). # (private) # -sub _main +sub _main { my $this = shift; @@ -421,7 +421,7 @@ if( $this->{parser}->isa('Tools::HTTPParser') ) { my $st = $this->{parser}->object->{StreamState}; - $success = $st =~ /^(body|parsed)\z/ && $this->{parser}{rest} eq ''; + $success = $st =~ /^(?:body|parsed)\z/ && $this->{parser}{rest} eq ''; }else { $this->{parser}->object->content( $this->{parser}->data ); @@ -455,7 +455,7 @@ my ($this, $err) = @_; $this->stop; - + if ($err) { $this->{callback}->($err); } @@ -535,7 +535,7 @@ HTTP Client for tiarra. ブロックしないように処理は非推奨なので, -ブロックしないように調整されている HTTP +ブロックしないように調整されている HTTP クライアントモジュール. =head1 METHODS @@ -586,7 +586,7 @@ my $content = $response->{Content}; } -C<\&progress_callback> も同様, +C<\&progress_callback> も同様, ただしこちらはエラーの報告には呼ばれない. =head2 stop diff -urN tiarra-20080510/module/Tools/ID3Tag.pm tiarra-20090206/module/Tools/ID3Tag.pm --- tiarra-20080510/module/Tools/ID3Tag.pm 1970-01-01 09:00:00.000000000 +0900 +++ tiarra-20090206/module/Tools/ID3Tag.pm 2009-02-09 22:30:11.000000000 +0900 @@ -0,0 +1,177 @@ +## ---------------------------------------------------------------------------- +# Tools::ID3Tag. +# ----------------------------------------------------------------------------- +# Mastering programmed by YAMASHINA Hio +# +# Copyright 2008 YAMASHINA Hio +# ----------------------------------------------------------------------------- +# $Id: ID3Tag.pm 15704 2008-07-12 12:31:42Z hio $ +# ----------------------------------------------------------------------------- +package Tools::ID3Tag; +use strict; +use warnings; +use Unicode::Japanese; + +our $VERSION = '0.01'; + +1; + +# ----------------------------------------------------------------------------- +# $info = $pkg->extract($content). +# $info->{version} = "2.x.x"; +# $info->{title} = $title | undef; +# $info->{artist} = $artist | undef; +# $info->{album} = $album | undef; +# +sub extract +{ + my $pkg = shift; + my $content = shift; + + if( $content !~ m{^ID3(.{7})}s ) + { + return; + } + + # ID3v2 tag. + my ($major, $minor, $flags, $size) = unpack("CCCN", $1); + my $ver = "2.$major.$minor"; + my $is_unsync = $flags & (1 << 7); + my $has_ex_header = ($flags & (1 << 6)) && ($major >= 3); + my $has_expermental = $flags & (1 << 5); + my $has_footer = $flags & (1 << 4); + #$DEBUG and print "id3v2: version=$ver, flags=".(sprintf("%x",$flags)).", ex=".($has_ex_header?"yes":"no").", footer=".($has_footer?"yes":"no")."\n"; + + if( $size < length($content) ) + { + $content = substr($content, 0, $size); + } + + my $offset = 10; + my $info = { + version => $ver, + size => $size, + title => undef, + artist => undef, + album => undef, + }; + my $old_frameid = { + TT2 => 'TIT2', + TP1 => 'TPE1', + TAL => 'TALB', + }; + + if( $has_ex_header ) + { + my $ex_size = unpack("\@$offset N", $content); + if( !$ex_size || $ex_size <= 6 || $offset + $ex_size > length($content) ) + { + return; + } + #my $ex = substr($content, $offset, $size); + $offset += $size; + } + + # frames. + for(;;) + { + my ($id, $size, $flags); + my ($hsize, $hformat); + if( $major == 2 ) + { + # 2.2.x + if( $offset + 6 > length($content) ) + { + last; + } + ($id, $size) = unpack("\@$offset a3 a3", $content); + $id = $old_frameid->{$id} || $id; + $size = unpack("N", "\0".$size); + $flags = 0; + $offset += 6; + }else + { + # 2.3.x- + if( $offset + 10 > length($content) ) + { + last; + } + ($id, $size, $flags) = unpack("\@$offset a4 N n", $content); + $offset += 10; + } + if( $offset + $size > length($content) ) + { + # over flow. + last; + } + my $pack = substr($content, $offset, $size); + $offset += $size; + + if( $id eq 'TIT2' ) + { + $info->{title} = $pkg->_decode_text_normal($pack); + } + if( $id eq 'TPE1' ) + { + $info->{artist} = $pkg->_decode_text_normal($pack); + } + if( $id eq 'TALB' ) + { + $info->{album} = $pkg->_decode_text_normal($pack); + } + } + $info; +} + +sub _decode_text_normal +{ + my $pkg = shift; + my $pack = shift; + + defined($pack) && length($pack)>=1 or die "#_decode_text_normal, no input"; + + my $type = unpack("C", substr($pack, 0, 1, '')); + my $out; + if( $type == 0 ) + { + # local encoding. + $out = Unicode::Japanese->new($pack, 'auto')->utf8; + }elsif( $type == 1 ) + { + # UTF-16 (with-BOM) + $out = Unicode::Japanese->new($pack, 'auto')->utf8; + }elsif( $type == 2 ) + { + # UTF-16BE (without-BOM) + $out = Unicode::Japanese->new($pack, 'utf16be')->utf8; + }elsif( $type == 3 ) + { + # UTF-8 + $out = $pack; + }else + { + die "#_decode_text_normal, unsupported type: $type"; + } + $out =~ s/\0+\z//; + $out; +} + + +# ----------------------------------------------------------------------------- +# End of Module. +# ----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- +# End of File. +# ----------------------------------------------------------------------------- +__END__ + +=encoding utf8 + +=for stopwords + YAMASHINA + Hio + ACKNOWLEDGEMENTS + AnnoCPAN + CPAN + RT + diff -urN tiarra-20080510/module/Tools/Reload.pm tiarra-20090206/module/Tools/Reload.pm --- tiarra-20080510/module/Tools/Reload.pm 1970-01-01 09:00:00.000000000 +0900 +++ tiarra-20090206/module/Tools/Reload.pm 2009-02-09 22:30:11.000000000 +0900 @@ -0,0 +1,157 @@ +# ----------------------------------------------------------------------------- +# $Id: Reload.pm 12856 2008-05-30 14:37:23Z hio $ +# ----------------------------------------------------------------------------- +# Tools::Reload. +# ----------------------------------------------------------------------------- +package Tools::Reload; +use strict; +use warnings; +use BulletinBoard; +use Timer; + +# ----------------------------------------------------------------------------- +# とりあえずサンプル. +# うごくかわかんない. +# ----------------------------------------------------------------------------- + +our $VERSION = '0.01'; +our $BBS_KEY = __PACKAGE__; + +our $DEFAULT_EXPIRE = 300; # seconds. + +1; + +# ----------------------------------------------------------------------------- +# (private) +# $bbs_val = $pkg->_get(). +# $bbs_val = $pkg->_get(-no_create). +# 全部の情報が入ってるハッシュを取得. +# +sub _get +{ + my $this = shift; + my $no_create = shift; + + my $bbs_val = BulletinBoard->shared->get($BBS_KEY); + if( !$bbs_val && !$no_create ) + { + #$runloop->notify_msg(__PACKAGE__."#_get, bbs[$BBS_KEY] initialize"); + $bbs_val = { + inited_at => time, + data => {}, + }; + BulletinBoard->shared->set($BBS_KEY, $bbs_val); + } + $bbs_val; +} + +# ----------------------------------------------------------------------------- +# Reload->store($my_key, $value). +# +sub store +{ + my $this = shift; + my $opts; + if( @_ >= 2 ) + { + my $key = shift; + my $value = shift; + $opts = { + Key => $key, + Value => $value, + Expire => $DEFAULT_EXPIRE, + }; + }else + { + $opts = shift; + } + + my $key = $opts->{Key} or die __PACKAGE__."#store, no Key"; + my $value = $opts->{Value} or die __PACKAGE__."#store, no Value"; + my $expire = $opts->{Expire} || $DEFAULT_EXPIRE; + + my $bbs_val = $this->_get(); + + my $ref = ref($key) || "$key"; + my $timer = Timer->new( + After => $expire, + Code => sub{ + if( $bbs_val->{data} ) + { + delete $bbs_val->{data}{$ref}; + } + }, + )->install; + + my $entry = { + key => $ref, + value => $value, + timer => $timer, + after => $expire, + }; + + $bbs_val->{data}{$ref} = $entry; + $value; +} + +# ----------------------------------------------------------------------------- +# my $value = Reload->get($my_key). +# +sub get +{ + my $this = shift; + my $key = shift; + + $key or die __PACKAGE__."#get, no key"; + + my $bbs_val = $this->_get(); + + my $ref = ref($key) || "$key"; + $bbs_val->{data}{$ref} && $bbs_val->{data}{$ref}{value}; +} + +# ----------------------------------------------------------------------------- +# my $existence = Reload->exists($my_key). +# +sub exists +{ + my $this = shift; + my $key = shift; + + $key or die __PACKAGE__."#exists, no key"; + + my $bbs_val = $this->_get(); + + my $ref = ref($key) || "$key"; + CORE::exists($bbs_val->{data}{$ref}); +} + +=head1 NAME + +Tools::Reload - save data for reloading. + +=head1 SYNOPSIS + + $my_key = __PACKAGE__; + + # At destruct(). + Tools::Reload->store($my_key, $value); + + # At new(). + my $value = Tools::Reload->fetch($my_key); + if( !$value ) + { + # new loading. + }else + { + # reloading. + } + +=head1 DESCRIPTION + +リロード用にデータの一時保存. +リロードじゃなくてアンロードだった場合は, +タイマーで既定秒後に削除される. + +=cut + diff -urN tiarra-20080510/module/User/Nick/Detached.pm tiarra-20090206/module/User/Nick/Detached.pm --- tiarra-20080510/module/User/Nick/Detached.pm 2008-05-11 00:25:26.000000000 +0900 +++ tiarra-20090206/module/User/Nick/Detached.pm 2009-02-09 22:30:11.000000000 +0900 @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# $Id: Detached.pm 11365 2008-05-10 14:58:28Z topia $ +# $Id: Detached.pm 29651 2009-02-06 13:44:53Z topia $ # ----------------------------------------------------------------------------- # このモジュールはRunLoopのcurrent_nick、すなわちローカルnickを変更しない。 # ----------------------------------------------------------------------------- @@ -8,15 +8,31 @@ use warnings; use base qw(Module); use RunLoop; +our $BB_KEY = __PACKAGE__.'/old-nick'; + +sub destruct { + my $this = shift; + # 掲示板から最終nickを消す + BulletinBoard->shared->set($BB_KEY, undef); +} + +sub config_reload { + my ($this, $old_config) = @_; + # リロードのときは掲示板を保持しておきたいので定義だけしておく。 +} sub client_attached { my ($this,$client) = @_; - # クライアントが接続されたという事は、 - # 少なくとも一つ以上のクライアントが存在するに決まっている。 - RunLoop->shared->broadcast_to_servers( - $this->construct_irc_message( - Command => 'NICK', - Param => RunLoop->shared->current_nick)); + + # 掲示板に古いnickが保存されていたら変更する。 + my $old_nick = BulletinBoard->shared->get($BB_KEY); + if (defined $old_nick) { + BulletinBoard->shared->set($BB_KEY, undef); + RunLoop->shared->broadcast_to_servers( + $this->construct_irc_message( + Command => 'NICK', + Param => $old_nick)); + } } sub client_detached { @@ -25,6 +41,7 @@ if (@{RunLoop->shared->clients} == 1 && defined $this->config->detached) { + BulletinBoard->shared->set($BB_KEY, RunLoop->shared->current_nick); RunLoop->shared->broadcast_to_servers( $this->construct_irc_message( Command => 'NICK', @@ -37,7 +54,12 @@ # クライアントの数が0ならNICKを実行。 if (@{RunLoop->shared->clients} == 0 && defined $this->config->detached) { - + + if (!defined BulletinBoard->shared->get($BB_KEY)) { + # 定義されていない場合(起動直後など)は現在のnickを記録しておく + BulletinBoard->shared->set($BB_KEY, RunLoop->shared->current_nick); + } + $server->send_message( $this->construct_irc_message( Command => 'NICK', diff -urN tiarra-20080510/sample.conf tiarra-20090206/sample.conf --- tiarra-20080510/sample.conf 2008-05-11 00:25:26.000000000 +0900 +++ tiarra-20090206/sample.conf 2009-02-09 22:30:12.000000000 +0900 @@ -1,10 +1,10 @@ # -*- tiarra-conf -*- # ----------------------------------------------------------------------------- -# $Id: sample.conf.in 11365 2008-05-10 14:58:28Z topia $ +# $Id: sample.conf.in 15771 2008-07-13 23:55:21Z drry $ # ----------------------------------------------------------------------------- # tiarra.conf サンプル # -# tiarraは起動時に全ての設定をこのファイルから取得します。 +# Tiarraは起動時に全ての設定をこのファイルから取得します。 # このファイルの文字コードは任意ですが、改行コードはLFもしくはCRLFでなければなりません。 # # 半角の#で始まる行はコメントとして無視されます。 @@ -91,7 +91,7 @@ # crypt は ./tiarra --make-password で行えます。 tiarra-password: xl7cflIcH9AwE - # 外部プログラムからtiarraをコントロールする為のUNIXドメインソケットの名前。 + # 外部プログラムからTiarraをコントロールする為のUNIXドメインソケットの名前。 # 例えば"foo"を指定した場合、ソケット/tmp/tiarra-control/fooが作られる。 # 省略された場合はこの機能を無効とする。 # また、非UNIX環境ではそもそもUNIXドメインソケットが利用可能でないため、 @@ -108,7 +108,7 @@ client-in-encoding: jis client-out-encoding: jis - # Tiarraは標準出力に様々なメッセージを出力するが、その文字コードを指定する。省略時にはeucとなる。 + # Tiarraは標準出力に様々なメッセージを出力するが、その文字コードを指定する。省略時にはutf8となる。 # ただしtiarra.confのパースが完了するまでは文字コードの変換は行なわれない(つまりこの設定が有効にならない)ことに注意して下さい。 stdout-encoding: utf8 @@ -144,7 +144,7 @@ #ipv4-bind-addr: 0.0.0.0 #ipv6-bind-addr: ::0 - # tiarra が、 001 や 002 や、 recent log を送信するときなどに使う prefix + # Tiarra が、 001 や 002 や、 recent log を送信するときなどに使う prefix # を指定します。 hostname や fqdn っぽいものを指定すると良いかもしれません。 # デフォルトは tiarra です。普通変える必要はありません。 #sysmsg-prefix: tiarra @@ -263,9 +263,15 @@ # そのアドレス、ポート、(必要なら)パスワードを定義します。 # ----------------------------------------------------------------------------- ircnet { - # サーバーのホストとポート。省略不可。 - host: irc.nara.wide.ad.jp - port: 6663 + # サーバーのホストとポートを指定。複数行可。(host/port が指定されていない場合は)省略不可。 + # 同じサーバの複数のポート指定は順番に試すので、大量に書かない方がよい。 + server: irc.nara.wide.ad.jp 6662 6663 + server: irc.fujisawa.wide.ad.jp 6661 6664 + + # サーバーのホストとポート。(server が指定されていない場合は)省略不可。 + # server を指定した場合は server が優先されます。 + #host: irc.nara.wide.ad.jp + #port: 6663 # general/userで設定したユーザ名を使わずに、各ネットワークで独自のユーザ名を使用する事も可能。 # 省略されたら当然、general/userで設定したものが使われる。 @@ -402,7 +408,7 @@ - CTCP::DCC::RewriteAddress { # クライアントが送信した CTCP DCC のアドレスを変換する。 - # CTCP DCC に指定されているアドレスを、 tiarra で取得したものに + # CTCP DCC に指定されているアドレスを、 Tiarra で取得したものに # 書き換えます。(EXPERIMENTAL) # # IPv4 のみサポートしています。 @@ -429,7 +435,7 @@ client-socket { # クライアントソケットのリモートアドレスを取ります。 - # client [this address]<-> tiarra <-> server + # client [this address]<-> Tiarra <-> server } dns { @@ -449,16 +455,16 @@ # リゾルバの選び方 # - # * tiarra を動作させているサーバとインターネットの間にルータ等があり、 + # * Tiarra を動作させているサーバとインターネットの間にルータ等があり、 # グローバルアドレスがない場合 # *-socket は役に立ちません。 http を利用してください。 # 適当な DDNS を持っていればdns も良いでしょう。 # - # * tiarra がレンタルサーバなどLAN上にないサーバで動作している場合 + # * Tiarra がレンタルサーバなどLAN上にないサーバで動作している場合 # server-socket, http は役に立ちません。 # client-socket がお勧めです。 # - # * tiarra がLAN上にあり、グローバルアドレスのついているホストで + # * Tiarra がLAN上にあり、グローバルアドレスのついているホストで # 動作している場合 # client-socket は役に立ちません。 # server-socket がお勧めです。 @@ -693,8 +699,17 @@ # channel: others * # この例では、#IRC談話室@ircnetのログはIRCDanwasitu/%Y.%m.%d.txtに、 # それ以外(privも含む)のログはothers/%Y.%m.%d.txtに保存される。 + # #(channel) はチャンネル名に展開される。 + # (古いバージョンだと展開されずにそのままディレクトリ名になってしまいます。) channel: priv priv - channel: others * + channel: #(channel) * + #channel: others * + + # ファイル名のエンコーディング. + # 指定可能な値は, utf8, sjis, euc, jis, ascii. + # ascii は実際には utf8 と同等で8bit部分が全てquoted-printableされる. + # デフォルトはWindowsではsjis, それ以外では utf8. + #filename-encoding: utf8 } - Log::Recent { diff -urN tiarra-20080510/tiarra tiarra-20090206/tiarra --- tiarra-20080510/tiarra 2008-05-11 00:25:24.000000000 +0900 +++ tiarra-20090206/tiarra 2009-02-09 22:30:09.000000000 +0900 @@ -5,9 +5,9 @@ # This is free software; you can redistribute it and/or modify it # under the same terms as Perl itself. # ----------------------------------------------------------------------------- -# $Id: tiarra 11365 2008-05-10 14:58:28Z topia $ +# $Id: tiarra 28572 2009-01-17 18:00:30Z topia $ # ----------------------------------------------------------------------------- -require 5.006; +require 5.008; use strict; use warnings; use File::Basename; @@ -235,7 +235,8 @@ eval { RunLoop->shared_loop->run; }; if ($@) { - die "Tiarra aborted: $@\n"; + print STDERR "Tiarra aborted: $@\n"; + return 98; } else { print "Tiarra successfully finished.\n"; } @@ -284,12 +285,12 @@ my $child_pid = fork; if ($child_pid == 0) { # 子プロセス - $boot->(); + return $boot->(); } elsif (!defined $child_pid) { print "Tiarra: fork() failed.\n"; } } else { - $boot->(); + return $boot->(); } return 0; } @@ -348,7 +349,7 @@ push @lines, "Environment Information:"; push @lines, " - Perl $Config{version} built for $Config{archname}"; push @lines, "Optional Modules:"; - push @lines, map " $_", Tiarra::OptionalModules->repr_modules; + push @lines, map " $_", Tiarra::OptionalModules->repr_modules(&debug_mode); push @lines, "Bundle Modules:"; foreach my $mod (qw(Unicode::Japanese enum)) { my $modfile = $mod . '.pm'; @@ -371,6 +372,7 @@ } $SIG{HUP} = \&handle_reload; $SIG{USR1} = \&handle_conf_reload; + $SIG{PIPE} = 'IGNORE'; } sub handle_exit {