/*
 *  cocousagi.c
 *      こうさぎAPI を使って生成した記事を blog に投稿するプログラム
 *
 *      written by H.Tsujimura      2 Jul 2004
 *      last updated:              15 Feb 2008  Ver 3.10
 *
 * History:
 * $Log: /makeRSS/cocousagi/cocousagi.c $
 * 
 * 189   09/03/24 18:56 Tsujimura543
 * Windows Live Spaces 拡張版 metaWeblog API 採用ブログの
 * 「カテゴリ指定」付き投稿に対応
 * 
 * 188   09/03/23 16:23 Tsujimura543
 * ウェブリブログの XML-RPC API 経由ではない方の諸々を削除
 * 
 * 187   09/03/20 22:26 Tsujimura543
 * はてなダイアリーの Atom API での投稿に対応
 * 
 * 186   09/03/20 17:43 Tsujimura543
 * ウェブリブログの XML-RPC API での投稿に対応
 * 
 * 185   08/09/10 23:12 Tsujimura543
 * loginExciteBlog(), postExciteBlog() を xmlRPC.dll から移管
 * 
 * 184   08/09/10 21:57 Tsujimura543
 * loginEchoo(), postEchoo() を xmlRPC.dll から移管
 * 
 * 183   08/09/10 18:04 Tsujimura543
 * loginRakuten(), postRakuten() を xmlRPC.dll から移管
 * 
 * 182   08/09/09 23:56 Tsujimura543
 * loginWebry(), postWebry() を xmlRPC.dll から移管
 * 
 * 181   08/09/09 22:49 Tsujimura543
 * loginMixi() と postMixi() を xmlRPC.dll から移管
 * 
 * 180   08/09/09 22:19 Tsujimura543
 * loginFC2NETWORK() と postFC2NETWORK() を xmlRPC.dll から移管
 * 
 * 179   08/09/09 21:42 Tsujimura543
 * loginKinugasa() と postKinugasa() を xmlRPC.dll から移管
 * 
 * 178   08/09/09 19:58 Tsujimura543
 * loginColors() と postColors() を xmlRPC.dll から移管
 * 
 * 177   08/02/15 23:07 Tsujimura543
 * 3.10 版
 * 
 * 176   08/02/15 23:03 Tsujimura543
 * similaritySearch() を AWS3.0 ベースのものから ECS4.0 ベースへ移行
 * 
 * 175   07/12/05 23:19 Tsujimura543
 * unbase64() の引数追加に伴う修正を実施
 * 
 * 174   07/11/30 2:31 Tsujimura543
 * (1) Jugem、ヤプログ!、ドリコムブログの XML-RPC API 経由ではない方の
 *     諸々を削除
 * (2) しまにてぃ内日記関連の諸々を削除 (2007年11月30日でサービス終了)
 * 
 * 173   07/10/29 20:06 Tsujimura543
 * XML-RPC API 経由での ヤプログ! への投稿に対応
 * 
 * 172   07/09/04 22:02 Tsujimura543
 * setVerbose() の第2引数追加に伴う変更
 * 
 * 171   07/03/06 4:03 Tsujimura543
 * setKeywordLink() の引数追加に伴う修正を実施
 * 
 * 170   07/02/24 2:05 Tsujimura543
 * JUGEM と ドリコムブログ の XML-RPC API 経由での投稿に対応
 * 
 * 169   07/02/14 21:45 Tsujimura543
 * 投稿リトライ処理を修正
 * 
 * 168   07/02/14 16:46 Tsujimura543
 * XML-RPC API および Atom API での投稿時のタイムアウト時間を
 * 長めにしてみる
 * 
 * 167   07/01/23 15:33 Tsujimura543
 * (1) 楽天ブログの新しい管理画面に対応
 * (2) 楽天ブログにカテゴリ指定して投稿できるようにした
 * 
 * 166   06/10/13 17:21 Tsujimura543
 * コメントを修正
 * 
 * 165   06/10/13 17:20 Tsujimura543
 * ウェブリブログに投稿する場合、「ここうさぎ」起動時のパラメータでテンプ
 * レートIDを指定しての投稿ができるようにした
 * 
 * 164   06/10/02 20:13 Tsujimura543
 * blog_info_tbl[] の実体を blogInfo.c へ分離
 * 
 * 163   06/10/02 20:01 Tsujimura543
 * COUSAGI_INFO の定義も cocousagi.h へ移動
 * 
 * 162   06/10/02 19:59 Tsujimura543
 * (1) cocousagi.c から JUGEM 特有の処理を分離し、jugem.c へ移譲
 * (2) 構造体の宣言、プロトタイプ宣言などを cocousagi.h へ分離
 * 
 * 161   06/10/02 19:50 Tsujimura543
 * cocousagi.c から exciteブログ 特有の処理を分離し、excite.c へ移譲
 * 
 * 160   06/10/02 19:34 Tsujimura543
 * cocousagi.c から ヤプログ! 特有の処理を分離し、yaplog.c へ移譲
 * 
 * 159   06/10/02 19:30 Tsujimura543
 * cocousagi.c から ドリコムブログ 特有の処理を分離し、drecom.c へ移譲
 * 
 * 158   06/10/02 19:22 Tsujimura543
 * cocousagi.c から mixi 特有の処理を分離し、mixi.c へ移譲
 * 
 * 157   06/10/02 19:18 Tsujimura543
 * cocousagi.c から キヌガサ 特有の処理を分離し、kinugasa.c へ移譲
 * 
 * 156   06/10/02 19:11 Tsujimura543
 * cocousagi.c から FC2NETWORK 特有の処理を分離し、fc2network.c へ移譲
 * 
 * 155   06/10/02 19:06 Tsujimura543
 * cocousagi.c から エコログ 特有の処理を分離し、echoo.c へ移譲
 * 
 * 154   06/10/02 19:01 Tsujimura543
 * cocousagi.c から ウェブリブログ 特有の処理を分離し、webry.c へ移譲
 * 
 * 153   06/10/02 18:54 Tsujimura543
 * cocousagi.c から 楽天ブログ 特有の処理を分離し、rakuten.c へ移譲
 * 
 * 152   06/10/02 18:39 Tsujimura543
 * cocousagi.c から COLORS 特有の処理を分離し、colors.c へ移譲
 * 
 * 151   06/10/02 18:28 Tsujimura543
 * cocousagi.c から OpenPNE 特有の処理を分離し、openPNE.c へ移譲
 * 
 * 150   06/10/02 18:20 Tsujimura543
 * cocousagi.c から しまにてぃ 特有の処理を分離、simanity.c へ移譲
 * 
 * 149   06/10/02 18:12 Tsujimura543
 * cocousagi.c から livedoor Blog 特有の処理を分離、livedoor.c へ移譲
 * 
 * 148   06/10/02 13:43 Tsujimura543
 * livedoor Blog の再構築処理を修正 (URL が想定しているものより長くなる
 * 場合があることが判明。URL の格納領域サイズを増やした)
 * 
 * 147   06/09/19 11:50 Tsujimura543
 * powered by cocousagi のリンク先を 3.00 版の配布ページの URL に変更
 * 
 * 146   06/09/15 21:17 Tsujimura543
 * OpenPNE / しまにてぃ への投稿時のタイトルの長さ調整処理をはずしてみた
 * 
 * 145   06/09/15 20:40 Tsujimura543
 * loginSimanity() の引数を減らした (日記を書くだけなら必要ないため)
 * 
 * 144   06/09/15 20:34 Tsujimura543
 * しまにてぃ に対応してみた
 * 
 * 143   06/09/15 20:07 Tsujimura543
 * OpenPNE に対応してみた
 * 
 * 142   06/09/14 19:49 Tsujimura543
 * Vox に対応してみた
 * 
 * 141   06/09/12 12:27 Tsujimura543
 * CP932 ではない文字が混入しているとの指摘(Visual Studio 2005)に基づき、
 * ソースコードの整理を実施
 * 
 * 140   06/09/11 18:28 Tsujimura543
 * 楽天広場 → 楽天ブログ
 * 
 * 139   06/09/08 21:51 Tsujimura543
 * MSN Spaces → Windows Live Spaces への名称変更に対応
 * 
 * 138   06/09/07 18:58 Tsujimura543
 * So-net blog の場合、Atom API 経由でのパスワード(のエンコード結果)が
 * 64バイトより大きくなることがあることが判明。パスワード格納領域のサイズ
 * を増やした
 * 
 * 137   06/09/06 17:06 Tsujimura543
 * アメーバブログの記事投稿時、タイトルが48文字より長いと投稿に失敗する
 * ことが判明。48文字より長い場合は、短いタイトルに差し替えるようにした
 * 
 * 136   06/09/04 14:35 Tsujimura543
 * COLORS への投稿時、記事タイトルが40バイトより長い場合は警告を
 * 出して終了していたのを、短いタイトルを生成して投稿するように変更
 * 
 * 135   06/09/04 13:33 Tsujimura543
 * バッファオーバーランを防止するため、setUpReceiveBuffer() を導入。
 * 
 * 134a  06/09/03 20:20 tsupo
 * (1) cookie まわりを修正
 * (2) Visuau Studio 2005 でビルドしたときに検出される warning を元に
 *     ソースを修正 (Visual Studio 6 でも Visual Studio 2005 でも。同
 *     一ソースのまま、問題なくビルドできるようにする)
 * 
 * 134   06/08/31 13:16 Tsujimura543
 * 強制代筆モードを実装
 * 
 * 133   06/08/29 21:19 Tsujimura543
 * 「記事生成」APIのみ復活とのことなので、「俳句生成」API関連の処理は
 * USE_HAIKU が #define されているときのみ有効という形にした
 * 
 * 132   06/07/26 16:36 Tsujimura543
 * ドリコムブログへ投稿する処理を修正
 * 
 * 131   06/05/31 15:04 Tsujimura543
 * 代理投稿時に必要に応じて <br /> を付加する処理を入れた
 * 
 * 130   06/05/30 13:21 Tsujimura543
 * autoMode 有効時の動作を修正 (必要に応じて<pre>〜</pre>でbodyを
 * 囲むようにした)
 * 
 * 129   06/03/15 21:58 Tsujimura543
 * livedoor Blog のエンドポイント URL を ATOMURL_LIVEDOOR_200507 に固定
 * 
 * 128   06/03/09 19:28 Tsujimura543
 * マイプロフィール関連の処理を完全に削除
 * 
 * 127   06/03/09 19:20 Tsujimura543
 * cocousagi.c 自体は変更なし (xmlRPC.dll の変更のみ)
 * (1) 2006年3月1日の「ウェブリブログ」リニューアルに対応
 * (2) 2006年3月6日の「ヤプログ!」リニューアルに対応
 * (3) エコログの新しい login シーケンスに対応 (いつから変わったのかは不明)
 * 
 * 126   06/02/28 14:17 Tsujimura543
 * MSN Spaces に対応
 * 
 * 125   06/01/27 20:34 Tsujimura543
 * livedoor Blog の再構築処理を追加した
 * 
 * 124   06/01/26 14:51 Tsujimura543
 * アメーバブログの Atom API に暫定対応
 * 
 * 123   06/01/25 19:20 Tsujimura543
 * excite Blog のログイン処理、投稿処理を変更 (引数を追加)
 * 
 * 122   06/01/04 18:23 Tsujimura543
 * COLORS 向け投稿処理を fix
 * 
 * 121   05/12/27 20:26 Tsujimura543
 * 投稿失敗時は「投稿に失敗しました。」と表示するようにした
 * 
 * 120   05/12/27 19:21 Tsujimura543
 * COLORS のダイアリーにも投稿できるようにしてみた
 * 
 * 119   05/12/06 17:44 Tsujimura543
 * 代筆時のファイルのやりとりを w/r から w+t/r+b に変更(Windowsの場合)
 * [投稿結果に余分なゴミがつくことがある現象に対する対策]
 * 
 * 118   05/11/28 15:07 Tsujimura543
 * ココログフリーと goo Blog に対応
 * (ココログフリーは動作確認済み、goo Blog はアカウントを持っていないので
 * 動作確認はしていない)
 * 
 * 117   05/09/21 22:37 Tsujimura543
 * RSA復号結果格納領域の解放を free() から freeRSAbuffer() に変更
 * 
 * 116   05/09/21 11:37 Tsujimura543
 * 設定ファイル中のパスワードの暗号化に対応
 * 
 * 115   05/08/17 15:19 Tsujimura543
 * (1) postEntryOnAtomAPI() と editEntryOnAtomAPI() の引数追加に対応
 * (2) エディタ起動時のデフォルトの指定を notepad から notepad.exe にする
 * 
 * 114   05/08/11 14:02 Tsujimura543
 * isPostingOK() の連続投稿調査時の記事タイトルを utf2sjis() する処理で
 * NULL が返ってきたときに落ちてしまう不具合を修正
 * 
 * 113   05/08/10 16:04 Tsujimura543
 * lcc-win32 でもコンパイルできるように、ヘッダファイルのインクルード順序
 * を変更
 * 
 * 112   05/07/20 15:42 Tsujimura543
 * livedoor Blog の新旧エンドポイントURL使い分け関連処理を修正
 * 
 * 111   05/07/20 15:17 Tsujimura543
 * livedoor Blog の新エンドポイントURLで試してみて駄目なら、旧エンドポイン
 * トURLを使うようにしてみた
 * 
 * 110   05/07/19 18:56 Tsujimura543
 * So-net blog に対応
 * 
 * 109   05/07/13 18:43 Tsujimura543
 * アメーバブログの文字コードの変更に対応する作業に漏れがあったので、
 * 引き続き対応作業を実施した
 * 
 * 108   05/07/13 17:34 Tsujimura543
 * アメーバブログの文字コードの変更、RSS の URL の再変更に対応
 * 
 * 107   05/04/27 16:55 Tsujimura543
 * ココログで取得したカテゴリ名の文字化けが回復できない場合でも、
 * 「カテゴリ指定なし」で投稿できるようにした
 * 
 * 106   05/04/21 19:12 Tsujimura543
 * editForRakuten() を追加し、楽天広場では使えない属性を除去するようにした
 * 
 * 105   05/04/21 16:43 Tsujimura543
 * 楽天広場に対応。投稿できることを確認
 * 
 * 104   05/04/20 15:33 Tsujimura543
 * 代筆投稿時は「俳句を詠ませる Blog Pet API」を呼び出すのを抑制するように
 * した
 * 
 * 103   05/04/14 15:45 Tsujimura543
 * FC2BLOG に対応。投稿できることを確認
 * 
 * 102   05/04/05 10:27 Tsujimura543
 * アメーバブログの RSS の URL 変更に伴う修正
 * 
 * 101   05/04/01 21:06 Tsujimura543
 * 2.23 版
 * 
 * 100   05/04/01 21:04 Tsujimura543
 * (1) アメブロのリニューアル後、「ここうさぎウィザード」で指定したカテゴリ
 *     が反映されなくなってしまった不具合を修正
 * (2) ヤプログ! の login 手順変更に伴う xmlRPC.dll の修正の影響が他に及ば
 *     ないことを確認
 * 
 * 99    05/04/01 16:53 Tsujimura543
 * アメーバブログのリニューアルに伴う仕様変更(XML-RPC API の廃止、
 * 管理画面の変更)に対応
 * 
 * 98    05/03/29 15:02 Tsujimura543
 * FC2NETWORK 向け投稿処理での改行の扱いを調整
 * 
 * 97    05/03/29 14:58 Tsujimura543
 * FC2NETWORK 向け投稿処理を修正
 * 
 * 96    05/03/29 14:16 Tsujimura543
 * (1) Windows 98 で(Windows 95でも?)発生している現象(notepad を終了しない
 *     うちにどんどん処理が先に進んでいく)に対処
 * (2) FC2NETWORK内の日記に対応
 * 
 * 95    05/03/25 21:47 Tsujimura543
 * JUGEMの投稿処理を引数でカテゴリ名の指定をできるようにした
 * 
 * 94    05/03/25 20:48 Tsujimura543
 * exciteブログの投稿処理を引数でカテゴリ名の指定をできるようにした
 * 
 * 93    05/03/25 19:50 Tsujimura543
 * ヤプログの投稿処理を引数でカテゴリ名の指定をできるようにした
 * 
 * 92    05/03/25 19:17 Tsujimura543
 * エコログの投稿処理を引数でカテゴリ名の指定をできるようにした
 * 
 * 91    05/03/25 15:06 Tsujimura543
 * ウェブリブログの投稿処理を引数でカテゴリ名(テーマ名)の指定をできるよう
 * にした
 * 
 * 90    05/03/16 16:46 Tsujimura543
 * 指定したカテゴリに正しく投稿されないことがある不具合に対処
 * 
 * 89    05/03/14 19:26 Tsujimura543
 * 代筆機能を追加
 * 
 * 88    05/03/08 20:36 Tsujimura543
 * 「SweetBox Blog」に記述を統一
 * 
 * 87    05/03/08 17:06 Tsujimura543
 * SweetBox Blog に対応
 * 
 * 86    05/03/07 23:23 Tsujimura543
 * NetLaputa と 269g に対応
 * 
 * 85    05/02/21 22:53 Tsujimura543
 * アメーバブログとウェブリブログをアフィリエリト可能ブログとして扱うよう
 * に変更
 * 
 * 84    05/02/21 22:45 Tsujimura543
 * ヤプログとドリコムの記事末尾に埋め込む絵文字をペットの種類に合わせて
 * 変えるようにした
 * 
 * 83    05/02/21 14:31 Tsujimura543
 * アメーバブログでも NewsHandler 互換 XML-RPC API が使えることを確認。
 * 試験的に XML-RPC API を使って投稿する処理を適用してみた。
 * 
 * 82    05/02/21 13:37 Tsujimura543
 * Newshandler でも「連続投稿チェック」機能が働くようにした
 * 
 * 81    05/02/21 13:16 Tsujimura543
 * NewsHandler もいつの間にか MovableType 互換 API をサポートする
 * ようになっていたので、カテゴリ関連の処理を Movable Type 互換 API
 * 相当として扱うようにした
 * 
 * 80    05/02/18 20:53 Tsujimura543
 * 2.00 版
 * 
 * 79    05/02/18 17:43 Tsujimura543
 * 「こうさぎ」から「BlogPet」への移行に伴う修正の第3弾
 *   -C オプションを追加
 * 
 * 78    05/02/18 17:24 Tsujimura543
 * 「こうさぎ」から「BlogPet」への移行に伴う修正の第2弾
 *   投稿するカテゴリを BlogPet の種別に合わせて変えるようにした
 * 
 * 77    05/02/18 16:51 Tsujimura543
 * 「こうさぎ」から「BlogPet」への移行に伴う修正の第1弾
 *  (1) BlogPet の種別を選択・設定可能にした
 *  (2) 投稿記事の内容を BlogPet の種別に合わせて変えるようにした
 *  (3) 投稿記事に powered by cocousagi の文字列を埋め込むようにした
 * 
 * 76    04/11/29 19:10 Tsujimura543
 * 1.50 版
 * 
 * 75    04/11/29 19:03 Tsujimura543
 * マイプロフィール関連関数をコメントアウト
 * 
 * 74    04/11/29 18:44 Tsujimura543
 * Seesaa Blog への投稿(XML-RPC API リクエスト)を https で行なうようにした
 * 
 * 73    04/11/29 18:30 Tsujimura543
 * アマゾンのおすすめ商品関連処理を修正
 * 
 * 72    04/11/29 13:26 Tsujimura543
 * コンソール入力関連関数を分離、xmlRPC.dll へ移管
 * 
 * 71    04/11/29 11:42 Tsujimura543
 * blog 種別選択画面を 80x23 以内におさまるように修正 (blog種別増加に対応)
 * 
 * 70    04/11/26 22:15 Tsujimura543
 * ウェブリブログでは記事の題名が50バイトより大きいと失敗することが
 * 判明。対策を実施 (1.50 RC1 版)
 * 
 * 69    04/11/26 21:47 Tsujimura543
 * 不要になった変数を削除
 * 
 * 68    04/11/26 21:39 Tsujimura543
 * ウェブリブログに投稿できることを確認
 * 
 * 67    04/11/26 13:19 Tsujimura543
 * エコログに投稿できることを確認
 * 
 * 66    04/11/25 19:03 Tsujimura543
 * エコログ対応版 (動作未確認)
 * 
 * 65    04/11/17 19:19 Tsujimura543
 * 1.44版
 * 
 * 64    04/11/11 11:29 Tsujimura543
 * Windows 98 での動作確認終了 (1.43 正式版)
 * 
 * 63    04/11/10 11:03 Tsujimura543
 * Windows 98 環境だと、常に「関連書籍は見つかりませんでした」になってしま
 * う現象に暫定対処(根本的な原因は「ここうさぎウィザード」の方にあると思わ
 * れる)
 * (1.43 RC4 版)
 * 
 * 62    04/11/08 10:25 Tsujimura543
 * </dt>の挿入位置が間違っていたのを修正。
 * (1.43 RC3 版)
 * 
 * 61    04/11/05 17:55 Tsujimura543
 * ドリコムブログも「おすすめ」指定無視対象(アフィリエイト禁止)扱いにした
 * (1.43 RC2 版)
 * 
 * 60    04/11/05 15:33 Tsujimura543
 * (1) ヤプログ、ドリコムブログで記事本文が長いと投稿に失敗することが
 *     判明。対策を組み込む。 
 * (2) exciteブログを「おすすめ」指定無視対象にした
 *     (アフィリエイトが禁止されているため)
 * 
 * 59    04/11/04 18:12 Tsujimura543
 * いったん、fix (1.43 RC1 版)
 * 
 * 58    04/11/02 22:00 Tsujimura543
 * おもちゃも書籍も「おすすめ」が見つからない場合は、「すべての商品」で
 * 検索をかけるようにした
 * 
 * 57    04/11/02 20:47 Tsujimura543
 * アマゾンの「おもちゃ&ホビー」の検索に対応
 * 
 * 56    04/11/02 18:01 Tsujimura543
 * (1) 同一サイトに複数blogを開設している場合のblogID決定処理を修正
 * (2) MovableType API にて取得したカテゴリの数が0のときにアプリケーション
 *     エラーになる不具合に対処
 * 
 * 55    04/11/01 20:09 Tsujimura543
 * 警告レベル4での warning を 0 にする
 * 
 * 54    04/11/01 19:54 Tsujimura543
 * 『「こうさぎ」からのおすすめ』を試験的に実装
 * 
 * 53    04/10/27 15:34 Tsujimura543
 * -V オプションを追加
 * 
 * 52    04/10/27 15:03 Tsujimura543
 * 「こうさぎの機嫌が悪いとき」に余分にメモリ解放しようとしていたバグを
 * 修正
 * 
 * 51    04/10/15 16:26 Tsujimura543
 * アメーバブログの管理画面のバグ改修に伴い、リンク埋め込みを復活させた
 * [一般公開版 1.42]
 * 
 * 50    04/10/15 15:22 Tsujimura543
 * NewsHandler の場合のみ、投稿日時がおかしな値になる現象に対処
 * (サーバ側の不具合であるが、「ここうさぎ」側で暫定対処しておく)
 * 
 * 49    04/10/14 14:21 Tsujimura543
 * 投稿者のIDとblogのオーナーのIDが別IDの場合に対応 (JUGEMの場合)
 * 
 * 48    04/10/13 16:32 Tsujimura543
 * -u, -U オプションを追加
 * 
 * 47    04/10/12 13:48 Tsujimura543
 * title と body の領域サイズを増やし、確保する場所をスタックからヒープに
 * 変更
 * 
 * 46    04/10/08 20:29 Tsujimura543
 * キヌガサ内日記対応
 * 
 * 45    04/09/29 13:37 Tsujimura543
 * 一般公開版 1.41
 *   (カテゴリ選択関連バグFix [xmlRPC.dll のみ修正])
 * 
 * 44    04/09/28 17:40 Tsujimura543
 * マイプロフィールを処理対象外とする [一般公開版 1.40]
 * 
 * 43    04/09/28 16:40 Tsujimura543
 * ドリコムブログ対応
 * 
 * 42    04/09/28 15:22 Tsujimura543
 * アメーバブログ対応
 * 
 * 41    04/09/17 16:10 Tsujimura543
 * エラーメッセージを追加
 * 
 * 40    04/09/13 15:03 Tsujimura543
 * 投稿に成功したかどうか、メッセージを出すようにした (投稿には成功
 * しているがリビルドには失敗している場合もあるため、「たぶん、投稿
 * に成功した」という曖昧なメッセージにしている)
 * 
 * 39    04/09/07 13:44 Tsujimura543
 * 「login に失敗しました。」というエラーメッセージを追加。
 * 
 * 38    04/09/07 13:35 Tsujimura543
 * mixi の日記に対応 (投稿できることを確認済み)
 * 
 * 37    04/09/06 14:22 Tsujimura543
 * "<![CDATA[ "や "]]>" という文字列が俳句中に出現した場合、再度俳句を取得
 * し直すようにした(取得再試行は最大3回まで実行)
 * 
 * 36    04/09/03 15:53 Tsujimura543
 * 一般公開版 1.3.1
 * 
 * 35    04/09/03 15:37 Tsujimura543
 * 一般公開候補版 1.3.1 RC1
 *   マイプロフィールに対応 (実際に投稿できることを確認)
 * 
 * 34    04/09/02 20:12 Tsujimura543
 * キーワードリンクで文字化けが発生した場合は、キーワードリンクなしの
 * 記事を投稿するようにした (文字化け発生原因は現時点では不明)
 * 
 * [     04/08/31 22:34 Tsujimura543      ]
 * [一般公開版 1.2.5 xmlRPC.dll のみの修正]
 *
 * 33    04/08/31 16:37 Tsujimura543
 * 一般公開版 1.2.4
 * 
 * 32    04/08/31 16:36 Tsujimura543
 * 記事中にuserName相当の文字列が存在しない場合、ニックネーム置換処理で
 * 記事本文の先頭にゴミが付いてしまう不具合を修正(化けた状態で「はてな
 * キーワードリンク」APIを呼び出すと化ける範囲が記事全文に及んでしまう)
 * 
 * 31    04/08/31 14:40 Tsujimura543
 * 一般公開準備
 * 
 * 30    04/08/31 14:03 Tsujimura543
 * ニックネーム置換処理修正 (置き換え対象のuserNameが記事中に複数個
 * 存在すると記事本文の先頭の方がおかしくなってしまう不具合に対処)
 * 
 * 29    04/08/30 19:29 Tsujimura543
 * 一般公開版 (1.2.3)
 * (1) 連続投稿制限を外すオプションを追加
 * (2) 「blogユーザ名」を「指定したニックネーム」に置換する機能を追加
 * 
 * 28    04/08/30 18:30 Tsujimura543
 * (1) 1週間に1回しか投稿しないように制限するはすが、60週間に1回しか投稿
 *     しないようになっていたバグを修正
 * (2) getRecentPostTitlesViaRSS() を追加。JUGEM、exciteブログ、ヤプログも
 *     連続投稿制限できるようにしてみた
 * 
 * 27    04/08/27 18:26 Tsujimura543
 * 一般公開版 (1.2.1)
 *   ヤプログに対応 (実際に投稿できることを確認)
 * 
 * 26    04/08/27 11:28 Tsujimura543
 * exciteブログ対応 (実際に投稿できることを確認済み)
 * 
 * 25    04/08/27 9:29 Tsujimura543
 * blog種別が newPostOnly のときの処理を修正
 *   (対話形式での使用時に blog ID の入力をする処理が抜けていた)
 * 
 * 24    04/08/25 16:59 Tsujimura543
 * JUGEM に対応 (XML-RPC API も Atom API も使わず、独自手法にて対応)
 * 
 * 23    04/08/24 16:37 Tsujimura543
 * 一般公開版 (1.1.4)
 * 
 * 22    04/08/24 14:44 Tsujimura543
 * 設定ファイルで unsupported を指定した場合、強制的に「blogユーザ名」、
 * 「blog投稿用パスワード」をクリアするようにした
 * 
 * 21    04/08/24 14:20 Tsujimura543
 * コメント修正、追加
 * 
 * 20    04/08/24 14:16 Tsujimura543
 * Blog種別に unsupported を追加
 * 
 * 19    04/08/24 11:24 Tsujimura543
 * 設定情報を静的に確保するのをやめ、動的に確保するようにした
 * 
 * 18    04/08/24 11:11 Tsujimura543
 * 設定ファイルで keywordLink: on と書いても、キーワードリンクが埋め込ま
 * れない不具合を修正
 * 
 * 17    04/08/20 16:52 Tsujimura543
 * 投稿先 blog が localhost 内に存在する場合に対応
 * 
 * 16    04/08/19 13:46 Tsujimura543
 * 一般公開版 (1.1.3)
 *   Windows 95 系の場合のキー入力処理を改善
 * 
 * 15    04/08/19 10:46 Tsujimura543
 * 一般公開版 (1.1.2) ← 結局、公開せず
 * 
 * 14    04/08/19 9:31 Tsujimura543
 * News-Handler および metaWeblog.newPost のみサポートしている blog
 * (「こうさぎ飼育箱」を含む)に対応
 * 
 * 13    04/08/19 8:52 Tsujimura543
 * blog_info_tbl[] に atomAPI を追加するのを忘れていた (→ 追加済み)
 * 
 * 12    04/08/17 18:50 Tsujimura543
 * コメントの誤字修正
 * 
 * 11    04/08/17 17:03 Tsujimura543
 * 一般公開版 (1.1.1)
 * 
 * 10    04/08/17 14:40 Tsujimura543
 * リファクタリングを実施
 * 
 * 9     04/08/17 12:25 Tsujimura543
 * blogger API, metaWeblog API のみサポートの blog への対応完了(動作は未確認)
 * 
 * 8     04/08/16 19:09 Tsujimura543
 * blogger API のみサポートしている blog、metaWeblog API のみサポートして
 * いる blog への対応を追加
 * 
 * 7     04/08/13 15:06 Tsujimura543
 * Atom API 経由での投稿でも「連続投稿抑止機能」が働くようにした
 * 
 * 6     04/08/13 13:55 Tsujimura543
 * 俳句詠唱の要否、キーワードリンク埋設の要否を対話型モードでも
 * 選択できるようにした
 * 
 * 5     04/08/13 10:56 Tsujimura543
 * (1) Movable Type への投稿動作確認完了
 * (2) 対話型でのパラメータ入力による動作確認完了
 * 
 * 4     04/08/13 9:48 Tsujimura543
 * Seesaa BLOG にて動作確認
 *  (投稿処理に時間がかかるようなので、タイムアウト時間の見直しが必要)
 * 
 * 3     04/08/12 21:18 Tsujimura543
 * ココログ以外にも対応(とりあえず、livedoor Blog にて動作確認成功)
 * 
 * 2     04/07/07 11:37 Tsujimura543
 * 一般公開版 (1.0.1)
 */

#ifdef  WIN32
#include <time.h>
#include <process.h>
#else
#include <sys/time.h>
#endif
#include "xmlRPC.h"
#include "atomAPI.h"
#include "amazon.h"
#include "encryptRSA.h"
#include "cocousagi.h"

#ifndef	lint
static char	*rcs_id =
"$Header: /makeRSS/cocousagi/cocousagi.c 189   09/03/24 18:56 Tsujimura543 $";
#endif

extern BLOG_INFO    blog_info_tbl[];


/*
 *  RSS を取得し、POSTINF構造体に情報を設定する
 */
int
getRecentPostTitlesViaRSS(
        BLOGKIND   blogType,        // (I) blog種別
        const char *userName,       // (I) ユーザ名
        const char *password,       // (I) パスワード
        int        *numberOfPosts,  // (I) 取得する記事情報の数
                                    // (O) 実際に取得した記事情報
                                    //     の数
        POSTINF    *postinf         // (O) 取得した記事情報
    )
{
    char    rssURL[MAX_URLLENGTH];  /* RSS の URL */
    int     numOfPost = 0;

    if ( !numberOfPosts || (*numberOfPosts <= 0) )
        return ( numOfPost );

    /* RSS の URL を決定 */
    switch ( blogType ) {
    case exciteBlog:    /* exciteブログ (RSS 2.0) */
        {
            char    *cookie = (char *)malloc( MAX_COOKIE_LEN );
            char    myBlogURL[MAX_URLLENGTH];
            char    eid[EID_LENGTH];

            myBlogURL[0] = NUL;
            if ( cookie ) {
                /* ユーザ名 → blog URL 変換 */
                memset( cookie, 0x00, MAX_COOKIE_LEN );
                if ( loginExciteBlog(userName, password, cookie, myBlogURL, eid ) )
                    sprintf( rssURL, "%s/index.xml", myBlogURL );
                free( cookie );
            }
            if ( myBlogURL[0] == NUL )
                return ( numOfPost );
        }
        break;

    case ameblo:        /* アメーバブログ (RSS 1.0) */
#ifdef  _BEFORE_2005_04_    /* 2005年3月31日まで */
        sprintf( rssURL, "http://ameblo.jp/rss.php?bid=%s", userName );
#else   
# ifdef  _BEFORE_2005_07_   /* 2005年4月1日から */
        sprintf( rssURL, "http://ameblo.jp/blog/rss.php?uid=%s", userName );
# else                      /* 2005年7月1日から */
        sprintf( rssURL, "http://ameblo.jp/%s/rss.html", userName );
 #endif /* _BEFORE_2005_07_ */
#endif  /* _BEFORE_2005_04_ */
        break;
    case echoo:         /* エコログ */
        {
            char    *cookie = (char *)malloc( MAX_COOKIE_LEN );
            char    myBlogURL[MAX_URLLENGTH];
            char    *p;

            myBlogURL[0] = NUL;
            if ( cookie ) {
                /* ユーザ名 → blog URL 変換 */
                memset( cookie, 0x00, MAX_COOKIE_LEN );
                if ( loginEchoo( userName, password, cookie, myBlogURL ) ) {
                    p = strrchr( myBlogURL, '/' );
                    if ( p ) {
                        sprintf( rssURL,
                                 "http://echoo.yubitoma.or.jp/rdf/%s.rdf",
                                 p + 1 );
                    }
                }
                free( cookie );
            }
            if ( myBlogURL[0] == NUL )
                return ( numOfPost );
        }
        break;

    case rakuten:   /* 楽天広場 (RSS 1.0) */
        {
            char    *cookie = (char *)malloc( MAX_COOKIE_LEN );
            char    myBlogURL[MAX_URLLENGTH];

            myBlogURL[0] = NUL;
            if ( cookie ) {
                /* ユーザ名 → blog URL 変換 */
                memset( cookie, 0x00, MAX_COOKIE_LEN );
                if ( loginRakuten(userName, password, cookie, myBlogURL) ) {
                    sprintf( rssURL, "http://api.plaza.rakuten.ne.jp/%srss/",
                             myBlogURL + 27 );
                             /* 27 ← strlen("http://plaza.rakuten.co.jp/") */
                }
                free( cookie );
            }
            if ( myBlogURL[0] == NUL )
                return ( numOfPost );
        }
        break;

    default:
        rssURL[0] = NUL;
        break;
    }
    if ( rssURL[0] == NUL )
        return ( numOfPost );

    /* RSS ファイルを取得し、その内容を解析し、記事情報を得る */
    numOfPost = getRecentPostTitlesFromRSS( rssURL, numberOfPosts, postinf );

    return ( numOfPost );
}

/*
 *  キーワード抽出
 */
#ifndef	iskanji1
#define	iskanji1(s)	((((s)>=0x81) && ((s)<=0x9f)) || \
			 (((s)>=0xe0) && ((s)<=0xfc)))
#endif
#ifndef	iskanji2
#define	iskanji2(s)	((((s)>=0x40) && ((s)<=0x7e)) || \
			 (((s)>=0x80) && ((s)<=0xfc)))
#endif

char    *
getKeyword( const char *body, char *keyword, int blogPetType )
{
    const char  *pp = blogPetTypeString( blogPetType );
    const char  *kana1  = "ァ";
    const char  *kana2  = "ヶ";
    const char  *kana3  = "ー";
    const char  *kanji1 = "亜";
    const char  *p = body;
    int         found    = 0;
    int         kanji    = 0;
    int         yokomoji = 0;
    int         idiom    = 0;

    /*
     *  漢字のみが3文字以上連続する場合、またはカタカナのみが4文字以上連続
     *  する場合、キーワードとみなして抽出する
     *  (いちばん最初に見つかったものをキーワードとする。見つからなかった場
     *   合は「こうさぎ」(あるいは「こねこ」、……、「こねずみ」)を仮のキー
     *   ワードとして返す)
     *  [将来的には、キーワードを複数抽出し、最も長いものを選んで返すように
     *   する予定]
     */

    while ( *p ) {
        if ( !kanji && iskanji1( (unsigned char)*p ) ) {
            kanji = 1;
        }
        else if ( kanji && iskanji2( (unsigned char)*p ) ) {
            kanji = 0;
            if ( ((*(p - 1) >= kana1[0]) && (*(p - 1) <= kana2[0])) ||
                 ((*(p - 1) == kana3[0]) && (*p == kana3[1])      )    ) {
                yokomoji++;
                idiom    = 0;
            }
            else if ( *(p - 1) >= kanji1[0] ) {
                idiom++;
                yokomoji = 0;
            }
            else {
                if ( yokomoji >= 4 ) {
                    found = 1;
                    strncpy( keyword,
                             p - (((yokomoji + 1) * 2) - 1),
                             yokomoji * 2 );
                    keyword[yokomoji * 2] = NUL;
                    break;
                }
                if ( idiom >= 3 ) {
                    found = 1;
                    strncpy( keyword,
                             p - (((idiom + 1) * 2) - 1),
                             idiom * 2 );
                    keyword[idiom * 2] = NUL;
                    break;
                }
                yokomoji = 0;
                idiom    = 0;
            }
        }
        else {
            kanji    = 0;
            yokomoji = 0;
            idiom    = 0;
        }
        p++;
    }

    if ( !found || !strcmp( keyword, "エントリ" ) )
        strcpy( keyword, pp );

    return ( keyword );
}


/*
 *  コマンドライン引数・設定ファイル解析
 *      blog種別
 */
BLOGKIND
evalBlogType( const char *str )
{
    BLOG_INFO   *p;
    BLOGKIND    blogType = cocolog;

    for ( p = &blog_info_tbl[0]; p->blogToolName; p++ ) {
        if ( !strcmpi( str, p->blogTypeName ) ) {
            blogType = p->blogInfoTbl.blogType;
            break;
        }
    }

    return ( blogType );
}

/*
 *  コマンドライン引数・設定ファイル解析
 *      文字コード種別
 */
int
evalCharSet( const char *str )
{
    int charSet = CHARSET_UTF8;

    if ( !strcmpi( str, "UTF-8" ) )
        charSet = CHARSET_UTF8;
    else if ( !strcmpi( str, "ISO-2022-JP" ) )
        charSet = CHARSET_ISO2022JP;
    else if ( !strcmpi( str, "Shift_JIS" ) )
        charSet = CHARSET_SHIFTJIS;
    else if ( !strcmpi( str, "EUC-JP" ) )
        charSet = CHARSET_EUCJP;

    return ( charSet );
}

/*
 *  コマンドライン引数・設定ファイル解析
 *      BlogPet種別
 */
int
evalBlogPetType( const char *str )
{
    int blogPetType = BPTYPE_UNKNOWN;

    if ( !strcmpi( str, "usagi" ) )
        blogPetType = BPTYPE_COUSAGI;
    else if ( !strcmpi( str, "neko" ) )
        blogPetType = BPTYPE_CONEKO;
    else if ( !strcmpi( str, "inu" ) )
        blogPetType = BPTYPE_COINU;
    else if ( !strcmpi( str, "panda" ) )
        blogPetType = BPTYPE_COPANDA;
    else if ( !strcmpi( str, "nezumi" ) )
        blogPetType = BPTYPE_CONEZUMI;

    return ( blogPetType );
}

int
getEndpointURL( BLOGKIND blogType, char *endpointURL, int *charSet )
{
    BLOG_INFO   *p;
    int         ret = -1;

    for ( p = &blog_info_tbl[0]; p->blogToolName; p++ ) {
        if ( blogType == p->blogInfoTbl.blogType ) {
            if ( p->blogInfoTbl.xmlEndpoint )
                strcpy( endpointURL, p->blogInfoTbl.xmlEndpoint );
            else if ( p->blogInfoTbl.atomEndpoint )
                strcpy( endpointURL, p->blogInfoTbl.atomEndpoint );
            *charSet =  p->blogInfoTbl.charSet;
            ret = 0;
            break;
        }
    }

    return ( ret );
}


/*
 * 年月日時分秒 → 1970年1月1日0時0分0秒からの経過秒数 変換
 */
time_t
timeValue( int yy, int mm, int dd, int HH, int MM, int SS )
{
    static int  m[12] = { 31, 28, 31, 30, 31, 30,
                          31, 31, 30, 31, 30, 31 };
    time_t      t;

#define isLeapYear( y ) \
    (((y) % 4) == 0) && ((((y) % 100) != 0) || (((y) % 400) == 0))

    t = (((dd - 1) * 24 + HH) * 60 + MM) * 60 + SS;
    t -= 9 * 60 * 60; /* JST -> GMT */
    while ( yy >= 1970 ) {
        while ( --mm >= 1 ) {
            if ( (mm == 2) && isLeapYear( yy ) )
                t += (24 * 60 * 60);
            t += (m[mm - 1] * 24 * 60 * 60);
        }
        yy--;
        mm = 13;
    }
#undef isLeapYear

    return ( t );
}


/*
 *  設定情報初期化
 */

void
initParam( COUSAGI_INFO *cp )
{
    cp->blogPetType = BPTYPE_UNKNOWN;   /* BlogPet種別    */
    cp->blogType = BLOGKIND_COCOLOG;    /* blog種別       */
#ifdef  USE_HAIKU
    cp->useHaikuWords = -1; /* 俳句詠唱の要否             */
#endif
    cp->useHatenaLink = -1; /* キーワードリンク埋設の要否 */
    cp->useProxy      = 0;  /* proxy経由で通信するか否か  */
    cp->promptLevel   = 0;  /* 0: 全部キーボードから入力,
                               1: パスワードのみキーボードから入力,
                               2: 全部設定ファイルから取得          */
    cp->allowFreePost = 0;  /* 連続投稿の制限を外すか否か */
    cp->pause         = 0;  /* 終了時に pause するか否か  */
    cp->useSimilarity = 0;  /* おすすめを使うか否か       */
    cp->useWritingForPet = 0;   /* 代筆機能を使うか否か   */
    cp->forceWritingForPet = 0; /* 強制代筆モード OFF     */

    memset( cp->cousagiName,       0x00, MAX_NAMELEN      + 1 );
    memset( cp->cousagiUserName,   0x00, MAX_USERIDLEN    + 1 );
    memset( cp->cousagiPassword,   0x00, MAX_PASSWDLEN    + 1 );
    memset( cp->blogUserName,      0x00, MAX_USERIDLEN    + 1 );
    memset( cp->blogPassword,      0x00, MAX_PASSWDLEN    + 1 );
    memset( cp->blogID,            0x00, MAX_BLOGIDLENGTH + 1 );
    memset( cp->endpointURL,       0x00, MAX_URLLENGTH    + 1 );
    memset( cp->userNickName,      0x00, MAX_USERIDLEN    + 1 );
    memset( cp->settingFilename,   0x00, MAX_PATH             );
    memset( cp->amazonAssociateID, 0x00, MAX_NAMELEN      + 1 );
    memset( cp->categoryName,      0x00, MAX_CATIDLENGTH  + 1 );
    memset( cp->templateID,        0x00, MAX_CATIDLENGTH  + 1 );
    memset( cp->editorPath,        0x00, MAX_PATH             );

    cp->verboseFp = stderr;
}


/*
 *  コマンドライン引数から設定情報を構築する
 */

#define getStringValue( string )    \
    if ( argv[i][j + 1] )   \
        strcpy( (string), &argv[i][j + 1] );    \
    else if ( i + 1 < argc )    \
        strcpy( (string), argv[++i] );  \
    else    \
        break;  \
    j = strlen( argv[i] ) - 1;

int
getParamFromArgv( int          argc,      /* 引数の数           */
                  char         *argv[],   /* 引数の配列         */
                  COUSAGI_INFO *cp )      /* ここうさぎ設定情報 */
{
    int     ret = 0;
    int     i, j;
    char    temp[BUFSIZ + 1];   /* テンポラリ */
    BOOL    verbose = FALSE;
    char    *p;

    for ( i = 1; i < argc; i++ ) {
        if ( argv[i][0] == '-' ) {
            for ( j = 1; argv[i][j]; j++ ) {
                switch ( argv[i][j] ) {
                case 'f':
                    getStringValue( cp->settingFilename );
                    cp->promptLevel = 2;
                    break;

                case 'F':
                    if ( cp->settingFilename[0] )
                        cp->promptLevel = 1;
                    break;

#ifdef  USE_HAIKU
                case 'h':
                    if ( cp->useHaikuWords == -1 )
                        cp->useHaikuWords = 1;
                    else
                        cp->useHaikuWords = !(cp->useHaikuWords);
                    break;
#endif

                case 'l':
                    if ( cp->useHatenaLink == -1 )
                        cp->useHatenaLink = 1;
                    else
                        cp->useHatenaLink = !(cp->useHatenaLink);
                    break;

                case 'b':
                    getStringValue( cp->blogID );
                    break;

                case 'B':
                    getStringValue( temp );
                    cp->blogType = evalBlogType( temp );
                    if ( (strcmp( temp, "cocolog" ) != 0) &&
                         (cp->blogType == cocolog)           )
                        break;
                    if ( !(cp->endpointURL[0]) )
                        getEndpointURL( cp->blogType, cp->endpointURL,
                                        &(cp->charSet) );
                    break;

                case 'e':
                    getStringValue( cp->endpointURL );
                    break;

                case 'c':
                    getStringValue( temp );
                    cp->charSet = evalCharSet( temp );
                    break;

                case 'p':
                    cp->useProxy = !(cp->useProxy);
                    break;

                case 'A':
                    cp->allowFreePost = !(cp->allowFreePost);
                    break;

                case 'n':
                    getStringValue( cp->userNickName );
                    break;

                case 'v':   /* 調査用 */
                    verbose = !verbose;
                    break;

                case 'V':   /* carot 向け */
                    cp->pause = !(cp->pause);
                    break;

                case 'S':   /* おすすめ */
                    cp->useSimilarity = !(cp->useSimilarity);
                    if ( (argv[i][j+1] == ':') && ( argv[i][j+2] != NUL) ) {
                        strcpy( cp->amazonAssociateID, &(argv[i][j+2]) );
                        j = strlen( &(argv[i][j]) ) - 1;
                    }
                    break;

                case 'u':
                    /* ブログにloginするための ID、パスワード */
                    getStringValue( temp );
                    p = strchr( temp, ':' );
                    if ( p ) {
                        /* (1) ブログにloginするための ID */
                        strncpy( cp->blogUserName, temp, p - temp );
                        cp->blogUserName[p - temp] = NUL;

                        /* (2) ブログにloginするためのパスワード */
                        strcpy( cp->blogPassword, p + 1 );
                    }
                    else
                        strcpy( cp->blogUserName, temp );
                    break;

                case 'U':
                    /* BlogPetサーバにloginするための ID、パスワード */
                    getStringValue( temp );
                    p = strchr( temp, ':' );
                    if ( p ) {
                        strncpy( cp->cousagiUserName, temp, p - temp );
                        cp->cousagiUserName[p - temp] = NUL;
                        strcpy( cp->cousagiPassword, p + 1 );
                    }
                    else
                        strcpy( cp->cousagiUserName, temp );
                    break;

                case 't':   /* BlogPet種別 */
                    getStringValue( temp );
                    cp->blogPetType = evalBlogPetType( temp );
                    break;

                case 'C':   /* カテゴリ名 */
                    getStringValue( cp->categoryName );
                    break;

                case 'T':   /* テンプレートID */
                    getStringValue( cp->templateID );
                    break;

                case 'E':   /* 代筆機能 */
                    cp->useWritingForPet = !(cp->useWritingForPet);
                    if ( cp->useWritingForPet ) {
                        if ( argv[i][j + 1] )
                            strcpy( cp->editorPath, &argv[i][j + 1] );
                        else if ( (i + 1 < argc) && (argv[i + 1][0] != '-') )
                            strcpy( cp->editorPath, argv[++i] );
                        else
                            break;
                        j = strlen( argv[i] ) - 1;
                    }
                    break;

                case 'O':   /* 強制代筆モード */
                    cp->forceWritingForPet = !(cp->forceWritingForPet);
                    break;
                }
            }

            continue;
        }

        strcpy( cp->cousagiName, argv[i] );
    }

    if ( verbose ) {
        cp->verboseFp = fopen( "./cocousagi.log", "a" );
        if ( !(cp->verboseFp) )
            cp->verboseFp = stderr;
        else {
            time_t      t   = time( NULL );
            struct tm   *tm = localtime( &t );
            fprintf( cp->verboseFp,
                   "\n\n*** logging start: %d/%02d/%02d %02d:%02d:%02d ***\n",
                     tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
                     tm->tm_hour, tm->tm_min, tm->tm_sec );
        }
    }
    setVerbose( verbose, cp->verboseFp );

    return ( ret );
}

#undef  getStringValue


/*
 *  設定ファイルから設定情報を読み取る
 */

BOOL
getParamString( 
        const char *label,  /* 設定項目の名前(ラベル) */
        const char *input,  /* 解析対象の文字列       */
        char       *target, /* 設定先                 */
        BOOL       forcely  /* 強制的に設定するか否か */
    )
{
    BOOL    found = FALSE;
    size_t  len   = (label && *label) ? strlen( label ) : 0;

    if ( !strncmp( input, label, len ) ) {
        if ( !(target[0]) || forcely ) {
            /* 未設定の場合、または強制的に設定する場合 */
            input += len;
            while ( (*input == ' ') || (*input == '\t') )
                input++;
            strcpy( target, input );
        }
        found = TRUE;
    }

    return ( found );
}

BOOL
getParamBlogType(
        const char *label,    /* 設定項目の名前(ラベル) */
        const char *input,    /* 解析対象の文字列       */
        BLOGKIND   *blogType, /* 設定先                 */
        BOOL       forcely    /* 強制的に設定するか否か */
    )
{
    BOOL    found = FALSE;
    size_t  len   = (label && *label) ? strlen( label ) : 0;

    if ( !strncmp( input, label, len ) ) {
        if ( ((int)(*blogType) == 0) || forcely ) {
            /* 未設定の場合、または強制的に設定する場合 */
            input += len;
            while ( (*input == ' ') || (*input == '\t') )
                input++;
            *blogType = evalBlogType( input );
        }
        found = TRUE;
    }

    return ( found );
}

BOOL
getParamCharSet(
        const char *label,  /* 設定項目の名前(ラベル) */
        const char *input,  /* 解析対象の文字列       */
        int        *charSet /* 設定先                 */
    )
{
    BOOL    found = FALSE;
    size_t  len   = (label && *label) ? strlen( label ) : 0;

    if ( !strncmp( input, label, len ) ) {
        input += len;
        while ( (*input == ' ') || (*input == '\t') )
            input++;
        *charSet = evalCharSet( input );
        found = TRUE;
    }

    return ( found );
}

BOOL
getBlogPetType(
        const char *label,      /* 設定項目の名前(ラベル) */
        const char *input,      /* 解析対象の文字列       */
        int        *blogPetType /* 設定先                 */
    )
{
    BOOL    found = FALSE;
    size_t  len   = (label && *label) ? strlen( label ) : 0;

    if ( !strncmp( input, label, len ) ) {
        input += len;
        while ( (*input == ' ') || (*input == '\t') )
            input++;
        *blogPetType = evalBlogPetType( input );
        found = TRUE;
    }

    return ( found );
}

BOOL
getParamBOOLEAN( 
        const char *label,  /* 設定項目の名前(ラベル) */
        const char *input,  /* 解析対象の文字列       */
        BOOL       *target  /* 設定先                 */
    )
{
    BOOL    found = FALSE;
    size_t  len   = (label && *label) ? strlen( label ) : 0;

    if ( !strncmp( input, label, len ) ) {
        input += len;
        while ( (*input == ' ') || (*input == '\t') )
            input++;
        if ( !strncmpi( input, "on", 2 ) )
            *target = TRUE;
        else if ( !strncmpi( input, "off", 3 ) )
            *target = FALSE;
        found = TRUE;
    }

    return ( found );
}

char    *
decodeParam(
        char                *param,             /* 設定先                   */
        const unsigned char *encryptedString,   /* 設定すべき内容(要・復号) */
        const unsigned char *rsaString          /* RSA鍵対応文字列          */
    )
{
    char            *p;
    char            *decodedString;         /* 復号結果格納領域へのポインタ */
    unsigned char   targetString[BUFSIZ];   /* 復号対象バイト列             */
    int             len;                    /* 復号結果(文字列)の長さ       */
    size_t          sz = 0;

    p = unbase64( encryptedString, targetString, &sz );
    if ( p ) {
        len = decodeByRSA( &decodedString, rsaString,
                           (unsigned char *)targetString,
                           strlen( (char *)targetString ) );
        if ( len > 0 ) {
            strcpy( param, decodedString );
            freeRSAbuffer( decodedString );
        }
    }

    return ( param );
}

int
getParamFromFile( COUSAGI_INFO *cp )
{
    int     ret = 0;
    FILE    *fp;
    char    *p, *q, buf[BUFSIZ];
    BOOL    encrypted = FALSE;
    char    cousagiPasswordEncrypted[MAX_PASSWDLEN * 16];
    char    blogPasswordEncrypted[MAX_PASSWDLEN * 16];
    char    key[BUFSIZ];

    cousagiPasswordEncrypted[0] = NUL;
    blogPasswordEncrypted[0]    = NUL;
    key[0]                      = NUL;

    if ( ( fp = fopen( cp->settingFilename, "r" ) ) != NULL ) {
        while ( ( p = fgets( buf, BUFSIZ - 1, fp ) ) != NULL ) {
            while ( (*p == ' ') || (*p == '\t') )
                p++;
            q = p;
            while ( *q )
                q++;
            q--;
            while ( (q > p) &&
                    ((*q == '\n') || (*q == '\r') ||
                     (*q == ' ')  || (*q == '\t')    ) ) {
                *q-- = NUL;
            }

            if ( getParamString( "cousagiName:", p,
                                 cp->cousagiName, FALSE ) )
                continue;

            if ( getParamString( "cousagiUserName:", p,
                                 cp->cousagiUserName, TRUE ) )
                continue;

            if ( getParamString( "cousagiPassword:", p,
                                 cp->cousagiPassword, TRUE ) )
                continue;

            if ( getParamString( "cousagiPasswordEncrypted:", p,
                                 cousagiPasswordEncrypted, FALSE ) ) {
                encrypted = TRUE;
                continue;
            }

            if ( getParamString( "blogUserName:", p,
                                 cp->blogUserName, TRUE ) )
                continue;

            if ( getParamString( "blogPassword:", p,
                                 cp->blogPassword, TRUE ) )
                continue;

            if ( getParamString( "blogPasswordEncrypted:", p,
                                 blogPasswordEncrypted, FALSE ) ) {
                encrypted = TRUE;
                continue;
            }

            if ( getParamString( "userNickName:", p,
                                 cp->userNickName, TRUE ) )
                continue;

            if ( getParamString( "blogID:", p,
                                 cp->blogID, FALSE ) )
                continue;

            if ( getParamString( "endpointURL:", p,
                                 cp->endpointURL, FALSE ) )
                continue;

            if ( getParamBlogType( "blogType:", p,
                                   &(cp->blogType), TRUE ) )
                continue;

            if ( getParamCharSet( "charSet:", p,
                                  &(cp->charSet) ) )
                continue;

#ifdef  USE_HAIKU
            if ( getParamBOOLEAN( "haiku:", p,
                                  &(cp->useHaikuWords) ) )
                continue;
#endif

            if ( getParamBOOLEAN( "keywordLink:", p,
                                  &(cp->useHatenaLink) ) )
                continue;

            if ( getParamBOOLEAN( "allowFreePost:", p,
                                  &(cp->allowFreePost) ) )
                continue;

            if ( getParamBOOLEAN( "similarity:", p,
                                  &(cp->useSimilarity) ) )
                continue;

            if ( getParamBOOLEAN( "useWriting:", p,
                                  &(cp->useWritingForPet) ) )
                continue;

            if ( getParamBOOLEAN( "forceWriting:", p,
                                  &(cp->forceWritingForPet) ) )
                continue;

            if ( getParamString( "editorPath:", p,
                                  cp->editorPath, FALSE ) )
                continue;

            if ( getParamString( "associateID:", p,
                                  cp->amazonAssociateID, FALSE ) )
                continue;

            if ( getParamString( "categoryName:", p,
                                  cp->categoryName, FALSE ) )
                continue;

            if ( getParamString( "templateID:", p,
                                  cp->templateID, FALSE ) )
                continue;

            if ( getBlogPetType( "blogPetType:", p,
                                  &(cp->blogPetType) ) )
                continue;

            if ( getParamString( "encryptedKey:", p, key, FALSE ) ) {
                encrypted = TRUE;
                continue;
            }
        }

        fclose( fp );
    }
    else
        fputs( "設定ファイルが見つかりません。\n",
               cp->verboseFp ? cp->verboseFp : stderr );

    /* パスワードが暗号化されている場合、復号を実行 */
    if ( encrypted && key[0] ) {
        char            *p;
        unsigned char   rsaString[BUFSIZ];
        size_t          sz = 0;

        p = unbase64( (unsigned char *)key, (unsigned char *)rsaString, &sz );
        if ( p ) {
            if ( cousagiPasswordEncrypted[0] )
                decodeParam( cp->cousagiPassword,
                             (unsigned char *)cousagiPasswordEncrypted,
                             (unsigned char *)rsaString );
            if ( blogPasswordEncrypted[0] )
                decodeParam( cp->blogPassword,
                             (unsigned char *)blogPasswordEncrypted,
                             (unsigned char *)rsaString );
        }
    }

    /* ここまで来てもエンドポイントURL未設定のままなら、Blog種別からエンド */
    /* ポイントURL(と文字コード種別)を決定する(可能な場合のみ)             */
    if ( !(cp->endpointURL[0]) )
        getEndpointURL( cp->blogType, cp->endpointURL, &(cp->charSet) );

    return ( ret );
}


/*
 *  標準入力からパラメータ(文字列)を読み取る
 */

char    *
paddingSpaces( int index, int len )
{
    static char buf[48];
    static int  status = 0;
    static int  pre    = -1;
    int         i;
#define MAX_PADDING_LENGTH  38

    if ( pre > index )
        status = 0;

    if ( len >= MAX_PADDING_LENGTH ) {
        if ( status )
            fputs( "\n", stderr );
        buf[0] = '\n';
        buf[1] = NUL;
        status = 1;
    }
    else {
        if ( !status ) {
            for ( i = 0; i < MAX_PADDING_LENGTH - len; i++ )
                buf[i] = ' ';
            buf[i] = NUL;
        }
        else {
            buf[0] = '\n';
            buf[1] = NUL;
        }
    }

    status = !status;
    pre    = index;

    return ( buf );
}

int
inputBlogType( BLOGKIND   *blogType,    /* blog種別          */
               char       *endpointURL, /* エンドポイントURL */
               int        *charSet,     /* 文字コード種別    */
               const char *prompt )     /* プロンプト        */
{
    int         ret = 0;
    int         i, numOfBlogTools = 0;
    BLOG_INFO   *bp;
    int         done = FALSE;
    int         len;

    endpointURL[0] = NUL;
    do {
        numOfBlogTools = 0;
        fprintf( stderr, "%s\n", prompt );
        for ( bp = &blog_info_tbl[0], i = 1; bp->blogToolName; bp++, i++ ) {
            len = strlen( bp->blogToolName ) + 5;
            fprintf( stderr, "%s[%d] %s%s",
                     (i < 10) ? "  " : " ",
                     i, bp->blogToolName, paddingSpaces( i, len ) );
            numOfBlogTools++;
        }
        fputc( '\n', stderr );
        done = inputNumeric( &i, 1, numOfBlogTools );
    } while ( done == FALSE );

    ret = -1;
    if ( (i > 0) && (i <= numOfBlogTools) ) {
        i--;
        bp = blog_info_tbl;
        *blogType = bp[i].blogInfoTbl.blogType;
        if ( bp[i].blogInfoTbl.xmlEndpoint &&
             (bp[i].blogInfoTbl.xmlEndpoint[0] != NUL) )
            strcpy( endpointURL, bp[i].blogInfoTbl.xmlEndpoint );
        else if ( bp[i].blogInfoTbl.atomEndpoint &&
                  (bp[i].blogInfoTbl.atomEndpoint[0] != NUL) )
            strcpy( endpointURL, bp[i].blogInfoTbl.atomEndpoint );
        *charSet = bp[i].blogInfoTbl.charSet;
        ret = 0;
    }

    return ( ret );
}

int
inputBlogPetType( int        *blogPetType,  /* BlogPet種別 */
                  const char *prompt )      /* プロンプト  */
{
    int ret = 0;
    int done = FALSE;
    int bpType;

    do {
        bpType = BPTYPE_UNKNOWN;
        fprintf( stderr, "%s\n", prompt );
        fputs( " [1] こうさぎ\n", stderr );
        fputs( " [2] こねこ\n",   stderr );
        fputs( " [3] こいぬ\n",   stderr );
        fputs( " [4] こぱんだ\n", stderr );
        fputs( " [5] こねずみ\n", stderr );
        fputc( '\n', stderr );
        done = inputNumeric( &bpType, 1, 5 );
    } while ( done == FALSE );

    if ( (bpType > 0) && (bpType <= 5) ) {
        switch ( bpType ) {
        case 1:
            *blogPetType = BPTYPE_COUSAGI;
            break;
        case 2:
            *blogPetType = BPTYPE_CONEKO;
            break;
        case 3:
            *blogPetType = BPTYPE_COINU;
            break;
        case 4:
            *blogPetType = BPTYPE_COPANDA;
            break;
        case 5:
            *blogPetType = BPTYPE_CONEZUMI;
            break;
        default:
            *blogPetType = bpType;  /* ここには来ないが念のため */
            break;
        }
        ret = 0;
    }

    return ( ret );
}

/*
 *  標準入力から設定情報を取り込む
 */

int
getParamFromStdin( COUSAGI_INFO *cp )
{
    int     ret = 0;

    switch ( cp->promptLevel ) {
    case 0:
        if ( cp->blogPetType == BPTYPE_UNKNOWN )
            inputBlogPetType( &(cp->blogPetType),
                         "BlogPetの種類: " );
        if ( cp->cousagiName[0] == NUL )
            inputString( cp->cousagiName,
                         "BlogPetの名前: ",            FALSE );
        if ( cp->userNickName[0] == NUL )
            inputString( cp->userNickName,
                        "飼い主のニックネーム: ",      FALSE );
        if ( (cp->endpointURL[0] == NUL) && (cp->blogType == cocolog) )
            inputBlogType( &(cp->blogType), cp->endpointURL, &(cp->charSet),
                           "BLOG種別: " );
        if ( cp->cousagiUserName[0] == NUL )
            inputString( cp->cousagiUserName,
                         "BlogPetサーバ ユーザ名: ",   FALSE );
        if ( cp->cousagiPassword[0] == NUL )
            inputString( cp->cousagiPassword,
                         "BlogPetサーバ パスワード: ", TRUE );
        if ( cp->blogType != unsupported ) {
            if ( cp->blogUserName[0] == NUL )
                inputString( cp->blogUserName,
                             "BlogPet投稿用ユーザ名: ",    FALSE );
            if ( cp->blogPassword[0] == NUL )
                inputString( cp->blogPassword,
                             "BlogPet投稿用パスワード: ",  TRUE );
            if ( cp->endpointURL[0] == NUL )
                if ( (cp->blogType != exciteBlog) &&
                     (cp->blogType != mixi)       &&
                     (cp->blogType != kinugasa)   &&
                     (cp->blogType != echoo)      &&
                     (cp->blogType != fc2network) &&
                     (cp->blogType != rakuten)    &&
                     (cp->blogType != colors)     &&
                     (cp->blogType != openPne)       )
                    inputString( cp->endpointURL,
                                 "エンドポイントURL: ", FALSE );
        }
        if ( cp->blogType == newPostOnly ) {
            if ( cp->blogID[0] == NUL )
                inputString( cp->blogID, "BLOG ID: ", TRUE );
        }
        if ( (cp->blogType == msnSpaces) ||
             (cp->blogType == webryX)       ) {
            if ( cp->blogID[0] == NUL )
                strcpy( cp->blogID, "MyBlog" );
        }
#ifdef  USE_HAIKU
        if ( cp->useHaikuWords == -1 )
            inputYesNo( &(cp->useHaikuWords),
                        "BlogPetに俳句を詠ませますか?" );
#endif
        if ( (cp->blogType != mixi)       &&
             (cp->blogType != kinugasa)   &&
             (cp->blogType != fc2network) &&
             (cp->blogType != colors)     &&
             (cp->blogType != openPne)       ) {
            if ( cp->useHatenaLink == -1 )
                inputYesNo( &(cp->useHatenaLink),
                            "キーワードリンクを埋め込みますか?" );
        }
        else
            cp->useHatenaLink = 0;
        ret++;
        break;

    case 1:
        inputString( cp->cousagiPassword,
                     "BlogPetサーバ パスワード: ", TRUE );
        inputString( cp->blogPassword,
                     "BlogPet投稿用パスワード: ",  TRUE );
        ret++;
        break;

    case 2:
        /*
         *  -F オプションの指定の有無に関わらず、パスワード未取得の場合は、
         *  入力を求める
         */
        if ( cp->cousagiPassword[0] == NUL ) 
            inputString( cp->cousagiPassword,
                         "BlogPetサーバ パスワード: ", TRUE );
        if ( cp->blogPassword[0] == NUL ) 
            inputString( cp->blogPassword,
                         "BlogPet投稿用パスワード: ",  TRUE );
        ret++;
        break;

    default:
        break;
    }

    return ( ret );
}


#define MAX_TITLE_LEN   512
#define MAX_BODY_LEN    (MAX_CONTENT_SIZE * 2)
#define MAX_POSTID_LEN  MAX_POSTIDLENGTH
#define MAX_HAIKU_LEN   1024
#define MAX_PROMPT_LEN  80

#define MAX_CATS        32  /* 同一blog内カテゴリ最大数 (暫定値)           */
#define MAX_USERS       20  /* 同一blog内最大ユーザ数   (暫定値)           */
#define MAX_BLOGS       10  /* 同一システム内最大設置可能blog数 (ココログ) */
#define RECENT_ENTRIES  15  /* 連続投稿抑止チェックに使う記事の取得数      */


/*
 *  blog ID の取得・設定
 */

void
setBlogID( char *blogID, BLOGKIND blogType )
{
    if ( !(blogID[0]) ) {
        BLOGINF blogInfo[MAX_BLOGS];
        char    prompt[MAX_PROMPT_LEN];
        char    buf[BUFSIZ];
        int     numOfBlogs = MAX_BLOGS;
        int     r, i;

        /* --- 投稿可能な blog 一覧の取得 */
        if ( (blogType == livedoor) ||
             (blogType == lovelog)  ||
             (blogType == sonet)    ||
             (blogType == blogger)  ||
             (blogType == vox)      ||
             (blogType == hatena)   ||
             (blogType == atomAPI)     )
            r = getBlogIDsOnAtomAPI( NULL, NULL, &numOfBlogs, blogInfo );
        else
            r = getBlogIDs( NULL, NULL, &numOfBlogs, blogInfo );
                                                            /* blogger API */
        if ( r == 1 )
            strcpy( (char *)blogID, blogInfo[0].blogID );/* 確定 */
        else if ( r > 1 ) {
            /* ---- 投稿先 blog の選択 */
            do {
                for ( i = 0; i < r; i++ ) {
                    printf( "(%d)\t%s (blogID: %s)\n",
                            i + 1,
                            utf2sjis( blogInfo[i].blogName ),
                            blogInfo[i].blogID );
                }
                sprintf( prompt, "どの blog に投稿しますか? (1 - %d): ", r );
                inputString( (char *)buf, prompt, FALSE );
                i = atoi( buf );
                if ( (i >= 1) && (i <= r) ) {
                    strcpy( blogID, blogInfo[i - 1].blogID );
                    break;
                }
            } while ( (i < 1) || (i > r) );
        }
    }
}


/*
 *  投稿可能かどうか調査
 *          (for 連続投稿抑制)
 *      注意: 古典的な blogger API の場合は title が存在しないため、
 *            title から 「BlogPet」 による投稿かどうか判断する方法
 *            は機能しない。したがって、blogger API の場合は、ユーザ
 *            ID と投稿年月日を元に判断することにする。(BlogPet 投稿
 *            用に別IDを用意する必要がある[別IDを用意しないと、Blog
 *            Pet の投稿なのか、そうでないのか判断できない = 全部、
 *            BlogPet による投稿と判断されてしまう])
 */

#define ONE_WEEK    (7 * 24 * 60 * 60)

#ifdef  _MSC_VER
#pragma warning ( disable: 4100 )
#endif
BOOL
isPostingOK( const char *blogID,
             BLOGKIND   blogType,
             const char *blogUserName,
             const char *blogPassword,
             const char *cousagiName,
          // char       *endpointURL,
             int        blogPetType )
{
    BOOL        ret = TRUE;
    int         numOfPosts, i, num;
    time_t      t, tt;
    POSTINF     postInf[RECENT_ENTRIES];
    char        title[MAX_TITLE_LEN];

    /* 「BlogPet」のために metaWeblog.newPost のみ対応した blog は、他の  */
    /* API が使えないので、常に TRUE を返すようにする                     */
    if ( (blogType == newPostOnly) ||
         (blogType == fc2network)  ||
         (blogType == kinugasa)    ||  /* キヌガサのRSS取得は将来対応とする */
         (blogType == mixi)        ||  /* mixi は metaWeblog.newPost も未対 */
         (blogType == colors)      ||  /* 応。COLORS も未対応               */
         (blogType == openPne)        )
        return ( ret );

    /* --- 記事一覧取得 */
    numOfPosts = RECENT_ENTRIES;
    if ( (blogType == exciteBlog) ||
         (blogType == echoo)      ||
         (blogType == rakuten)       )
        num = getRecentPostTitlesViaRSS( blogType,
                                         blogUserName, blogPassword,
                                         &numOfPosts, postInf );
                                        /* RSS 経由で POSTINF 情報を取得 */
    else if ( (blogType == livedoor) ||
              (blogType == lovelog)  ||
              (blogType == sonet)    ||
              (blogType == ameblo)   ||
              (blogType == blogger)  ||
              (blogType == vox)      ||
              (blogType == hatena)   ||
              (blogType == atomAPI)     ) {
        num = getRecentPostTitlesOnAtomAPI( blogID, NULL, NULL,
                                            &numOfPosts, postInf );
                                        /* Atom API */
    }
    else if ( blogType == bloggerAPI )
        num = getRecentPostTitlesOnBloggerAPI( blogID, NULL, NULL,
                                               &numOfPosts, postInf );
                                        /* blogger API */
    else if ( (blogType == metaWeblog)  ||
              (blogType == msnSpaces)   ||
              (blogType == newsHandler) ||
              (blogType == fc2blog)     ||
              (blogType == jugemX)      ||
              (blogType == drecomX)     ||
              (blogType == yaplogX)     ||
              (blogType == webryX)         )
        num = getRecentPostTitlesOnMetaWeblog( blogID, NULL, NULL,
                                               &numOfPosts, postInf );
                                        /* metaWeblog API */
    else
        num = getRecentPostTitles( blogID, NULL, NULL,
                                   &numOfPosts, postInf );
                                        /* Movable Type API */
    t = time( NULL );
    if ( blogType == bloggerAPI ) {
        int     numOfUsers = MAX_USERS;
        USERINF *userInfo;

        userInfo = (USERINF *)malloc( sizeof ( USERINF ) * numOfUsers );
        if ( userInfo &&
             (getUserInfo( NULL, NULL, &numOfUsers, userInfo ) > 0) ) {
            char    *userID = NULL;

            for ( i = 0; i < numOfUsers; i++ ) {
                if ( !strncmp( blogUserName, userInfo[i].firstname,
                               strlen(userInfo[i].firstname) ) ) {
                    userID = userInfo[i].userid;
                    break;
                }
            }

            if ( userID ) {
                for ( i = 0; i < num; i++ ) {
                    if ( !strcmp( postInf[i].userid, userID ) ) {
                        tt = timeValue( postInf[i].yy,
                                        postInf[i].mm,
                                        postInf[i].dd,
                                        postInf[i].HH,
                                        postInf[i].MM,
                                        postInf[i].SS );
                        if ( t - tt >= ONE_WEEK )
                            continue;

                        ret = FALSE;
                        break;
                    }
                }
            }
        }

        if ( userInfo )
            free( userInfo );
    }
    else {
        char    *p;

        for ( i = 0; i < num; i++ ) {
            p = utf2sjis( postInf[i].title );
            strcpy( title, p ? p : postInf[i].title );
#ifdef  BLOCK_COUSAGI_AUTOPOST
            if ( strstr( title, "[via cocousagi]" ) ||
                 strstr( title, "(BlogPet)" )    /* ||
                 strstr( title, "(こうさぎ)" )    */   ) {
#else   /* !BLOCK_COUSAGI_AUTOPOST */
            if ( (( p = strstr( title, "[via cocousagi]" ) ) != NULL) &&
                 (strcmp( p + 15, "(BlogPet)" ) != 0)                    ) {
#endif  /* !BLOCK_COUSAGI_AUTOPOST */
                tt = timeValue( postInf[i].yy, postInf[i].mm, postInf[i].dd,
                                postInf[i].HH, postInf[i].MM, postInf[i].SS );
                if ( t - tt >= ONE_WEEK )
                    continue;

                ret = FALSE;
                break;
            }
        }
    }

    return ( ret );
}
#ifdef  _MSC_VER
#pragma warning ( default: 4100 )
#endif


/*
 *  ニックネーム置換
 */
char    *
replaceString( char *dst, const char *src,
               const char *userNickName, const char *cousagiUserName )
{
    const char  *p;
    char        *q;
    char        *r;
    int         l = strlen( cousagiUserName );
    int         m = strlen( userNickName );

     p = src;
     q = dst;

     while ( ( r = strstr( p, cousagiUserName ) ) != NULL ) {
         if ( p != r ) {
             strncpy( q, p, r - p );
             q[r - p] = NUL;
             strcat(  q, userNickName );
             q += ((r - p) + m);
             p = r + l;
         }
         else {
             strcpy( q, userNickName );
             p += l;
             q += m;
         }
     }

     if ( *p )
         strcat( q, p );
     else
         *q = NUL;

    return ( dst );
}


char    *
similaritySearch(
        const char *body,   /* (I) 記事本文                              */
        int        mode,    /* (I) 埋め込みモード [1: 画像, 2: テキスト] */
        int        blogPetType  /* (I) BlogPet種別 */,
        FILE       *verboseFp
    )
{
    int         numOfArticles = 10;
    int         numOfAmazon   = 10;
    POSTINF     *p;
    AMAZON_LITE *ap;
    int         ret, i, n;
    char        keyword[BUFSIZ];
#if 0
    time_t      t = time( NULL );
    struct tm   *tm = localtime( &t );
    int         yy = tm->tm_year + 1900;
    int         mm = tm->tm_mon + 1;
    int         dd = tm->tm_mday;
#endif
    char        *sim = NULL;
    int         len = 0;

    /* 関連記事検索 (bulkfeeds を利用) */
    getKeyword( body, keyword, blogPetType );
                                    /* 記事本文からキーワードを抽出 */
#ifdef  _DEBUG
    fprintf( verboseFp ? verboseFp : stderr,
             "キーワード: %s\n", keyword );
#endif

    p = (POSTINF *)malloc( numOfArticles * sizeof ( POSTINF ) );
    if ( p ) {
        ret = searchEntryOnBulkfeeds( keyword, &numOfArticles, p );
        if ( (ret > 0) && (numOfArticles > 0) ) {
            char    *pp;
            char    *qq;

            sim = (char *)malloc( MAX_CONTENT_SIZE );
            if ( !sim )
                return ( sim );

            for ( i = 0, n = 1; i < numOfArticles; i++ ) {
                pp = utf2sjis(p[i].title);
                if ( !pp )
                    continue;
                qq = p[i].postid;
                if ( (!strncmp( pp, "はてなアンテナ",   14 ) ||
                      !strncmp( pp, "未来検索アラート", 16 )   ) &&
                     strstr( qq, "blog.livedoor." )               )
                    continue; /* うざい記事はカット */

                if ( n == 1 ) {
                    sprintf( sim,
                             "<hr />\n"
                             "<dl><dt>%sちゃんのおすすめ記事 ("
                             "<strong>“%s”に関連する記事の一覧</strong>)"
                             "</dt>\n",
                             blogPetTypeString( blogPetType ),
                             keyword );
                }
                sprintf( sim + strlen(sim),
                         "<dd><a href=\"%s\">%s</a></dd>\n", qq, pp );
                n++;

                if ( mode == 2 )
                    if ( n >= 6 )
                        break;/* 暫定(yaplogは2000文字までしか投稿できない) */
            }
            if ( n > 1 ) {
                strcat( sim, "</dl>\n" );
                len = strlen( sim );
            }
        }
        else
            fprintf( verboseFp ? verboseFp : stderr,
                     "“%s”関連記事は見つかりませんでした。\n", keyword );

        free( p );
    }

    /* 関連商品検索 (Amazon Web Services を利用) */
    ap = (AMAZON_LITE *)malloc( sizeof ( AMAZON_LITE ) * numOfAmazon );
    if ( ap ) {
        char    *searchTitle = "";
        int     searchMode   = AMAZON_MODE_TOYS;

        /* まず、「おもちゃ&ホビー」で検索 */
        ret = searchItemsOnAmazon4( AMAZON_TYPE_SMALL,
                                   searchMode,
                                   AMAZON_STYPE_ITEMSEARCH, keyword,
                                   &numOfAmazon, ap );
        if ( numOfAmazon == 0 ) {
            /* ヒット数0なら、改めて「和書」で検索 */
            numOfAmazon = 10;
            searchMode  = AMAZON_MODE_JBOOKS;
            ret = searchItemsOnAmazon4( AMAZON_TYPE_SMALL,
                                       searchMode,
                                       AMAZON_STYPE_ITEMSEARCH, keyword,
                                       &numOfAmazon, ap );
        }
        if ( numOfAmazon == 0 ) {
            /* ヒット数0なら、今度は「すべての商品」で検索 */
            numOfAmazon = 10;
            searchMode  = AMAZON_MODE_BLENDED;
            ret = searchItemsOnAmazon4( AMAZON_TYPE_SMALL,
                                        searchMode,
                                        AMAZON_STYPE_ITEMSEARCH, keyword,
                                        &numOfAmazon, ap );
        }
        if ( numOfAmazon == 0 ) {
            /* ヒット数0なら、最後は「和書-こども向け」のベストセラーを取得 */
            numOfAmazon = 10;
            searchMode  = AMAZON_MODE_JBOOKS;
            searchTitle = "こども向け書籍ベストセラー";
            ret = getSalesRankingOnAmazon4( AMAZON_TYPE_SMALL,
                                            searchMode,
                                            AMAZON_NODE_CHILDREN,
                                            searchTitle,
                                            &numOfAmazon, ap );
        }

        if ( (ret > 0) && (numOfAmazon > 0) ) {
            char    *pp;

            if ( !sim ) {
                sim = (char *)malloc( MAX_CONTENT_SIZE );
                if ( !sim )
                    return ( sim );
                len = 0;
            }

            for ( i = 0, n = 1; i < numOfAmazon; i++ ) {
                pp = utf2sjis( ap[i].productName );
                if ( !pp || !(*pp) )
                    continue;

                if ( n == 1 ) {
                    if ( searchTitle[0] )
                        sprintf( sim + len,
                                 "<hr />"
                                 "%s%sちゃんのおすすめ商品 ("
                                 "<strong>%s</strong>)%s",
                                 mode == 1 ? "<p>" : "<dl><dt>",
                                 blogPetTypeString( blogPetType ),
                                 searchTitle,
                                 mode == 1 ? "</p>\n<p>" : "</dt>\n" );
                    else
                        sprintf( sim + len,
                                 "<hr />"
                                 "%s%sちゃんのおすすめ商品 ("
                                 "<strong>“%s”に関連する%sの一覧</strong>)"
                                 "%s",
                                 mode == 1 ? "<p>" : "<dl><dt>",
                                 blogPetTypeString( blogPetType ),
                                 keyword,
                                 searchMode == AMAZON_MODE_TOYS
                                    ? "おもちゃ"
                                    :
                                 searchMode == AMAZON_MODE_BLENDED
                                    ? "商品"
                                    : "書籍",
                                 mode == 1 ? "</p>\n<p>" : "</dt>\n" );
                }

                switch ( mode ) {
                case 1:
                    /* 画像埋め込み */
                    sprintf( sim + strlen( sim ),
                             "<a href=\"%s\">"
                             "<img src=\"%s\" alt=\"%s\" border=\"0\" />"
                             "</a>\n",
                             ap[i].url, ap[i].imageURLmedium, pp );
                    break;

                case 2:
                default:
                    /* テキスト埋め込み */
                    {
                        char *qq = strchr( ap[i].url, '?' );
                        if ( qq )
                            *qq = NUL;
                    }
                    sprintf( sim + strlen( sim ),
                             "<dd><a href=\"%s\">%s</a></dd>\n",
                             ap[i].url, pp );
                    break;
                }

                n++;

                if ( mode == 2 )
                    if ( n >= 4 )
                        break;/* 暫定(yaplogは2000文字までしか投稿できない) */
            }

            if ( n > 1 ) {
                switch ( mode ) {
                case 1:
                    /* 画像埋め込み */
                    strcat( sim, "</p>\n" );
                    break;

                case 2:
                default:
                    /* テキスト埋め込み */
                    strcat( sim, "</dl>\n" );
                    break;
                }
            }
        }
        else
            fprintf( verboseFp ? verboseFp : stderr,
                     "“%s”関連書籍は見つかりませんでした。\n", keyword );

        free( ap );
    }

    return ( sim );
}


/*
 *  代筆
 */
int
writeNewEntry( const char *editorPath,
               char       *title,
               char       *body,
               FILE       *verboseFp )
{
    int     ret = 0;
    char    filename[MAX_PATH];
    char    cmd[1024];
    FILE    *fp;
    char    *p, *q, buf[BUFSIZ];
    int     flag = 0;
    int     ins  = 0;
    int     autoMode = 0;

    autoMode = isatty( fileno( stdin ) ) ? 0 : 1;

    title[0] = NUL;
    body[0]  = NUL;

    sprintf( filename, "tempEdit.txt" );
#ifdef  WIN32
    if ( ( fp = fopen( filename, "w+t" ) ) == NULL ) {
#else
    if ( ( fp = fopen( filename, "w" ) ) == NULL ) {
#endif
        fprintf( verboseFp ? verboseFp : stderr,
                 "代筆用ファイルが作成できません\n" );
        return ( ret );
    }

    if ( autoMode ) {
        p = fgets( buf, BUFSIZ - 1, stdin );
        if ( p && *p ) {
            q = p + (strlen(p) - 1);
            if ( *q == '\n' )
                *q = NUL;
            strcpy( title, p );

            body[0] = NUL;
            while ( ( p = fgets( buf, BUFSIZ - 1, stdin ) ) != NULL )
                strcat( body, p );

            fprintf( fp,
                     "題名: ↓↓↓\n%s\n↑↑↑↑↑↑\n"
                     "本文: ↓↓↓\n%s\n↑↑↑↑↑↑\n",
                     title, body );
        }
        clearerr( stdin );
        title[0] = NUL;
        body[0]  = NUL;
    }
    else
        fprintf(fp,
            "題名: ↓↓↓\n\n↑↑↑↑↑↑\n本文: ↓↓↓\n\n↑↑↑↑↑↑\n");

    fprintf(fp,"(注意: 「題名」は 1行 で書いてください。\n");
    fprintf(fp,"       「本文」が長い場合は、投稿時にエラーが出ないように\n");
    fprintf(fp,"        適度に短くすることがあります。)\n\n");
    fclose( fp );

    if ( !autoMode ) {
#ifdef  WIN32
        /* Windows 98 では(Windows 95 でも?)、notepad の終了を待たずに system() */
        /* から戻ってくる (P_NOWAIT で子プロセスを起動したのと同じような結果に  */
        /* なる) ため、system() の代わりに spawnlp() を使うことにする           */
        strcpy( cmd, (editorPath && editorPath[0]) ? editorPath : "notepad.exe" );
        spawnlp( P_WAIT, cmd, cmd, "tempEdit.txt", NULL );
#else
        sprintf( cmd, "%s %s",
                 (editorPath && editorPath[0]) ? editorPath : "notepad.exe",
                 filename );

        system( cmd );
#endif
    }

#ifdef  WIN32
    if ( ( fp = fopen( filename, "r+b" ) ) == NULL ) {
#else
    if ( ( fp = fopen( filename, "r" ) ) == NULL ) {
#endif
        fprintf( verboseFp ? verboseFp : stderr,
                 "代筆したファイルが見つかりません\n" );
        return ( ret );
    }

    while ( ( p = fgets( buf, BUFSIZ - 1, fp ) ) != NULL ) {
        if ( flag == 0 ) {
            if ( !strncmp( p, "題名: ↓↓↓", 12 ) )
                flag = 1;
            continue;
        }

        if ( flag == 1 ) {
            if ( !strncmp( p, "↑↑↑↑↑↑", 12 ) )
                flag = 2;
            else {
                while ( (*p == ' ') || (*p == '\t') )
                    p++;
                if ( (*p == '\n') || (*p == '\r') )
                    continue;
                while ( (p[strlen( p ) - 1] == '\n') ||
                        (p[strlen( p ) - 1] == '\r') ||
                        (p[strlen( p ) - 1] == '\t') ||
                        (p[strlen( p ) - 1] == ' ' )    )
                    p[strlen(p) - 1] = NUL;
                if ( *p && !(title[0]) ) {
                    q = sjis2utf( p );
                    if ( q )
                        strcpy( title, q );
                }
            }
            continue;
        }

        if ( flag == 2 ) {
            if ( !strncmp( p, "本文: ↓↓↓", 12 ) )
                flag = 3;
            continue;
        }

        if ( flag == 3 ) {
            if ( !strncmp( p, "↑↑↑↑↑↑", 12 ) )
                break;
            else {
                q = sjis2utf( p );
                if ( q ) {
                    if ( !(body[0]) && !autoMode ) {
                        if ( !strncmpi( q, "<p>", 3 ) ||
                             !strncmpi( q, "<p ", 3 )    )
                            strcpy( body, q );
                        else {
                            strcpy( body, "<p>" );
                            strcat( body, q );
                            ins = 1;
                        }
                    }
                    else
                        strcat( body, q );
                    if ( !autoMode ) {
                        if ( !strstr( q, "<br" ) )
                            strcat( body, "<br />" );
                    }
                    ret = 1;
                }
            }
            continue;
        }
    }
    fclose( fp );

    if ( body[0] && (strlen(body) >= 6) ) {
        p = &(body[strlen(body) - 6]);
        if ( !strcmp( p, "<br />" ) )
            *p = NUL;
    }
    if ( ins )
        strcat( body, "</p>" );

    return ( ret );
}

/*
 *  BlogPet による記事生成
 */
BOOL
_makeEntry(
        const char *cousagiUserName,
        const char *cousagiName,
        const char *userNickName,
        int        blogPetType,
#ifdef  USE_HAIKU
        int        useHaikuWords,
        BOOL       useWritingForPet,
#endif
        int        useHatenaLink,
        int        useSimilarity,
        char       *title,
        char       *body,
        FILE       *verboseFp
    )
{
    BOOL    ret  = FALSE;
    char    *sim = NULL, *p;

#ifdef  _DEBUG
    if ( title[0] )
        fprintf( verboseFp ? verboseFp : stderr,
                 "題名: %s\n", utf2sjis( title ) );
    if ( body[0] )
        fprintf( verboseFp ? verboseFp : stderr,
                 "本文: %s\n", utf2sjis( body ) );
#endif
    strcpy( body, utf2sjis( body ) );

    /* ニックネーム置換 */
    if ( userNickName && *userNickName &&
         (strcmp( userNickName, cousagiUserName ) != 0) ) {
        char    *work = (char *)malloc( MAX_BODY_LEN );
        if ( work ) {
            memset( work, 0x00, MAX_BODY_LEN );
            replaceString( work, body, userNickName, cousagiUserName );
            strcpy( body, work );
            free( work );
        }
    }

#ifdef  USE_HAIKU
    if ( useHaikuWords == 1 ) {
        int     retry = 0;
        int     r     = 0;
        char    haiku[MAX_HAIKU_LEN];

        memset( haiku, 0x00, MAX_HAIKU_LEN );
        if ( useWritingForPet == FALSE ) {
            /* 俳句取得 */
            do {
                r = getNewHaikuOnCousagi( NULL, NULL, haiku );
                if ( r && haiku[0] ) {
                    if ( !strstr( haiku, "<![CDATA[" ) &&
                         !strstr( haiku, "]]>" )          )
                        break;
                    retry++;
                }
                else
                    break;
            } while ( retry <= 2 );
        }
        if ( r && haiku[0] ) {
#ifdef  _DEBUG
            fprintf( verboseFp ? verboseFp : stderr,
                     "俳句: %s\n", utf2sjis( haiku ) );
#endif
            /* 俳句埋め込み */
            insertHaikuIntoEntry( body, utf2sjis( haiku ),
                                  cousagiName, blogPetType );
        }
        else {
            fprintf( verboseFp ? verboseFp : stderr,
                     "%sちゃんは、俳句を詠みたくないそうです (涙)\n",
                     cousagiName );
        }
    }
    else {
        /* こうさぎ → こねこ、……、こねずみ への置換 (必要な場合) */
        insertHaikuIntoEntry( body, NULL,
                              cousagiName, blogPetType );
    }
#else   /* !USE_HAIKU */
    /* こうさぎ → こねこ、……、こねずみ への置換 (必要な場合) */
    insertHaikuIntoEntry( body, NULL,
                          cousagiName, blogPetType );
#endif  /* !USE_HAIKU */

    if ( useSimilarity )
        sim = similaritySearch( body, useSimilarity, blogPetType, verboseFp );

    if ( useHatenaLink == 1 ) {
        /* キーワードリンク埋め込み */
        char    *p, *result;
        size_t  sz = MAX_BODY_LEN;

        result = (char *)malloc( sz );
        if ( result ) {
            memset( result, 0x00, sz );
            p = setKeywordLink( sjis2utf( body ),
                                result, sz, 0, CNAME_GENERAL,
                                "_blank", "hatena" );
            if ( p && *p && strcmp( p, "(null)" ) ) {
                if ( !strncmp( p, ";lt=;lt=", 8 ) ) {
                    /* 文字化け検出                       */
                    /*  → キーワードリンク埋め込みを中止 */
                    fputs( "「はてなキーワードリンク」で文字化け発生し"
                           "ました!!\n"
                           "キーワードリンクの埋め込みを中止します。\n",
                           verboseFp ? verboseFp : stderr );
                }
                else
                    strcpy( body, utf2sjis( p ) );
            }
            else {
                /* はてなダイアリーキーワード自動リンクAPIが */
                /* 機能していない場合                        */
                memset( result, 0x00, MAX_BODY_LEN );
                p = changeHatenaKeyword( body, result, MODE_HTML );
                strcpy( body, euc2sjis( p ) );
            }
#ifdef  _DEBUG
            fprintf( verboseFp ? verboseFp : stderr,
                     "結果: %s\n", body );
#endif
            free( result );
        }
    }

    if ( sim ) {
        strcat( body, sim );
        free( sim );
    }

    p = strrchr( body, '<' );
    if ( p && !strncmp( p, "</p>", 4 ) ) {
        strcpy( p,
          "<br />--- powered by <a href=\""
#ifdef  COCOUSAGI_VER2
          "http://watcher.moe-nifty.com/memo/2005/02/cocousagi.html"
#else   /* COCOUSAGI_VER3 */
          "http://watcher.moe-nifty.com/memo/2006/09/cocousagi.html"
#endif  /* COCOUSAGI_VER3 */
          "\">cocousagi</a> ---</p>\n" );
    }

    ret = TRUE;

    return ( ret );
}

BOOL
makeEntryByCousagi(
        const char *cousagiUserName,
        const char *cousagiPassword,
        const char *cousagiName,
        const char *userNickName,
        int        blogPetType,
#ifdef  USE_HAIKU
        int        useHaikuWords,
#endif
        int        useHatenaLink,
        int        useSimilarity,
        char       *title,
        char       *body,
        FILE       *verboseFp
    )
{
    BOOL    ret = FALSE;
    int     r;

    /* 記事取得 */
    setUserInfo( cousagiUserName, cousagiPassword );
    r = getNewEntryOnCousagi( NULL, NULL, title, body );
    if ( !r || !(title[0]) || !(body[0]) ) {
        fprintf( verboseFp ? verboseFp : stderr,
                 "BlogPetサーバとのお話がうまくいきませんでした。\n"
                 "しばらく経ってから、もう一度試してみてください。\n" );
        return ( ret );
    }

    ret = _makeEntry( cousagiUserName,
                      cousagiName, userNickName,
                      blogPetType,
#ifdef  USE_HAIKU
                      useHaikuWords, FALSE,
#endif
                      useHatenaLink, useSimilarity,
                      title, body,
                      verboseFp );
    return ( ret );
}

BOOL
makeEntryByYourself(
        const char *editorPath,
        const char *cousagiUserName,
        const char *cousagiName,
        const char *userNickName,
        int        blogPetType,
#ifdef  USE_HAIKU
        int        useHaikuWords,
#endif
        int        useHatenaLink,
        int        useSimilarity,
        BLOGKIND   blogType,
        char       *title,
        char       *body,
        FILE       *verboseFp
    )
{
    BOOL    ret = FALSE;
    int     r;
    char    buf[BUFSIZ];

    /* 記事作成 */
    r = writeNewEntry( editorPath, title, body, verboseFp );

    if ( !(body[0]) )
        return ( ret );
    if ( (blogType != mixi)       &&
         (blogType != kinugasa)   &&
         (blogType != fc2network) &&
         (blogType != colors)     &&
         (blogType != rakuten)    &&
         (blogType != openPne)       ) {
        if ( !strstr( body, "<p>" ) ) {
            char    *tmp = (char *)malloc( strlen( body ) + 32 );
            if ( tmp ) {
                strcpy( tmp, "<pre>\n" );
                strcat( tmp, body );
                strcat( tmp, "</pre>\n" );
                strcpy( body, tmp );
                free( tmp );
            }
        }
    }

    sprintf( buf,
             "\n<p>*このエントリは <a href=\"http://www.blogpet.net/\">%s</a> の"
             " %s の代わりに「%s」が書きました。</p>\n",
             blogPetType == BPTYPE_COUSAGI
                 ? "こうさぎ" :
             blogPetType == BPTYPE_CONEKO
                 ? "こねこ"   :
             blogPetType == BPTYPE_COINU
                 ? "こいぬ"   :
             blogPetType == BPTYPE_COPANDA
                 ? "こぱんだ" :
             blogPetType == BPTYPE_CONEZUMI
                 ? "こねずみ" :
                   "BlogPet",
             cousagiName,
             userNickName && *userNickName ? userNickName
                                           : "どこかの誰かさん" );
    strcat( body, sjis2utf( buf ) );

    if ( !(title[0]) )
        strcpy( title, sjis2utf( "無題" ) );

    ret = _makeEntry( cousagiUserName,
                      cousagiName, userNickName,
                      blogPetType,
#ifdef  USE_HAIKU
                      useHaikuWords, TRUE,
#endif
                      useHatenaLink, useSimilarity,
                      title, body,
                      verboseFp );
    return ( ret );
}


/*
 *  Atom API 系投稿処理
 */

int
postArticleOnAtomAPI( const char *blogID,       /* 投稿先 blog ID     */
                      BLOGKIND   blogType,      /* blog 種別          */
                      const char *title,        /* 投稿記事題名       */
                      const char *body,         /* 投稿記事本文       */
                      const char *catName,      /* 投稿対象カテゴリ名 */
                   // char       *endpointURL,  /* エンドポイントURL  */
                      int        blogPetType,   /* BlogPet種別        */
                      int        forceWritingForPet ) /* 強制代理投稿 */
{
    int             ret = 0;
    char            summary[BUFSIZ];
    char            postID[MAX_POSTIDLENGTH + 1];
    CATLISTINF_t    categories;
    int             found = FALSE;
    CATLISTINFEX    catEx;
    char            *content;
    CATLISTINF      categoryList[MAX_CATS];
    long            numOfC = MAX_CATS;
    int             numOfCategories;
    int             i;
    char            *subject;
    char            *categoryName;
    BOOL            bRet;

    /* 「こうさぎ」カテゴリの categoryId を取得 */
    catEx.numberOfCategories = 0;
    categories = (CATLISTINF *)malloc( sizeof ( CATLISTINF ) * 1 );
    numOfCategories = 
        listCategoriesOnAtomAPI( NULL, NULL, blogID, &numOfC, categoryList );
    for ( i = 0; i < numOfCategories; i++ ) {
        categoryName = utf2sjis( categoryList[i].categoryName );

        if ( catName && catName[0] &&
             !strcmp( categoryName, catName ) ) {
            catEx.numberOfCategories = 1;
            strcpy( catEx.categoryName[0], sjis2utf( catName ) );
            found = TRUE;
            break;
        }
    }

    if ( found == FALSE ) {
        for ( i = 0; i < numOfCategories; i++ ) {
            categoryName = utf2sjis( categoryList[i].categoryName );

            if ( !strcmp( categoryName, "BlogPet" )           ||
                 !strcmp( categoryName, "Blog Pet" )          ||
                 !strcmp( categoryName, "ここうさぎ" )        ||
                 !strcmp( categoryName, "cocousagi" )         ||
                 ((blogPetType == BPTYPE_COUSAGI)         &&
                  (!strcmp( categoryName, "こうさぎ" ) ||
                   !strcmp( categoryName, "cousagi" )  ||
                   !strcmp( categoryName, "kousagi" )    )  ) ||
                 ((blogPetType == BPTYPE_CONEKO)          &&
                  (!strcmp( categoryName, "こねこ" )   ||
                   !strcmp( categoryName, "coneko" )   ||
                   !strcmp( categoryName, "koneko" )     )  ) ||
                 ((blogPetType == BPTYPE_COINU)           &&
                  (!strcmp( categoryName, "こいぬ" )   ||
                   !strcmp( categoryName, "coinu" )    ||
                   !strcmp( categoryName, "koinu" )      )  ) ||
                 ((blogPetType == BPTYPE_COPANDA)         &&
                  (!strcmp( categoryName, "こぱんだ" ) ||
                   !strcmp( categoryName, "copanda" )  ||
                   !strcmp( categoryName, "kopanda" )    )  ) ||
                 ((blogPetType == BPTYPE_CONEZUMI)        &&
                  (!strcmp( categoryName, "こねずみ" ) ||
                   !strcmp( categoryName, "conezumi" ) ||
                   !strcmp( categoryName, "konezumi" )   )  )    ) {
                char    temp[MAX_CATNAMELENGTH];

                catEx.numberOfCategories = 1;
                strcpy( temp, categoryName );
                strcpy( catEx.categoryName[0], sjis2utf( temp ) );
                found = TRUE;
                break;
            }
        }

        if ( (found == FALSE) && (blogType == vox) ) {
            catEx.numberOfCategories = 2;
            strcpy( catEx.categoryName[0], "BlogPet" );
            strcpy( catEx.categoryName[1], "cocousasgi" );
            found = TRUE;
        }
    }
    free( (char *)categories );

    /* 投稿 */
    content = (char *)malloc( (MAX_BODY_LEN * 3) / 2 );
    subject = (char *)malloc( strlen( title ) + BUFSIZ + 1 );
    strcpy( subject, title );
    if ( !forceWritingForPet )
        strcat( subject, " [via cocousagi]" );
    else {
        time_t      t = time( NULL );
        struct tm   *tm = localtime( &t );

        sprintf( subject + strlen( subject ),
                 " (%04d/%02d/%02d)",
                 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday );
    }

    if ( blogType == ameblo ) {
        if ( strlen( subject ) >= 48 * 2 ) {
            /* アメーバブログでは、記事のタイトルは 48文字以内 */
            /*     (48文字より長いと投稿に失敗する)            */
            strcpy( subject, 
                    sjis2utf( "今日の日記 (BlogPet) [via cocousagi]" ) );
        }
    }
    if ( blogType == vox ) {
        if ( strlen( subject ) >= 80 ) {
            /* アメーバブログでは、記事のタイトルは 80バイト以内    */
            /*     (80文字より長いとあふれた部分は捨てられてしまう) */
            strcpy( subject, 
                    sjis2utf( "今日の日記 (BlogPet) [via cocousagi]" ) );
        }
    }

    strcpy( summary,
            sjis2utf(
                "この記事は「Blog Pet API応用プログラム cocousagi」"
                "による自動投稿です。" ) );
    strcpy( content, sjis2utf( body ) );

    setTimeoutTime( DEFAULT_HTTP_TIMEOUT * 4 );
    bRet = postEntryOnAtomAPI( NULL, NULL, blogID, 
                               subject,  /* 題名           */
                               summary,  /* 概要           */
                               content,  /* 本文           */
                               &catEx,   /* カテゴリ       */
                               TRUE,     /* 投稿状態       */
                               postID    /* 投稿後の記事ID */
                             );
    setTimeoutTime( DEFAULT_HTTP_TIMEOUT );

    free( subject );
    free( content );

    ret = (bRet == TRUE) ? 1 : 0;

    return ( ret );
}


int
postArticleOnXMLRPC( const char *blogID,        /* 投稿先 blog ID     */
                     BLOGKIND   blogType,       /* blog 種別          */
                     const char *title,         /* 投稿記事題名       */
                     const char *body,          /* 投稿記事本文       */
                     const char *catName,       /* 投稿対象カテゴリ名 */
                     int        blogPetType,    /* BlogPet種別        */
                     int        forceWritingForPet )  /* 強制代理投稿 */
{
    int         ret = 0;
    int         found = FALSE;
    char        *categoryName;
    char        postID[MAX_POSTID_LEN + 1];
    CATLISTINF  categoryList[MAX_CATS];
    CATEGORY    category;
    long        numOfCategories;
    CONTENT     content;
    CONTENTINF  content0;
    CONTENTINF  content2;
    int         i;
    int         retry, retry2;
    BOOL        bRet = FALSE;

    if ( (blogType != bloggerAPI)  &&
         (blogType != metaWeblog)  &&
         (blogType != msnSpaces)   &&
      /* (blogType != newsHandler) && */
         (blogType != jugemX)      &&
         (blogType != drecomX)     &&
         (blogType != yaplogX)     &&
         (blogType != webryX)      &&
         (blogType != newPostOnly)    ) {
        /* --「こうさぎ」カテゴリの categoryId を取得 */
        numOfCategories = MAX_CATS;
        getCategoryList( blogID, NULL, NULL,
                         &numOfCategories,
                         categoryList );    /* Movable Type API */
        if ( numOfCategories > 0 ) {
            strcpy( category.categoryId, categoryList[0].categoryId );
            for ( i = 0; i < numOfCategories; i++ ) {
                categoryName = utf2sjis( categoryList[i].categoryName );

                if ( catName && catName[0] &&
                     !strcmp( categoryName, catName ) ) {
                    strcpy( category.categoryId, categoryList[i].categoryId );
                    found = TRUE;
                    break;
                }
            }

            if ( found == FALSE ) {
                for ( i = 0; i < numOfCategories; i++ ) {
                    categoryName = utf2sjis( categoryList[i].categoryName );
                    if ( !categoryName )
                        continue;

                    if ( !strcmp( categoryName, "BlogPet" )           ||
                         !strcmp( categoryName, "Blog Pet" )          ||
                         !strcmp( categoryName, "ここうさぎ" )        ||
                         !strcmp( categoryName, "cocousagi" )         ||
                         ((blogPetType == BPTYPE_COUSAGI)         &&
                          (!strcmp( categoryName, "こうさぎ" ) ||
                           !strcmp( categoryName, "cousagi" )  ||
                           !strcmp( categoryName, "kousagi" )    )  ) ||
                         ((blogPetType == BPTYPE_CONEKO)          &&
                          (!strcmp( categoryName, "こねこ" )   ||
                           !strcmp( categoryName, "coneko" )   ||
                           !strcmp( categoryName, "koneko" )     )  ) ||
                         ((blogPetType == BPTYPE_COINU)           &&
                          (!strcmp( categoryName, "こいぬ" )   ||
                           !strcmp( categoryName, "coinu" )    ||
                           !strcmp( categoryName, "koinu" )      )  ) ||
                         ((blogPetType == BPTYPE_COPANDA)         &&
                          (!strcmp( categoryName, "こぱんだ" ) ||
                           !strcmp( categoryName, "copanda" )  ||
                           !strcmp( categoryName, "kopanda" )    )  ) ||
                         ((blogPetType == BPTYPE_CONEZUMI)        &&
                          (!strcmp( categoryName, "こねずみ" ) ||
                           !strcmp( categoryName, "conezumi" ) ||
                           !strcmp( categoryName, "konezumi" )   )  )    ) {
                        strcpy( category.categoryId,
                                categoryList[i].categoryId );
                        found = TRUE;
                        break;
                    }
                }
            }
        }
    }

    /* -- 投稿 */
    if ( !forceWritingForPet )
        strcat( (char *)title, " [via cocousagi]" );
    else {
        time_t      t = time( NULL );
        struct tm   *tm = localtime( &t );

        sprintf( (char *)title + strlen( title ),
                 " (%04d/%02d/%02d)",
                 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday );
    }
    if ( blogType == newsHandler ) {
        /* News-Handler の場合、投稿日時を明示しないと、変な投稿日時になっ */
        /* てしまうことがあるため、特別に対策を施す (本来は、サーバの現在  */
        /* 時刻が投稿日時になるはず)                                       */
        time_t      t = time( NULL );
        struct tm   *tm = gmtime( &t ); /* グリニッジ標準時 */
        sprintf( content.dateCreated,
                 "%04d%02d%02d %02d:%02d:%02d",
                 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
                 tm->tm_hour, tm->tm_min, tm->tm_sec );
        /* 備考: yyyy-mm-ddThh:mm:ss や yyyy-mm-ddThh:mm:ssZ の形式だと変な */
        /*       投稿日時(1年前とか)になる。 yyyymmdd hh:mm:ss 形式だとうま */
        /*       くいくことが判明。                                         */
        /*       明らかにサーバ側の不具合だと思われるが、いつ修正されるかわ */
        /*       からないので、とりあえず、クライアント側で対処しておく     */
    }
#if 0
    else if ( blogType == drecomX ) {
        time_t      t = time( NULL );
        struct tm   *tm = gmtime( &t ); /* グリニッジ標準時 */
        sprintf( content.dateCreated,
                 "%04d-%02d-%02dT%02d:%02d:%02dZ",
                 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
                 tm->tm_hour, tm->tm_min, tm->tm_sec );
    }
#endif
    else
        strcpy( content.dateCreated, "" );
    strcpy( content.description, sjis2utf( body ) );
    strcpy( content.title,       title );
    content.mt_allow_comments = 1;
    content.mt_allow_pings    = 1;
    strcpy( content.mt_convert_breaks, "0" );
    strcpy( content.mt_excerpt,
            sjis2utf(
                "この記事は「Blog Pet API応用プログラム cocousagi」"
                "による自動投稿です。" ) );
    strcpy( content.mt_text_more, "" );
    strcpy( content.mt_keywords,  "cousagi autopost" );
    content.numOfTrackbacks = 0;
    memset( &(content.mt_tb_ping_urls), 0x00,
            MAX_PINGS * MAX_LOGICALLINELEN );

    if ( (blogType == msnSpaces) ||
         (blogType == webryX)       ) {
        if ( catName && catName[0] ) {
            /* -- カテゴリ設定 */
            content.numOfCategories = 1;
            strcpy( content.categories[0], catName );
            // 今のところ、カテゴリ指定は1個だけ (暫定)
        }
    }

    memset( &content0, 0x00, sizeof ( CONTENTINF ) );
    if ( (blogType == msnSpaces) ||
         (blogType == webryX)       ) {
        CONTENTINFEX    content0x;
        int             num = 1;

        memset( &content0x, 0x00, sizeof ( CONTENTINFEX ) );
        getRecentPostsEx( blogID, NULL, NULL, &num, &content0x );
                                                /* metaWeblog API */
        strcpy( content0.postid, content0x.postid );
    }
    else
        getRecentPost( blogID, NULL, NULL, &content0 );

    setTimeoutTime( DEFAULT_HTTP_TIMEOUT * 8 );
    retry = 5;
    do {
        memset( postID, 0x00, MAX_POSTID_LEN );
        retry--;
        if ( retry < 0 )
            break;
        if ( blogType == bloggerAPI )
            bRet = newPost( blogID, NULL, NULL, body, TRUE, postID );
                                                        /* blogger API */
        else
            bRet = newPostEx( blogID, NULL, NULL,
                              &content, TRUE, postID ); /* metaWeblog API */

        if ( postID[0] == NUL ) {
            retry2 = 5;
            do {
                retry2--;
                if ( retry2 < 0 )
                    break;
                Sleep( 8000 );
                memset( &content2, 0x00, sizeof ( CONTENTINF ) );
                if ( (blogType == msnSpaces) ||
                     (blogType == webryX)       ) {
                    CONTENTINFEX    content2x;
                    int             num = 1;

                    memset( &content2x, 0x00, sizeof ( CONTENTINFEX ) );
                    getRecentPostsEx( blogID, NULL, NULL, &num, &content2x );
                                                          /* metaWeblog API */
                    strcpy( content2.postid, content2x.postid );
                }
                else
                    getRecentPost( blogID, NULL, NULL, &content2 );

                if ( content2.postid[0]                                  &&
                     ((content0.postid[0] == NUL) ||
                      (strcmp( content2.postid, content0.postid ) != 0))    )
                    strcpy( postID, content2.postid );
            } while ( postID[0] == NUL );
        }
    } while ( postID[0] == NUL );

    if ( postID[0] && (found == TRUE) ) {
        /* -- カテゴリ設定 */
        retry = 5;
        do {
            retry--;
            if ( retry < 0 )
                break;
            Sleep( 2500 );
            category.isPrimary = TRUE;
            ret = setPostCategories( postID, NULL, NULL, 1, &category );
                                                /* Movable Type API */
        } while ( ret == FALSE );

        /* -- rebuild */
     // if ( blogType == metaWeblog ) { /* → blogType == metaWeblog のとき */
     //                                 /*    は、found == FALSE なので、こ */
     //                                 /*    こに来ることはあり得ない      */
     //     /* metaWeblog API はサポートしているが Movable Type 互換 */
     //     /* API はサポートしていない blog の場合                  */
     //     editPostEx( postID, NULL, NULL, &content, 1 );
     // }
     // else {
            /* Movable Type 互換 API サポート blog の場合 */
            retry = 5;
            do {
                retry--;
                if ( retry < 0 )
                    break;
                Sleep( 2500 );
                ret = publishPost(postID, NULL, NULL);/* Movable Type API */
                    /* 注意: この関数の実行中にタイムアウトが発生した場合 */
                    /*       は、カテゴリが反映されていない可能性が高い   */
            } while ( ret == FALSE );
     // }
    }
    setTimeoutTime( DEFAULT_HTTP_TIMEOUT );

    ret = (bRet == TRUE) ? 1 : 0;

    return ( ret );
}


/*
 *  BlogPet サーバで記事を生成し、blog に投稿する
 */
int
makeEntry( const COUSAGI_INFO *cp, char *title, char *body )
{
    int ret = 0, r;
    int useSimilarity = 0;

    /* 準備 */
    setUseProxy( cp->useProxy );
    setBlogKind( cp->blogType );

    if ( cp->blogUserName[0] && cp->blogPassword[0] &&
         (cp->blogType != newPostOnly) &&
         (cp->blogType != exciteBlog)  &&
         (cp->blogType != mixi)        &&
         (cp->blogType != kinugasa)    &&
         (cp->blogType != echoo)       &&
         (cp->blogType != fc2network)  &&
         (cp->blogType != rakuten)     &&
         (cp->blogType != colors)      &&
         (cp->blogType != openPne)        ) {
        if ( !strncmp( cp->endpointURL, "http://localhost/", 17 ) )
            setUseProxy( FALSE );
        setTargetURL( cp->endpointURL );
        setUserInfo( cp->blogUserName, cp->blogPassword );

        /* -- 投稿先 blog ID の確定 */
        setBlogID( (char *)(cp->blogID), cp->blogType );
#ifdef  _DEBUG
        fprintf( cp->verboseFp ? cp->verboseFp : stderr,
                 "blogID = %s\n", cp->blogID );
#endif

        /* -- 連続投稿抑制 (1週間に1回しか投稿できないようにする) */
        if ( !(cp->allowFreePost) &&    /* 連続投稿抑制ありの場合 */
             isPostingOK( cp->blogID, cp->blogType,
                          cp->blogUserName, cp->blogPassword,
                          cp->cousagiName,
                       // (char *)(cp->endpointURL),
                          cp->blogPetType ) == FALSE ) {
            if ( cp->cousagiName && *(cp->cousagiName) )
                fprintf( cp->verboseFp ? cp->verboseFp : stderr,
                         "%s %sちゃんはお休み中です。\n",
                         blogPetTypeString( cp->blogPetType ),
                         cp->cousagiName );
            else
                fprintf( cp->verboseFp ? cp->verboseFp : stderr,
                         "%sちゃんはお休み中です。\n",
                         blogPetTypeString( cp->blogPetType ) );
            return ( ret );
        }

        if ( !strncmp( cp->endpointURL, "http://localhost/", 17 ) &&
             cp->useProxy )
            setUseProxy( cp->useProxy );
    }
    if ( (cp->blogType == exciteBlog) ||
         (cp->blogType == echoo)      ||
         (cp->blogType == rakuten)       ) {
        /* -- 連続投稿抑制 (1週間に1回しか投稿できないようにする) */
        if ( !(cp->allowFreePost) &&    /* 連続投稿抑制ありの場合 */
             isPostingOK( cp->blogID, cp->blogType,
                          cp->blogUserName, cp->blogPassword,
                          cp->cousagiName,
                       // (char *)(cp->endpointURL),
                          cp->blogPetType ) == FALSE ) {
            if ( cp->cousagiName && *(cp->cousagiName) )
                fprintf( cp->verboseFp ? cp->verboseFp : stderr,
                         "%s %sちゃんはお休み中です。\n",
                         blogPetTypeString( cp->blogPetType ),
                         cp->cousagiName );
            else
                fprintf( cp->verboseFp ? cp->verboseFp : stderr,
                         "%sちゃんはお休み中です。\n",
                         blogPetTypeString( cp->blogPetType ) );
            return ( ret );
        }
    }

    /* BlogPet に記事を書かせる */
    if ( cp->amazonAssociateID[0] && strstr( cp->amazonAssociateID, "-22" ) )
        setAssociateIdOnAmazon( cp->amazonAssociateID );
    if ( cp->useSimilarity ) {
        useSimilarity = cp->useSimilarity;
        /* アフィリエイト禁止ブログやhtml埋め込み不可のブログは、    */
        /* 「おすすめ」は使わないようにする                          */
        /*  (注意: キヌガサはアフィリエイト可能だが、将来対応とする) */
        if ( (cp->blogType == exciteBlog) ||
             (cp->blogType == mixi)       ||
             (cp->blogType == kinugasa)   ||
             (cp->blogType == echoo)      ||
             (cp->blogType == fc2network) ||
             (cp->blogType == rakuten)    ||
             (cp->blogType == colors)     ||
             (cp->blogType == openPne)       )
            useSimilarity = 0;
        if ( // (cp->blogType == yaplog) ||
             (cp->blogType == ameblo) ||
             (cp->blogType == webryX)    )
            useSimilarity = 2;  /* 画像埋込みではなく、テキスト埋込みを使う */
    }
    if ( cp->forceWritingForPet )
        r = FALSE;  /* 強制代筆モード ON の場合 */
    else
        r = makeEntryByCousagi( cp->cousagiUserName, cp->cousagiPassword,
                                cp->cousagiName, cp->userNickName,
                                cp->blogPetType,
#ifdef  USE_HAIKU
                                cp->useHaikuWords,
#endif
                                cp->useHatenaLink,
                                useSimilarity,
                                title, body,
                                cp->verboseFp );
    if ( r == FALSE ) {
        fprintf( cp->verboseFp ? cp->verboseFp : stderr,
                 "%s %sちゃんのご機嫌が悪いようです。\n",
                 blogPetTypeString( cp->blogPetType ),
                 cp->cousagiName );

        if ( cp->useWritingForPet ) {
            int yesNo = 0;

            if ( isatty( fileno( stdin ) ) )
                inputYesNo( &yesNo,
                            "あなたが BlogPet の代わりに記事を書きますか?" );
            else
                yesNo = YES;
            if ( yesNo == YES )
                r = makeEntryByYourself( cp->editorPath,
                                         cp->cousagiUserName,
                                         cp->cousagiName,
                                         cp->userNickName,
                                         cp->blogPetType,
#ifdef  USE_HAIKU
                                         cp->useHaikuWords,
#endif
                                         cp->useHatenaLink,
                                         useSimilarity,
                                         cp->blogType,
                                         title, body,
                                         cp->verboseFp );
        }

        if ( r == FALSE )
            return ( ret );
    }

    if ( cp->blogUserName[0] && cp->blogPassword[0] ) {
        if ( !strncmp( cp->endpointURL, "http://localhost/", 17 ) )
            setUseProxy( FALSE );
        setTargetURL( cp->endpointURL );
        setUserInfo( cp->blogUserName, cp->blogPassword );

        /* 投稿 */
        if ( cp->blogType == exciteBlog )
            return ( postArticleOnExcite( cp->blogUserName,
                                          cp->blogPassword,
                                          title, body, cp->categoryName,
                                          cp->forceWritingForPet ) );
                            /* exciteブログ専用投稿処理 */
        if ( cp->blogType == mixi )
            return ( postArticleOnMixi( cp->blogUserName,
                                        cp->blogPassword,
                                        title, body,
                                        cp->forceWritingForPet ) );
                            /* mixi専用投稿処理 */
        if ( cp->blogType == kinugasa )
            return ( postArticleOnKinugasa( cp->blogUserName,
                                            cp->blogPassword,
                                            title, body,
                                            cp->forceWritingForPet ) );
                            /* キヌガサ専用投稿処理 */
        if ( cp->blogType == echoo )
            return ( postArticleOnEchoo( cp->blogUserName,
                                         cp->blogPassword,
                                         title, body, cp->categoryName,
                                         cp->forceWritingForPet ) );
                            /* エコログ専用投稿処理 */
        if ( cp->blogType == fc2network )
            return ( postArticleOnFC2NETWORK( cp->blogUserName,
                                              cp->blogPassword,
                                              title, body,
                                              cp->forceWritingForPet ) );
                            /* FC2NETWORK専用投稿処理 */
        if ( cp->blogType == rakuten )
            return ( postArticleOnRakuten( cp->blogUserName,
                                           cp->blogPassword,
                                           title, body,
                                           cp->categoryName,
                                           cp->forceWritingForPet ) );
                            /* 楽天広場専用投稿処理 */
        if ( cp->blogType == colors )
            return ( postArticleOnColors( cp->blogUserName, cp->blogPassword,
                                          title, body,
                                          cp->forceWritingForPet ) );
                            /* COLORS専用投稿処理 */
        if ( cp->blogType == openPne )
            return ( postArticleOnOpenPNE( cp->blogUserName, cp->blogPassword,
                                           title, body,
                                           cp->forceWritingForPet ) );
                            /* OpenPNE専用投稿処理 */
        if ( cp->blogID[0] ) {
            if ( (cp->blogType == livedoor) ||
                 (cp->blogType == lovelog)  ||
                 (cp->blogType == sonet)    ||
                 (cp->blogType == ameblo)   ||
                 (cp->blogType == blogger)  ||
                 (cp->blogType == vox)      ||
                 (cp->blogType == hatena)   ||
                 (cp->blogType == atomAPI)     ) {
                ret = postArticleOnAtomAPI( cp->blogID,
                                            cp->blogType,
                                            title, body,
                                            cp->categoryName,
                                         // (char *)(cp->endpointURL),
                                            cp->blogPetType,
                                            cp->forceWritingForPet );
                if ( ret && (cp->blogType == livedoor) ) {
                    /* livedoor Blog のみの特別な処理 */
                    BOOL    yesNo = NO;

                    fputs( "記事を投稿しました。\n",
                           cp->verboseFp ? cp->verboseFp : stderr );
                    inputYesNo( &yesNo, "blog を再構築しますか?" );
                    if ( yesNo == YES ) {
                        /* (blog の管理画面経由で)強制的に再構築する */
                        rebuildOnLivedoor( cp->blogUserName, cp->blogPassword,
                                           cp->blogID );
                    }
                }
                return ( ret );
                            /* Atom API 系投稿処理 */
            }
            else
                return ( postArticleOnXMLRPC( cp->blogID, cp->blogType,
                                              title, body,
                                              cp->categoryName,
                                              cp->blogPetType,
                                              cp->forceWritingForPet ) );
                            /* XML-RPC API 系投稿処理 */
        }
    }

    /* 標準出力へ書き出し */
    printf( "題名: %s\n", utf2sjis( title ) );
    printf( "本文: %s\n", body );

    return ( ret );
}


/*
 *  usage
 *    cocousagi [-f 設定ファイル [-F]] [-h] [-l] [-b blogID] [-p] [-A]
 *              [-B blog種別 [-e エンドポイントURL] [-c 文字コード種別]]
 *              [-S[:アマゾンアソシエイトID]]
 *              [-u blogユーザ名[:パスワード[:オーナー名]]]
 *              [-U BlogPetサーバユーザ名[:パスワード]]
 *              [-t BlogPet種別]
 *              [-C カテゴリ名]
 *              [-n 記事に埋め込むユーザ名] [BlogPetの名前]
 *              [-E [エディタのパス名]]
 *
 *          -f 設定ファイル     指定した設定ファイルから設定情報を取得する
 *            設定ファイルのフォーマット
 *              cousagiUserName: BlogPetサーバ login 名
 *              cousagiPassword: BlogPetサーバ パスワード [-F 指定時、省略可]
 *              blogType:        blog種別   [ココログの場合のみ省略可]
 *              endpointURL:     XML-RPC API または Atom API エンドポイントURL
 *                                  [blog種別によりエンドポイントURLが一意に決
 *                                   まる場合(ココログ、ブログ人、livedoor
 *                                   Blog、Seesaa BLOG など)は省略可]
 *              blogUserName:    blog login 名        [省略可]
 *              blogPassword:    blog パスワード      [省略可]
 *              blogID:          blogID               [省略可]
 *              cousagiName:     BlogPetの名前        [省略可]
 *              charSet:         文字コード種別       [省略可]
 *              haiku:           俳句を埋め込むか否か [省略可]
 *              keywordLink:     「はてなダイアリー」キーワードリンクを
 *                               埋め込むか否か [省略可]
 *
 *                  blogType が省略されている場合は、対象blogを「ココログ」
 *                  とみなす。
 *
 *                  blogUserName, blogPassword, blogID の3つを
 *                  全て省略した場合は、生成した記事を標準出力に書き出す。
 *                  cousagiName を省略する場合は、コマンドライン引数で「Blog
 *                  Petの名前」を必ず指定すること。
 *
 *                  charSet が省略されている場合は、投稿用の文字コードを UTF-8
 *                  とみなして処理を行なう
 *
 *                  haiku, keywordLink は、各々、埋め込みたい場合は on、埋め込
 *                  みたくない場合は off を指定する。省略した場合は off を指定
 *                  したものとして取り扱う(が、コマンドラインで埋め込みを指定
 *                  された場合は、コマンドラインでの指定にしたがう)
 *
 *              -f オプションを指定しない場合は、上記の内容をキーボードから入
 *              力するためのプロンプトを順次表示する
 *
 *          -F 「BlogPetサーバ パスワード」、「ココログ パスワード」をキー
 *             ボード入力から取得することを指示する。-f オプションと同時に
 *             指定する
 *
 *          -h 記事に俳句を埋め込む
 *          -l 記事に「はてなダイアリーキーワード」リンクを埋め込む
 *
 *          -b blogID   blogID を指定する。設定ファイルによる指定よりコマン
 *                      ドラインでの指定の方を優先、適用する
 *
 *          -B blog種別 blog の種別を指定する。本パラメータを省略した場合は、
 *                      「ココログ」を指定したものとみなす
 *                 blog種別:
 *                   typepad       Typepad, Typepad France または TypePad 
 *                                 Germany
 *                   typepadJapan  Typepad Japan
 *                   cocolog       ココログ
 *                   cocologFree   ココログフリー
 *                   blogzine      ブログ人
 *                   newsHandler   News-Handler
 *                   seesaa        Seesaa BLOG
 *                   netLaputa     NetLaputa BLOG
 *                   blogG         269g
 *                   fc2blog       FC2BLOG
 *                   gooblog       goo Blog
 *                   msnSpaces     MSN Spaces (Windows Live Spaces)
 *                   jugemX        JUGEM (XML-RPC API)
 *                   drecomX       ドリコムブログ (XML-RPC API)
 *                   yaplogX       ヤプログ! (XML-RPC API)
 *                   webryX        ウェブリブログ (XML-RPC API)
 *
 *                   livedoor      livedoor Blog
 *                   lovelog       LOVELOG
 *                   sonet         So-net blog
 *                   ameblo        アメーバブログ
 *                   blogger       Blogger
 *                   vox           Vox
 *                   hatena        はてなダイアリー
 *
 *                   movableType   Movable Type または BOXERBLOG
 *                   bloggerAPI    blogger API サポート blog
 *                   metaWeblog    metaWeblog API サポート blog
 *                   mtAPI         MovableType API (+ 拡張 metaWeblog API)
 *                                 サポート blog
 *                   atomAPI       Atom API サポート blog
 *                   newPostOnly   metawebLog.newPost のみサポートしている
 *                                 blog (「BlogPet飼育箱」を含む)
 *                   unsupported   「BlogPet」による投稿が未対応の blog
 *
 *                  (以下の blog は XML-RPC API も Atom API も未対応であるため、
 *                   管理画面と直接対話する方式により投稿を実現している)
 *                   exciteBlog    exciteブログ
 *                   mixi          mixi内日記
 *                   kinugasa      キヌガサ内日記
 *                   echoo         エコログ
 *                   fc2network    FC2NETWORK内日記
 *                   rakuten       楽天広場 (楽天ブログ)
 *                   colors        COLORS内ダイアリー
 *                   openPne       OpenPNE内日記
 *
 *                -e エンドポイントURL
 *                     XML-RPC API または Atom API エンドポイント URL を指定
 *                     する。blog種別が movableType, bloggerAPI, metaWeblog,
 *                     mtAPI, atomAPI のとき、必ず指定すること。
 *                -c 文字コード種別
 *                     blog への投稿に使用する文字コードを指定する。blog種別が
 *                     movableType, bloggerAPI, metaWeblog, mtAPI, atomAPI の
 *                     とき、必ず指定すること。
 *                         文字コード種別:
 *                           UTF-8
 *                           Shift_JIS
 *                           EUC-JP
 *                           ISO-2022-JP
 *                         注意: livedoor Blog のように、“管理画面からの投稿
 *                               時は EUC-JP を使うが、 Atom API による投稿で
 *                               は UTF-8 を使う”、といったように、 投稿の仕
 *                               方によって使用する文字コードが異なる場合もあ
 *                               るので注意!!
 *
 *          -A 
 *             このオプションを指定しない場合は、1週間以上あけないと次回の投
 *             稿ができないように制限がかかる。この制限を外したいときに、-A
 *             オプションを指定する。ただし、blog種別の値によっては、この制限
 *             が働かないことがある。
 *
 *          -n ニックネーム
 *             通常、「BlogPet」の各記事にはユーザ名(blogUserName相当)が埋め
 *             込まれるが、blog によっては、blogUserName=ブログのディレクトリ
 *             名である場合があり、記事に埋め込むと不自然になることがある。
 *             本オプションを用いることで、記事中のblogUserName相当文字列を、
 *             指定したニックネームに置換することができる。
 *
 *          -p proxy 経由で通信を行なう (Internet Explorer の設定を流用、もし
 *             くは、以下の設定ファイルから設定を取得する)
 *                ファイル名: proxy.txt
 *                ファイルの中身: 以下の2行からなるテキストファイル
 *                    proxy サーバ名(または IPアドレス)
 *                    ポート番号
 *
 *          -uユーザ名[:パスワード]
 *          -uユーザ名 (パスワードを指定しない場合)
 *             ブログに login するユーザ名(ID、もしくはメールアドレス)、パス
 *             ワードを指定する
 *             ※注意事項 >> パスワードは : を含まないものにしてください。 <<
 *                (一般的に、パスワードには : は使えないはずですが、使えるシ
 *                 ステムがあっても不思議ではないので、: は意識的に使わない
 *                 ようにしてください。どうしても、: を使いたい場合は、パス
 *                 ワードをコマンドラインから与えるのはあきらめてください。
 *                 申し訳ありません)
 *
 *          -Uユーザ名[:パスワード]
 *             BlogPetサーバに login するユーザ名、パスワードを指定する
 *
 *          -S[:アマゾンアソシエイトID]
 *             BlogPetからの「おすすめ」を使用する。現時点では アマゾン のみ
 *             対応。アマゾンアソシエイトIDを指定しない場合は、私(tsupo)のアソ
 *             シエイトIDが自動的に挿入されます。ご了承ください。 
 *
 *          -t BlogPet種別
 *             BlogPet の種別を指定する。本パラメータを省略した場合は、
 *            「Blog Pet」を指定したものとみなす
 *                 BlogPet種別:
 *                   blogPet       Blog Pet
 *                   usagi         こうさぎ
 *                   neko          こねこ
 *                   inu           こいぬ
 *                   panda         こぱんだ
 *                   nezumi        こねずみ
 *
 *          -C カテゴリ名
 *             投稿記事のカテゴリ名を指定する (カテゴリ名の概念のある blog
 *             のみ対応)
 *             本オプションを省略した場合は、「注1」の考え方で、投稿するカテ
 *             ゴリを自動的に決定する。
 *             なお、現時点では、本オプションは XML-RPC API および Atom API
 *             を使って投稿する blog でのみ有効である。それ以外の blog への
 *             対応は将来の課題とする。また、複数カテゴリ指定は将来対応する
 *             予定。
 *
 *          -T テンプレートID
 *             投稿対象がウェブリブログの場合のみ有効。投稿記事(の固定リン
 *             クに対応するWebページ)に適用するデザインテンプレートのIDを指
 *             定する。
 *
 *          -E [エディタのパス名]
 *             BlogPet サーバとの通信が正常に行なえない場合、ユーザの指定し
 *             た文面を代わりに投稿する。「エディタのパス名」を指定すること
 *             で、文面の編集に使うエディタを指定することが可能。「エディタ
 *             のパス名」を省略した場合は、『メモ帳』を使用する。
 *
 *      注1: 「こうさぎ」という名前のカテゴリが存在すれば、カテゴリに「こうさ
 *           ぎ」を指定して投稿する(存在しない場合は、カテゴリ指定なしで投稿す
 *           る[BlogPet種別が「こうさぎ」の場合])
 *      注2: blog ID を指定しなかった場合、投稿可能な blog 一覧を取得し、選択
 *           肢が複数あれば、選択肢を表示する。選択肢が1つしかない場合は、当該
 *           blog に投稿する。
 */


int
main( int argc, char *argv[] )
{
    int             ret = 0;
    COUSAGI_INFO    *cp;        /* ここうさぎ設定情報 */
    char            *title = NULL;
    char            *body  = NULL;
#ifdef  _DEBUG
    BOOL            verbose = FALSE;
#endif

    /* 設定情報格納領域確保 */
    cp = (COUSAGI_INFO *)malloc( sizeof ( COUSAGI_INFO ) );
    if ( !cp ) {
        fputs( "メモリが確保できません。\n", stderr );
        return ( 255 );
    }

    /* 設定情報初期化 */
    initParam( cp );

    /* 設定情報構築 */
    if ( argc >= 2 )
        getParamFromArgv( argc, argv, cp );
                                /* コマンドライン引数から取得 */

    if ( cp->settingFilename[0] )
        getParamFromFile( cp ); /* 設定ファイルから取得       */

    getParamFromStdin( cp );    /* 標準入力から取得           */

    if ( cp->blogType == unsupported ) {
        /* 「BlogPet」未サポート blog の場合は、blog ユーザ名、パスワード */
        /* をクリア → BlogPet による blog への投稿を抑制(→ 結果を標準出 */
        /* 力に出力する)                                                  */
        cp->blogUserName[0] = NUL;
        cp->blogPassword[0] = NUL;
    }

    if ( (cp->blogType == mixi)       ||  /* mixi または kinugasa の場合は、*/
         (cp->blogType == kinugasa)   ||  /* キーワードリンクを強制的に off */
         (cp->blogType == fc2network) ||  /* にする                         */
         (cp->blogType == colors)     ||
         (cp->blogType == openPne)       )
        cp->useHatenaLink = 0;

    if ( cp->blogType == jugemX ) {
        if ( cp->blogUserName[0] ) {
            char    url[MAX_URLLENGTH];
            char    *q = strchr( cp->endpointURL, '$' );

            if ( q )
                q++;
            else
                q = ".jugem.jp/admin/xmlrpc.php";
            sprintf( url, "http://%s%s",
                     cp->blogUserName,
                     q );

            strcpy( cp->endpointURL, url );
        }
    }

    if ( (cp->blogType == msnSpaces) ||
         (cp->blogType == webryX)       ) {
        if ( cp->blogID[0] == NUL )
            strcpy( cp->blogID, "MyBlog" );
    }

    /* メモリの確保 */
    title = (char *)malloc( MAX_TITLE_LEN * 9 );
    if ( !title ) {
        fputs( "メモリが足りません。\n",
               cp->verboseFp ? cp->verboseFp : stderr );
        return ( 254 );
    }
    body  = (char *)malloc( MAX_BODY_LEN  * 9 + MAX_CONTENT_SIZE );
    if ( !body ) {
        free( title );
        fputs( "メモリが足りません。\n",
               cp->verboseFp ? cp->verboseFp : stderr );
        return ( 254 );
    }
    memset( title,  0x00, MAX_TITLE_LEN * 9 );
    memset( body,   0x00, MAX_BODY_LEN  * 9 + MAX_CONTENT_SIZE);

    /* 記事の生成、投稿 */
#ifdef  _DEBUG
    verbose = cp->verboseFp && (cp->verboseFp != stderr) ? TRUE : FALSE;
    if ( verbose == FALSE ) {
        if ( !(cp->verboseFp) )
            cp->verboseFp = stderr;
        setVerbose( TRUE, cp->verboseFp );
    }
#endif
    ret = makeEntry( cp, title, body );
    if ( ret == 1 )
        fputs( "たぶん、投稿に成功したと思います。\n",
               cp->verboseFp ? cp->verboseFp : stderr );
    else
        fputs( "投稿に失敗しました。\n",
               cp->verboseFp ? cp->verboseFp : stderr );
#ifdef  _DEBUG
    if ( verbose == FALSE )
        setVerbose( FALSE, NULL );
#endif

    /* メモリの解放 */
    free( body );
    free( title );

    if ( (cp->pause) && isatty( fileno( stdin ) ) ) {
        int c;

        fputs( "\n「ここうさぎ」を終了します。何かキーを押してください。",
               stderr );
        if ( (c = getchar()) == EOF )
            clearerr( stdin );
    }

    /* 設定情報格納領域解放 */
    encodeURL( NULL );
    base64( NULL, 0 );
    if ( cp->verboseFp && (cp->verboseFp != stderr) ) {
        time_t      t   = time( NULL );
        struct tm   *tm = localtime( &t );
        fprintf( cp->verboseFp,
                 "\n*** logging end:   %d/%02d/%02d %02d:%02d:%02d ***\n",
                 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
                 tm->tm_hour, tm->tm_min, tm->tm_sec );
        fclose( cp->verboseFp );
    }
    free( (void *)cp );
    setVerbose( FALSE, NULL );

    return ( ret );
}


/*
 *  ユーティリティ関数群
 */

/* Unicode(UCS-2BE) (%uXXXX or &#XXXXX;) → UTF-8 (xxyyzz) 変換 */
char    *
convUnicode( unsigned long code )
{
    unsigned short  a, b, c, d;
    static char     codeStr[6];

    if ( code < 0x0080 ) {
        a = (unsigned short)code;
        codeStr[0] = (char)(a & 0xFF);
        codeStr[1] = NUL;
    }
    else if ( code < 0x0800 ) {
        a = (unsigned short)(((code & 0x07C0) >> 6) | 0xC0);
        b = (unsigned short)(( code & 0x003F)       | 0x80);
        codeStr[0] = (char)(a & 0xFF);
        codeStr[1] = (char)(b & 0xFF);
        codeStr[2] = NUL;
    }
    else if ( code < 0x010000 ) {
        a = (unsigned short)(((code & 0xF000) >> 12) | 0xE0);
        b = (unsigned short)(((code & 0x0FC0) >>  6) | 0x80);
        c = (unsigned short)(( code & 0x003F)        | 0x80);
        codeStr[0] = (char)(a & 0xFF);
        codeStr[1] = (char)(b & 0xFF);
        codeStr[2] = (char)(c & 0xFF);
        codeStr[3] = NUL;
    }
    else {
        a = (unsigned short)(((code & 0x001C0000) >> 18) | 0xF0);
        b = (unsigned short)(((code & 0x0003F000) >> 12) | 0x80);
        c = (unsigned short)(((code & 0x00000FC0) >>  6) | 0x80);
        d = (unsigned short)(( code & 0x0000003F)        | 0x80);
        codeStr[0] = (char)(a & 0xFF);
        codeStr[1] = (char)(b & 0xFF);
        codeStr[2] = (char)(c & 0xFF);
        codeStr[3] = (char)(d & 0xFF);
        codeStr[4] = NUL;
    }

    return ( codeStr );
}

char    *
decodeNumericReference( char *src )
{
    char    *p    = src;
    char    *temp = (char *)malloc( strlen( src ) + 1 );

    if ( temp ) {
        unsigned long   l;

        temp[0] = NUL;
        while ( *p ) {
            if ( (*p == '&') && (*(p + 1) == '#') ) {
                l = atol( p + 2 );
                strcat( temp, convUnicode( l ) );
                p = strchr( p + 2, ';' ) + 1;
            }
            else {
                l = temp[0] ? strlen( temp ) : 0;
                temp[l]     = *p;
                temp[l + 1] = NUL;
                p++;
            }
        }
        strcpy( src, temp );

        free( temp );
    }

    return ( src );
}


/* 管理画面対話系blogでのカテゴリID取得 */
int
getCategoryIDfromStdin(
        int        *categoryID,  /* (I/O) カテゴリID             */
        const char *htmlSrcCode, /* (I) 管理画面htmlソースコード */
        int        minimumID,    /* (I) カテゴリID最小値         */
        const char *beginSelect, /* (I) <select>                 */
        const char *endSelect,   /* (I) </select>                */
        const char *beginOption, /* (I) <option>                 */
        const char *endOption,   /* (I) </option>                */
        int        charSet       /* (I) 文字コード               */
    )
{
    char    *p, *q, *r;
    char    buf[BUFSIZ];
    char    temp[MAX_CATNAMELENGTH];
    BOOL    done  = FALSE;
    int     defaultCatID = *categoryID;
    int     catID;
 // int     len11 = strlen( beginSelect );
    int     len12 = strlen( endSelect );
    int     len21 = strlen( beginOption );
    int     len22 = strlen( endOption );
    int     i = defaultCatID, numOfCategories;
    char    *categoryName;

    do {
        fputs( "\nカテゴリID: \n", stderr );
        numOfCategories = 0;

        p = strstr( htmlSrcCode, beginSelect );
        if ( p ) {
            do {
                while ( *p &&
                        ((*p == '\r') || (*p == '\n') ||
                         (*p == ' ')  || (*p == '\t')    ) )
                    p++;
                if ( !(*p) || !strncmp( p, endSelect, len12 ) )
                    break;

                p = strstr( p, beginOption );
                if ( p ) {
                    p += len21;
                    catID = atol( p );
                    if ( catID >= minimumID ) {
                        q = strchr( p, '>' );
                        if ( q && *++q ) {
                            r = strchr( q, '<' );
                            if ( r ) {
                                strncpy( temp, q, r - q );
                                temp[r - q] = '\0';

                                switch ( charSet ) {
                                case CHARSET_EUCJP:
                                    categoryName = euc2sjis( temp );
                                    break;
                                case CHARSET_ISO2022JP:
                                    categoryName = jis2sjis( temp );
                                    break;
                                case CHARSET_UTF8:
                                    categoryName = utf2sjis( temp );
                                    break;
                                case CHARSET_SHIFTJIS:
                                default:
                                    categoryName = temp;
                                    break;
                                }

                                if ( categoryName )
                                    break;

                                fprintf( stderr, "\t[%d] %s\n",
                                         ++numOfCategories,
                                         categoryName );
                            }
                        }
                    }

                    p = strstr( p, endOption );
                    if ( p )
                        p += len22;
                }
                else
                    break;
            } while ( !done );

            if ( numOfCategories <= 1 ) {
                i = 1;
                if ( numOfCategories == 0 )
                    fputs( "\t0\n", stderr );
                break;
            }
        }
        else
            break;

        fprintf( stderr, "[1-%d]: 選択してください: ", numOfCategories );
        p = fgets( buf, BUFSIZ - 1, stdin );
        if ( !p ) {
            clearerr( stdin );
            continue;
        }

        while ( (*p == ' ') || (*p == '\t') )
            p++;
        q = p;
        while ( *q )
            q++;
        q--;
        while ( (q > p) &&
                ((*q == '\n') || (*q == '\r') ||
                 (*q == ' ')  || (*q == '\t')    ) ) {
            *q-- = '\0';
        }
        if ( p && *p && (q >= p) ) {
            i = atol( p );
            if ( (i > 0) && (i <= numOfCategories) )
                done = TRUE;
        }
    } while ( !done );

    *categoryID = i;

    return ( *categoryID );
}

char    *
getCategoryNameFromStdin(
        char       *catID,       /* (O) カテゴリID (文字列)      */
        char       *categoryName,/* (I/O) カテゴリ名             */
        const char *htmlSrcCode, /* (I) 管理画面htmlソースコード */
        const char *beginSelect, /* (I) <select>                 */
        const char *endSelect,   /* (I) </select>                */
        const char *beginOption, /* (I) <option>                 */
        const char *endOption,   /* (I) </option>                */
        int        charSet       /* (I) 文字コード               */
    )
{
    char    *p, *q, *r;
    char    buf[BUFSIZ];
    char    temp[MAX_CATNAMELENGTH], *pp;
    BOOL    done  = FALSE;
 // int     len11 = strlen( beginSelect );
    int     len12 = strlen( endSelect );
    int     len21 = strlen( beginOption );
    int     len22 = strlen( endOption );
    int     i = 0, numOfCategories;

    do {
        fputs( "\nカテゴリID: \n", stderr );
        numOfCategories = 0;

        p = strstr( htmlSrcCode, beginSelect );
        if ( p ) {
            p += strlen( beginSelect );
            do {
                while ( *p &&
                        ((*p == '\r') || (*p == '\n') ||
                         (*p == ' ')  || (*p == '\t')    ) )
                    p++;
                if ( !(*p) || !strncmp( p, endSelect, len12 ) )
                    break;

                p = strstr( p, beginOption );
                if ( p ) {
                    p += len21;
                    q = strchr( p, '>' );
                    if ( q && *++q ) {
                        r = strchr( q, '<' );
                        if ( r ) {
                            if ( r - q < MAX_CATNAMELENGTH - 1 ) {
                                strncpy( temp, q, r - q );
                                temp[r - q] = '\0';
                            }
                            else {
                                strncpy( temp, q, MAX_CATNAMELENGTH - 1 );
                                temp[MAX_CATNAMELENGTH - 1] = '\0';
                            }

                            switch ( charSet ) {
                            case CHARSET_EUCJP:
                                pp = euc2sjis( temp );
                                break;
                            case CHARSET_ISO2022JP:
                                pp = jis2sjis( temp );
                                break;
                            case CHARSET_UTF8:
                                pp = utf2sjis( temp );
                                break;
                            case CHARSET_SHIFTJIS:
                            default:
                                pp = temp;
                                break;
                            }

                            if ( pp )
                                break;

                            while ( (pp[strlen(pp) - 1] == '\r') ||
                                    (pp[strlen(pp) - 1] == '\n') ||
                                    (pp[strlen(pp) - 1] == '\t') ||
                                    (pp[strlen(pp) - 1] == ' ')     )
                                pp[strlen(pp) - 1] = NUL;

                            fprintf( stderr, "\t[%d] %s\n",
                                     ++numOfCategories,
                                     pp );
                        }
                    }

                    p = strstr( p, endOption );
                    if ( p )
                        p += len22;
                }
                else
                    break;
            } while ( !done );

            if ( numOfCategories <= 1 ) {
                if ( numOfCategories == 0 )
                    fputs( "\t0\n", stderr );
                break;
            }
        }
        else
            break;

        fprintf( stderr, "[1-%d]: 選択してください: ", numOfCategories );
        p = fgets( buf, BUFSIZ - 1, stdin );
        if ( !p ) {
            clearerr( stdin );
            continue;
        }

        while ( (*p == ' ') || (*p == '\t') )
            p++;
        q = p;
        while ( *q )
            q++;
        q--;
        while ( (q > p) &&
                ((*q == '\n') || (*q == '\r') ||
                 (*q == ' ')  || (*q == '\t')    ) ) {
            *q-- = '\0';
        }
        if ( p && *p && (q >= p) ) {
            i = atol( p );
            if ( (i > 0) && (i <= numOfCategories) )
                done = TRUE;
        }
    } while ( !done );

    if ( i > 0 ) {
        numOfCategories = 0;
        p = strstr( htmlSrcCode, beginSelect );
        if ( p ) {
            done = FALSE;
            p += strlen( beginSelect );
            do {
                while ( *p &&
                        ((*p == '\r') || (*p == '\n') ||
                         (*p == ' ')  || (*p == '\t')    ) )
                    p++;
                if ( !(*p) || !strncmp( p, endSelect, len12 ) )
                    break;

                p = strstr( p, beginOption );
                if ( p ) {
                    p += len21;
                    q = strchr( p, '>' );
                    r = strchr( p, '"' );
                    if ( r && q && (r < q) )
                        q = r;
                    if ( q && *++q ) {
                        if ( (q - 1) - p < MAX_CATIDLENGTH - 1 ) {
                            strncpy( catID, p, (q - 1) - p );
                            catID[(q - 1) - p] = NUL;
                        }
                        else {
                            strncpy( catID, p, MAX_CATIDLENGTH - 1 );
                            catID[MAX_CATIDLENGTH - 1] = NUL;
                        }

                        r = strchr( q, '<' );
                        if ( r ) {
                            strncpy( temp, q, r - q );
                            temp[r - q] = '\0';

                            switch ( charSet ) {
                            case CHARSET_EUCJP:
                                pp = euc2sjis( temp );
                                break;
                            case CHARSET_ISO2022JP:
                                pp = jis2sjis( temp );
                                break;
                            case CHARSET_UTF8:
                                pp = utf2sjis( temp );
                                break;
                            case CHARSET_SHIFTJIS:
                            default:
                                pp = temp;
                                break;
                            }

                            if ( pp )
                                break;

                            while ( (pp[strlen(pp) - 1] == '\r') ||
                                    (pp[strlen(pp) - 1] == '\n') ||
                                    (pp[strlen(pp) - 1] == '\t') ||
                                    (pp[strlen(pp) - 1] == ' ')     )
                                pp[strlen(pp) - 1] = NUL;

                            ++numOfCategories;
                            if ( numOfCategories == i ) {
                                strcpy( categoryName, pp );
                                done = TRUE;
                                break;
                            }
                        }
                    }

                    p = strstr( p, endOption );
                    if ( p )
                        p += len22;
                }
                else
                    break;
            } while ( !done );
        }
    }

    return ( categoryName );
}


int
getCategoryID( int        *categoryID,  /* (I/O) カテゴリID             */
               const char *htmlSrcCode, /* (I) 管理画面htmlソースコード */
               int        minimumID,    /* (I) カテゴリID最小値         */
               const char *beginSelect, /* (I) <select>                 */
               const char *endSelect,   /* (I) </select>                */
               const char *beginOption, /* (I) <option>                 */
               const char *endOption,   /* (I) </option>                */
               int        charSet       /* (I) 文字コード               */
             )
{
    int     defaultCatID = *categoryID;
    BOOL    found        = FALSE;

    if ( *categoryID == defaultCatID ) {
        /* カテゴリ指定なし、またはデフォルトカテゴリ指定 */
        getCategoryIDfromStdin( categoryID, htmlSrcCode, minimumID,
                                beginSelect, endSelect,
                                beginOption, endOption,
                                charSet );
    }

    if ( *categoryID >= defaultCatID ) {
        char    *p, *q, *r;
        int     catID, cnt = 0;
     // int     len11 = strlen( beginSelect );
        int     len12 = strlen( endSelect );
        int     len21 = strlen( beginOption );
        int     len22 = strlen( endOption );

        p = strstr( htmlSrcCode, beginSelect );
        if ( p ) {
            do {
                if ( !strncmp( p, endSelect, len12 ) )
                    break;

                p = strstr( p, beginOption );
                if ( p ) {
                    p += len21;

                    q = strchr( p, '>' );           /* for JUGEM */
                    r = strstr( p, "value=\"" );    /* for JUGEM */
                    if ( r && (r < q) )             /* for JUGEM */
                        p = r + 7;                  /* for JUGEM */

                    catID = atol( p );
                    if ( catID != defaultCatID )
                        cnt++;
                    if ( cnt == *categoryID ) {
                        *categoryID = catID;
                        found = TRUE;
                    }
                    else {
                        p = strstr( p, endOption );
                        if ( p )
                            p += len22;
                    }
                }
                else
                    break;
            } while ( !found );
        }
    }

    if ( !found )
        *categoryID = defaultCatID; /* カテゴリ指定なし */

    return ( *categoryID );
}


int
getCategoryIDwithName(
        int        *categoryID,  /* (I/O) カテゴリID             */
        char       *categoryName,/* (O)   カテゴリ名             */
        const char *htmlSrcCode, /* (I) 管理画面htmlソースコード */
        int        minimumID,    /* (I) カテゴリID最小値         */
        const char *beginSelect, /* (I) <select>                 */
        const char *endSelect,   /* (I) </select>                */
        const char *beginOption, /* (I) <option>                 */
        const char *endOption,   /* (I) </option>                */
        int        charSet       /* (I) 文字コード               */
    )
{
    int     defaultCatID = *categoryID;
    BOOL    found        = FALSE;

    if ( *categoryID == defaultCatID ) {
        /* 未分類 */
        getCategoryIDfromStdin( categoryID, htmlSrcCode, minimumID,
                                beginSelect, endSelect,
                                beginOption, endOption,
                                charSet );
    }

    if ( *categoryID >= defaultCatID ) {
        char    *p, *q, *r;
        int     catID, cnt = 0;
     // int     len11 = strlen( beginSelect );
        int     len12 = strlen( endSelect );
        int     len21 = strlen( beginOption );
        int     len22 = strlen( endOption );

        p = strstr( htmlSrcCode, beginSelect );
        if ( p ) {
            do {
                if ( !strncmp( p, endSelect, len12 ) )
                    break;

                p = strstr( p, beginOption );
                if ( p ) {
                    p += len21;
                    catID = atol( p );
                    if ( catID != defaultCatID )
                        cnt++;
                    if ( cnt == *categoryID ) {
                        *categoryID = catID;
                        found = TRUE;
                        q = strchr( p, '>' );
                        if ( q && *++q ) {
                            r = strchr( q, '<' );
                            if ( r ) {
                                strncpy( categoryName, q, r - q );
                                categoryName[r - q] = '\0';
                            }
                        }
                    }
                    else {
                        p = strstr( p, endOption );
                        if ( p )
                            p += len22;
                    }
                }
                else
                    break;
            } while ( !found );
        }
    }

    if ( !found )
        *categoryName = NUL;

    return ( *categoryID );
}


char    *
getCategoryIDstring( 
        char       *catID,          /* (O) カテゴリID (文字列)      */
        const char *htmlSrcCode,    /* (I) 管理画面htmlソースコード */
        const char *beginSelect,    /* (I) <select>                 */
        const char *endSelect,      /* (I) </select>                */
        const char *beginOption,    /* (I) <option>                 */
        const char *endOption,      /* (I) </option>                */
        int        charSet          /* (I) 文字コード               */
    )
{
    char    categoryName[128];

    getCategoryNameFromStdin( catID, categoryName, htmlSrcCode,
                              beginSelect, endSelect,
                              beginOption, endOption,
                              charSet );

    return ( catID );
}


/* カテゴリ名を元にカテゴリIDを取得する */
char    *
getCategoryIDbyName(
        char       *catID,          /* (O) カテゴリID (文字列)      */
        const char *categoryName,   /* (I) カテゴリ名 (Shift_JIS)   */
        const char *htmlSrcCode,    /* (I) 管理画面htmlソースコード */
        const char *beginSelect,    /* (I) <select>                 */
        const char *endSelect,      /* (I) </select>                */
        const char *beginOption,    /* (I) <option>                 */
        const char *endOption,      /* (I) </option>                */
        int        charSet          /* (I) 文字コード               */
    )
{
    char    *p, *q, *r;
    char    temp[MAX_CATNAMELENGTH], *pp;
    char    value[MAX_CATNAMELENGTH];
    BOOL    done  = FALSE;
 // int     len11 = strlen( beginSelect );
    int     len12 = strlen( endSelect );
    int     len21 = strlen( beginOption );
    int     len22 = strlen( endOption );

    p = strstr( htmlSrcCode, beginSelect );
    if ( p ) {
        p += strlen( beginSelect );
        do {
            while ( *p &&
                    ((*p == '\r') || (*p == '\n') ||
                     (*p == ' ')  || (*p == '\t')    ) )
                p++;
            if ( !(*p) || !strncmp( p, endSelect, len12 ) )
                break;

            p = strstr( p, beginOption );
            if ( p ) {
                p += len21;
                q = strchr( p, '>' );
                if ( q && *++q ) {
                    r = strstr( p, "value=\"" );    /* for JUGEM */
                    if ( r && (r < q) )             /* for JUGEM */
                        p = r + 7;                  /* for JUGEM */
                    strncpy( value, p, (q - 1) - p );
                    value[(q - 1) - p] = NUL;
                    if ( value[strlen(value) - 1] == '"' )
                        value[strlen(value) - 1] = NUL;

                    r = strchr( q, '<' );
                    if ( r ) {
                        strncpy( temp, q, r - q );
                        temp[r - q] = '\0';

                        switch ( charSet ) {
                        case CHARSET_EUCJP:
                            pp = euc2sjis( temp );
                            break;
                        case CHARSET_ISO2022JP:
                            pp = jis2sjis( temp );
                            break;
                        case CHARSET_UTF8:
                            pp = utf2sjis( decodeNumericReference( temp ) );
                            break;
                        case CHARSET_SHIFTJIS:
                        default:
                            pp = temp;
                            break;
                        }

                        if ( pp )
                            break;

                        while ( (pp[strlen(pp) - 1] == '\r') ||
                                (pp[strlen(pp) - 1] == '\n') ||
                                (pp[strlen(pp) - 1] == '\t') ||
                                (pp[strlen(pp) - 1] == ' ')     )
                            pp[strlen(pp) - 1] = NUL;

                        if ( !strcmp( pp, categoryName ) ) {
                            strcpy( catID, value );
                            done = TRUE;
                            break;
                        }
                    }
                }

                p = strstr( p, endOption );
                if ( p )
                    p += len22;
            }
            else
                break;
        } while ( !done );
    }

    return ( catID );
}


/* カテゴリ一覧の取得 */
int
getCategoryIDs(
        long         *numOfCategories,  /* (I) 取得するカテゴリの数     */
                                        /* (O) 取得したカテゴリの数     */
        CATLISTINF_t categories,        /* (O) 取得したカテゴリ情報     */
        const char   *htmlSrcCode,      /* (I) 管理画面htmlソースコード */
        const char   *beginSelect,      /* (I) <select>                 */
        const char   *endSelect,        /* (I) </select>                */
        const char   *beginOption,      /* (I) <option>                 */
        const char   *endOption,        /* (I) </option>                */
        int          charSet            /* (I) 文字コード               */
    )
{
    int     numOfCats = 0;
    char    *p, *q, *r;
    char    temp[MAX_CATNAMELENGTH], *pp;
    BOOL    done  = FALSE;
 // int     len11 = strlen( beginSelect );
    int     len12 = strlen( endSelect );
    int     len21 = strlen( beginOption );
    int     len22 = strlen( endOption );

    p = strstr( htmlSrcCode, beginSelect );
    if ( p ) {
        p += strlen( beginSelect );
        do {
            while ( *p &&
                    ((*p == '\r') || (*p == '\n') ||
                     (*p == ' ')  || (*p == '\t')    ) )
                p++;
            if ( !(*p) || !strncmp( p, endSelect, len12 ) )
                break;
            if ( !(*p) != '<' ) {
                q = strchr( p, '<' );
                if ( q && (endSelect[0] == '<')      &&
                     !strncmp( q, endSelect, len12 )    )
                    break;
            }

            p = strstr( p, beginOption );
            if ( p ) {
                p += len21;
                q = strchr( p, '>' );
                if ( q && *++q ) {
                    r = strchr( q, '<' );
                    if ( r ) {
                        strncpy( temp, q, r - q );
                        temp[r - q] = '\0';

                        switch ( charSet ) {
                        case CHARSET_EUCJP:
                            pp = euc2sjis( temp );
                            break;
                        case CHARSET_ISO2022JP:
                            pp = jis2sjis( temp );
                            break;
                        case CHARSET_UTF8:
                            pp = utf2sjis( decodeNumericReference( temp ) );
                            break;
                        case CHARSET_SHIFTJIS:
                        default:
                            pp = temp;
                            break;
                        }

                        if ( pp )
                            break;

                        while ( (pp[strlen(pp) - 1] == '\r') ||
                                (pp[strlen(pp) - 1] == '\n') ||
                                (pp[strlen(pp) - 1] == '\t') ||
                                (pp[strlen(pp) - 1] == ' ')     )
                            pp[strlen(pp) - 1] = NUL;

                        sprintf( categories[numOfCats].categoryId, "%08d", numOfCats );
                        strcpy( categories[numOfCats].categoryName, pp );
                        numOfCats++;
                        if ( numOfCats >= *numOfCategories )
                            break;
                    }
                }

                p = strstr( p, endOption );
                if ( p )
                    p += len22;
            }
            else
                break;
        } while ( !done );
    }

    *numOfCategories = numOfCats;
    return ( numOfCats );
}
