2009.06.26

XOOPSメモ:d3forumの新着リストと未承認投稿

d3forumでフォーラム毎の承認待ち件数なんてやってたら、新着リスト(投稿リスト)に、承認待ち投稿のタイトルが表示されてしまうことが気になりだした。

下調べ1:この辺は、d3forum_block_list_posts.html というテンプレートで表示をコントロールしているようである。
表示部分は

<{foreach item=post from=$block.posts}>
<li style="margin:1px;"><a href="<{$block.mod_url}>/index.php?post_id=<{$post.id}>">
<{$post.subject}></a> <{$post.uname}> <{$post.post_time_formatted}></li>
<{/foreach}>

とある(私はstyle属性を削除した:後々は外部CSSファイルで調整する方が楽なのだろうと思ったため)。

下調べ2:データベースを覗いてみるに、XXXXX_forum_posts、_topics、_forumsのようなテーブルがある。投稿は XXXXX_forum_posts で、タイトルや、承認済みかどうかなどかはこのテーブルに格納されている。不可視かどうかは invisible(不可視ならTRUEであろう)、未承認かどうかは approval(承認済みならTRUEなのであろう)というフィールドで判定できるようである。(忙しい人はまとめへ

じゃぁってことで、見よう見まねで条件を加えてみる。

<{if $post.approval && ! $post.invisible}>
     <li><a href="<{$block.mod_url}>/index.php?post_id=<{$post.id}>"><{$post.subject}></a>
<{$post.uname}> <{$post.post_time_formatted}></li>
<{/if}>

ダメなようである。値はどうなってるんだろう? と思い、条件文を外して確認。

<li><{$post.invisible}><{$post.approval}><a href="<{$block.mod_url}>/index.php?post_id=<{$post.id}>"><{$post.subject}></a> <{$post.uname}> <{$post.post_time_formatted}></li>

全部0(すなわちFALSE、というか初期値、というか値が取得されていない?)。何かが違う。

機能の作業を思い出してみるに、この辺は多分SQLでテーブル全体ではなく必要なフィールドだけを取り出しているに違いない。そしてここでは、invisibleや approvalは必要とされていなかったのではなかろうか。私の考えている用途と違うのかもしれないが、でも、私は invisibleや approvalが欲しい。

テンプレートやスタイルシートという話ではなさそうなのでソースを見るしかなさそうである。ひとまず、URLで指定されている/modules/d3forum/index.phpを見る。

require XOOPS_TRUST_PATH.'/modules/'.$mytrustdirname.'/main.php' ;という行に目をつける。そうか、XOOPS_TRUST_PATH下にあるファイルか。d3モジュールというのはそういうことになっているという話だったけど、なるほどそういうことか。

じゃぁってことで、とりあえず XOOPS_TRUST_PATHのmodules/d3forum/main.phpを見る。

// fork each pages
$page = preg_replace( '/[^a-zA-Z0-9_-]/' , '' , @$_GET['page'] ) ;
if( file_exists( "$mytrustdirpath/main/$page.php" ) ) {
     include "$mytrustdirpath/main/$page.php" ;
} else {
     include "$mytrustdirpath/main/index.php" ;
}

というあたりに目をつける。

XOOPS_TRUST_PATHのmodules/d3forum/main/を見る。

なんか違うっぽいと思う。

XOOPS_TRUST_PATHのmodules/d3forum/blocks/というディレクトリを見つける。

block_functions.php というファイルがあるのでそれを見る。

function b_d3forum_list_posts_show( $options ) を見つける。

SQL文のあたりを眺める。

$sql = "SELECT p.post_id, p.subject, p.votes_sum, p.votes_count, p.post_time, p.uid, f.forum_id, f.forum_title FROM…

から察するに、やはり、invisibleや approvalはない。他方、フォーラムIDやフォーラムタイトルは取得しているのだな、と思う。
(vote=役に立った云々の投票に関するやつだろうきっと、はむしろいらないんだけどなぁとも思うが何かするつもりはとりあえずない)

試しに、列名 forum_title をそのまま表示に使ってみる。

<li><{$post.forum_title}><a href="<{$block.mod_url}>/index.php?post_id=<{$post.id}>"><{$post.subject}></a> <{$post.uname}> <{$post.post_time_formatted}></li>

なるほどフォーラム名が表示される。

じゃぁってことで、SQL文に invisibleと approvalを追加してみる(SELECT部分、p.invisible, p.approval。FROM句で投稿データのテーブル名を p に割り当ててある模様)。

$sql = "SELECT p.post_id, p.subject, p.votes_sum, p.votes_count, p.post_time, p.uid, p.invisible, p.approval, f.forum_id, f.forum_title FROM…

これだけではダメなようである。おちついて前後のソースを見る。

while( $post_row = $db->fetchArray( $result ) ) {
   $post4assign = array(
        'id' => intval( $post_row['post_id'] ) ,
        'subject' => $myts->makeTboxData4Show( $post_row['subject'] ) ,
        'forum_id' => intval( $post_row['forum_id'] ) ,
        'forum_title' => $myts->makeTboxData4Show( $post_row['forum_title'] ) ,
        'votes_count' => $post_row['votes_count'] ,
        'votes_sum' => intval( $post_row['votes_sum'] ) ,
        'post_time' => intval( $post_row['post_time'] ) ,
        'post_time_formatted' => formatTimestamp( $post_row['post_time'] , 'm' ) ,
        'uid' => intval( $post_row['uid'] ) ,
        'uname' => XoopsUser::getUnameFromId( $post_row['uid'] ) ,
   ) ;
   $block['posts'][] = $post4assign ;
}

ああここか、と思う。

じゃぁってことで

while( $post_row = $db->fetchArray( $result ) ) {
   $post4assign = array(
        'id' => intval( $post_row['post_id'] ) ,
        'subject' => $myts->makeTboxData4Show( $post_row['subject'] ) ,
        'forum_id' => intval( $post_row['forum_id'] ) ,
        'forum_title' => $myts->makeTboxData4Show( $post_row['forum_title'] ) ,
        'votes_count' => $post_row['votes_count'] ,
        'votes_sum' => intval( $post_row['votes_sum'] ) ,
        'post_time' => intval( $post_row['post_time'] ) ,
        'post_time_formatted' => formatTimestamp( $post_row['post_time'] , 'm' ) ,
        'uid' => intval( $post_row['uid'] ) ,
        'uname' => XoopsUser::getUnameFromId( $post_row['uid'] ) ,
        'invisible' => intval( $post_row['invisible'] ) ,
        'approval' => intval( $post_row['approval'] ) ,
   ) ;
   $block['posts'][] = $post4assign ;
}

てな感じで invisibleと approval の部分を追加(※arrayの中のラスト2行)。

よし。あらためて、d3forum_block_list_posts.html テンプレートでinvisibleと approval の値を表示してみる。

<li><{$post.invisible}><{$post.approval}><a href="<{$block.mod_url}>/index.php?post_id=<{$post.id}>"><{$post.subject}></a> <{$post.uname}> <{$post.post_time_formatted}></li>

0や1がセットされている様子が見受けられる。万歳。

なので、そうだなぁ、未承認や不可視の発言は存在しないがごとく、という表示でいいんじゃなかろうか。

<{if $post.approval && ! $post.invisible}>
     <li><a href="<{$block.mod_url}>/index.php?post_id=<{$post.id}>"><{$post.subject}></a> <{$post.uname}> <{$post.post_time_formatted}></li>
<{/if}>

ここは管理者の好みと、参加者次第だよね。「未承認投稿」と表示した方がわかりやすいという場と、そういうのは見せないでおいてくれるかな、という場とあるからね。

まぁそれはさておき、やったことのまとめ

ファイル名:XOOPS_TRUST_PATHのmodules/d3forum/blocks/block_functions.php

(1)function b_d3forum_list_posts_show( $options ) にて、SQL文に p.invisible, p.approval を追加。

$sql = "SELECT p.post_id, p.subject, p.votes_sum, p.votes_count, p.post_time, p.uid, p.invisible, p.approval, f.forum_id, f.forum_title FROM…

(2)同じくfunction b_d3forum_list_posts_show( $options ) にて、$post4assign = array(の最後に invisibleと approvalの行を追加。

while( $post_row = $db->fetchArray( $result ) ) {
   $post4assign = array(
        'id' => intval( $post_row['post_id'] ) ,
        'subject' => $myts->makeTboxData4Show( $post_row['subject'] ) ,
        'forum_id' => intval( $post_row['forum_id'] ) ,
        'forum_title' => $myts->makeTboxData4Show( $post_row['forum_title'] ) ,
        'votes_count' => $post_row['votes_count'] ,
        'votes_sum' => intval( $post_row['votes_sum'] ) ,
        'post_time' => intval( $post_row['post_time'] ) ,
        'post_time_formatted' => formatTimestamp( $post_row['post_time'] , 'm' ) ,
        'uid' => intval( $post_row['uid'] ) ,
        'uname' => XoopsUser::getUnameFromId( $post_row['uid'] ) ,
        'invisible' => intval( $post_row['invisible'] ) , //added
        'approval' => intval( $post_row['approval'] ) , //added
   ) ;
   $block['posts'][] = $post4assign ;
}

(3)これで、d3forum_block_list_posts.html テンプレートでinvisibleと approval の値を利用できるようになりました。めでたい。

取得する列を増やした追加しただけなので、副作用は特にないはず(もちろん、処理に必要なメモリはその分増えるので、厳密に考えれば処理スピードに影響がでるかもしれないけど、今回の場合はまー影響ないっしょ、と思える範囲内だと思います)。

| | Comments (0)

2009.06.24

XOOPSメモ:d3forumでフォーラム毎の承認待ち件数

d3forumモジュールの承認待ち件数取得プラグインwaiting.plugin.php (XOOPS_TRUST_PATHのmodules/d3forum/include内。拡張承認待ちコンテンツwaitingモジュールから呼び出される)。XOOPSを初めてもうすぐ3週間、d3forumと格闘し敗れ再びインストールして2日。試行錯誤の末作成。なくなったら嫌だからメモ。

データ件数が少ない時しかだめだろうなぁ。

  • forumタイトルを別の手段で表示するなら2つめのJOINは不要
  • forumリストで回してSQLを発行する方が良いと思う

……だけどよくわからないからSQL1回でごりおししました。ご了承ください。

ダウンロード waiting.plugin.php (1.1K)

<?php

function b_waiting_d3forum( $mydirname )
{
$db =& Database::getInstance();
$ret = array();

$sql = "SELECT ".$db->prefix($mydirname."_topics").".forum_id, forum_title, ".
   " COUNT( ".$db->prefix($mydirname."_topics").".forum_id ) AS waiting_count ,MIN( post_id ) AS post_id".
   " FROM (".$db->prefix($mydirname."_posts")." LEFT JOIN ".$db->prefix($mydirname."_topics").
   " ON ".$db->prefix($mydirname."_posts").".topic_id = ".$db->prefix($mydirname."_topics").".topic_id)".
   " INNER JOIN ".$db->prefix($mydirname."_forums").
   " ON ".$db->prefix($mydirname."_topics").".forum_id = ".$db->prefix($mydirname."_forums").".forum_id".
   " WHERE ".$db->prefix($mydirname."_posts").".approval = 0".
   " GROUP BY ".$db->prefix($mydirname."_topics").".forum_id";

if ($recs = $db->query($sql)){
while ($result = $db->fetchArray($recs)){
$ret[] = array(
'adminlink' => XOOPS_URL.'/modules/'.$mydirname.'/index.php?post_id='.intval($result['post_id']) ,
'pendingnum' => intval($result['waiting_count']) ,
'lang_linkname' => $result['forum_title'] //._PI_WAITING_WAITINGS ,
) ;
}
}

return $ret ;
}
?>

| | Comments (0)

2009.06.05

[TeraTerm] SSHで自動ログインするマクロ

SSH2対応 かつ UTF-8対応の TeraTerm: http://sourceforge.jp/projects/ttssh2/

以下のマクロを実行すると、初回だけユーザー名とパスワードを入力するダイアログが表示される。
C:\Data\myserver.dat というファイルが作られ(ファイル名は何でもよい)、ユーザー名とパスワードが C:\Data\myserver.dat に暗号化された状態で保存される。
ファイル名を myserver.ttl で作成、右クリック→プログラムから開く で、TeraTerm.exe と一緒にインストールされるTTPMacro (C:\Program Files\teraterm\ttpmacro.exe)を選択。「この種類のファイルを開くときは、選択したプログラムをいつも使う」を有効にすれば、ダブルクリックなり、コマンドラインで「myserver.ttl」を実行するなりで、さくっとログインできる。おめでとう。


hostname = 'XXX.XXX.XXX.XXX'
getpassword '
C:\Data\myserver.dat' 'UserName' myname
getpassword '
C:\Data\myserver.dat' 'Password' mypassword
msg = hostname
strconcat msg ':22 /ssh /2 /auth=password /user='
strconcat msg myname
strconcat msg ' /passwd='
strconcat msg mypassword
connect msg

西村自宅構成:ThinkPad(WindowsXP) から MacBookに、Teratermでログインできると便利.だ、ということで、

STEP1c:\bat\MacBook.ttl」に上記マクロを保存(c:\bat にはPATHが通っている状態)

STEP2 環境変数 PATHEXT に .ttl を追加

STEP3 コマンドラインで \> macbookmac[TAB][Enter]) でMacBookにログインしたTeraTermが起動

コマンドラインで作業しているとは限らないので、クリックしやすい場所にショートカットも作ってあります。

| | Comments (0)

2009.05.29

nisimura.info閉鎖します

メンテナンスできなくなって久しいので、この際、www.nisimura.info を閉鎖することにしました。
古い日記もこちらに移動し、仕事リストはココログ内につくりました。→ Works

これからもよろしくお願いします。

| | Comments (0)

2009.05.19

REGZA外付けハードディスク交換(作業記録)

*2009年04月14日の日記をそのままコピー*
REGZA37Z7000の録画用に、対応が謳われていないHDDを使用していたらディスクの管理領域が壊れてしまったようだ。それ以前から、使用していない時間が数時間以上あると認識に失敗するなど、HDDの省電力がらみで何かよろしくない状態だった様子(REGZA側はHDDについては省電力モードはオフにしてある)。

現象
・「未登録のHDDが検出されました」というメッセージが出て、録画済みデータの参照ができなくなった
※再登録すると、新規ディスクという扱いになりフォーマットされるため録画データが削除されてしまう

やりたいこと、できたこと
・ ”未登録”となってしまったHDDを修復し、録画済みデータを再生できるようにしたい
・ エラーが起こるHDDの内容をそっくりそのまま別のHDDにコピーし、今後はそちらのHDDを使うようにしたい

準備した物
・新しいHDD (対応機器を確認するべし。kakaku.comでのトラブル相談を見ている限りではBuffaloの方がよさそうです。2009/4/13現在)
XFSファイルシステムを扱うことができるPC
(具体的には、KNOPPIXのDVD版をダウンロードし、DVDに焼き、そのDVDでThinkPad X41を起動しました)(多分CD版でも大丈夫です)

やったこと
1. 新しいHDDをREGZAに接続、画面に従って登録およびフォーマットを行う(修復のみの場合、この作業は不要)
2. 設定→レグザリンク設定→USBハードディスク設定→機器の取り外しを実行してからHDDをREGZAから取りはずす(USBハードディスク接続の作法はREGZAのマニュアルに従っておく)

ここからはLinux(ThinkPad X41+KNOPPIX5.3.1DVD)
3. 修復したいHDDと新しいHDDを接続した状態でLinuxを起動

以下、修復したいHDDを/dev/sdb1、新しいHDDを/dev/sda1とします
("sdb1"や"sda1"は、KNOPPIXの場合、デスクトップアイコンで確認できるはず。できない場合はdmesgコマンドの実行結果をじっと見て($ dmesg | less)それっぽい所を探す)
4. 修復:sudo xfs_repair -Lv /dev/sdb1実行、終わるのをじっと待つ

修復だけで良い場合、この作業のみでKNOPPIXを終了させ、REGZAに再接続。多分これで認識されるようになるはず(私は2回これで大丈夫になった:ああ、2回ダメになったのさ)。だめだったら他の手を探すかあきらめてフォーマットしちゃってください。

5. 移行:su実行後、
# xfsdump -J - /media/sdb1 | xfsrestore -J -p 120 - /media/sda1

xfsdumpで旧HDDの内容を吸い上げxfsrestoreで新HDDへ書き込んでいます。 -Jはジャーナルを更新しないというオプション、-p 120で2分おきに経過を表示しています。

6. KNOPPIXを終了させ、新HDDをREGZAに接続し、REGZAの電源ON。

7. 上手くいけば何もメッセージが出ないはず(登録済みのHDDが接続されている状態なので)。録画リストが表示できたらめでたしめでたし。

ものすごく参考にさせていただいたページ。ありがたや。
REGZAのUSB HDDが未登録に
xfsdump,xfsrestoreでXFSのディスクコピー
xfs パーティションを手抜き操作する方法

| | Comments (0)

REGZA外付けハードディスク交換(作業に至るまでのいきさつなど、単なるドタバタの記録)

*2009年04月14日の日記をそのままコピー*


*REGZA 37Z7000で使っていたHDDがエラーになったので修復しました。ついでに別のHDDに内容をそっくりそのまま移行しました。という話です。実作業は次の日記へ*

うちの液晶テレビREGZA 37Zは「自分でUSBハードディスクを用意すれば録画できますよ」という品。対応HDDが指定されているのは知っていたけど、とりあえず手持ちのディスクを使ってみた(Western DigitalのMy Book 500GB)。「なんだ使えるじゃん」とそのまま使っていたけれどどうも調子が悪い。録画リストを見ようとしてもHDDが眠っていて1回目は必ず失敗するとか、”いつでもニュース”が録画できていないとか。どうも省電力のあたりが問題らしい(推奨機種であるところのIOデータのHDDも省電力がらみでトラブルがちょいちょいありkakaku.comを賑わせている模様)。

まいったなーと思っていたら、とうとう「未登録のHDDが検出されました」というメッセージに。「登録しますか?」に「はい」と答えると、続いて初期化されることになる。まーたいしたもの録画してるわけじゃぁないけど、なんか嫌だし、毎度これじゃ困る。来月はNHK教育で冬の絵空が放映されるんだし。まぁ素直に推奨機種買っとくか。Buffaloの1TBが1万弱だよ。→Amazonさんにお願いする。明日届くらしい。Amazonすげぇ。これが昨日の朝。

じゃぁ、現在とらぶってるHDDは、またMacに戻せばいいわけですね(元々バックアップやら作業やらに使っていたHDDだった)。じゃぁその前にちょっといじってみてもいいですね。まだ見ていない録画がいくつかあるしね。ここでいじっておけば、先々、Buffaloのディスクに何かトラブルがおきても対処できるかもしれん。

で、調べた。

・REGZAで使うHDDはどうもxfsフォーマットらしい
・データはREGZA本体のmacアドレスで暗号化されているらしい
・したがって録画したREGZA本体でしか再生できないが、ファイルのコピー等はできるらしい(LAN接続されたPCの共有ディスクにも録画できるんだもんそりゃそうだ)

・Linuxならxfsが気軽に扱えるはずである。ThinkPadをCD/DVDで起動すればよろしい
(VMware Fusionだとどうなるんだろうなーという実験はまだしていない)

さてはじめました。

xfsを扱えるOS、最初はサイズが小さくて扱いやすいKNOPPIXのCD版。MacでダウンロードしてCDを焼いてThinkPadに持ってゆく。

KNOPPIX起動。HDDは起動時に接続し電源を入れておく。
世間ではこの段階でKNOPPIXのデスクトップにハードディスクのアイコンが表示されているようだが、わたくしのKNOPPIXでは出ていない。ハードディスクが物理的にどうにかなってしまったのだろうか。

dmesgコマンドで確認する。USBや/devなどを検索してみると、WD My Bookなどと表示。なんとなく/dev/sdb1っぽい。ハードウェアは生きているのか?

とりあえずfdiskしてみる(fdisk /dev/sdb1)。pコマンドで情報を見ると「EFI GPT」とある。GUIDパーティションテーブル。はぁ。

じゃぁってことで、partedコマンドを使ってみる(parted /dev/sdb1)。

Filesystem表示が空欄。なぜ?(magic numberのあたりが壊れていたようだ。そのようなメッセージが出ていたけれどキャプチャ失敗してたしょんぼり)xfsとか表示されるんじゃないの? 違うの? いかんせんpartedコマンドを使ったことがないのでよくわからない。KNOPPIXのCD版はxfs非対応とか? いやカーネル標準のはずだし? ここはDVD版にするべき?(ISOイメージをダウンロードするのでDVDは避けたい)

うーむと思って検索してみたら、GParted Live CDなるものがある。これならxfs行けるでしょ。大丈夫でしょ。じゃぁってことでISOイメージをダウンロード、CDを焼いてふたたび起動。

グラフィカルなpartedであるGPartedがいきなり起動してくれてうれしいが、やっぱりファイルシステム不明。うーむ。

この段階で、xfsで管理されているHDDがうちには1つもない(多分)ため、正解がわからない。KNOPPIX(CD)なりGParted Live CDなりでxfsという表示を確認できれば、「じゃぁこのHDDが悪いのね」とわかるけど、いかんせんxfs非対応時代のLinuxを使っていた記憶もあるだけに「対応してるの?」という不安がぬぐえない。元のフォーマットがMacOSというのも微妙にひっかかっている。

よしわかった。KNOPPIXでxfs用のコマンドで修復したという人がいるんだから、じゃぁ、KNOPPIXのDVDで試そう。それでFilesystemが不明のままだったら、もう、xfsだと断定してリカバーコマンドを実行しちゃおう。

で、4.3GBのISOイメージファイルをダウンロードしてる途中にひかり電話の工事が入ってみたり(ルータつなぎかえ)、
DVDで起動しようとしたらエラーになったので再度ダウンロードしてみたり、もう一度DVDを焼いてみたり、
結果「どうもこれはダウンロードのエラーでもなくDVDを焼くときのエラーでもなく、この外付けのDVDドライブのせい・またはThinkPad X60のせい」という結論に至ったり、
じゃぁってことでThinkPad X41用のDock(DVDドライブがくっついている。X60用のDockは持ってない)を探し出したり

という大変に無駄な時間を過ごす。

そして、KNOPPIXのDVDで起動、partedでFilesystemが空白であることを見、そして、ああもういいですよ、とxfsの修復コマンドを実行してしまう(xfs_repair -Lv /dev/sdb1)。

待つことしばし。特段嫌なメッセージも出ず終了。partedコマンドではFilesystemにxfsと表示され「ああやっぱりこう表示されるのか」と確認。そしてREGZAに接続。

じゃじゃーん、ちゃんと認識されましたよーだ!

ってことで満足したのでこのHDDについては終了。Buffaloのディスクが届くのを待つことに。
(※翌日すなわち今日、問題のHDDの内容を新しく届いたBuffaloのディスクにごっそりコピーして、何事もなかったかのように今までの録画データに新しい録画を追加してゆくことができましたとさ:作業内容のみを別にまとめます)

| | Comments (0)

2008.12.09

シェルスクリプト・トレーニング 第12回:getopts

シェルスクリプト・トレーニング 第12回
getoptsでスクリプトのオプションを処理する
http://www.techworld.jp/channels/desktop/102774/

公開されました。

| | Comments (0)

2008.12.03

がむしゃらに作ってみたスクリプトの改訂版

決着をつけておこうということで、スクリプト修正。

AppleScriptで文字列を処理する(2)を見ながら、見よう見まねで。あらゆるところアナゴな感じで。


tell application "Safari"
	open location "http://info.ddo.jp/remote_addr.php"
	set theIP to text of document 1
	set pos to (offset of ":" in theIP) + 1
	set theIP to text pos thru length of theIP as text
	open location "http://free.ddo.jp/dnsupdate.php?dn=XXXXXXXX&ip=" &
		theIP & "&pw=XXXXXXXX"
end tell

なんとなくSafariを使う処理じゃないんだろうな、と思うに至る。
さて、リファレンスとか読みますよ。でも今日はここでおしまい。

| | Comments (0)

Dynamic DO!.jp(無料用)のIPアドレスを変更するシェルスクリプト

本体は1行。処理結果はddnsupdateresult.txtに保存(curl -o ファイル名)。

#! /bin/bash
curl -s -o ddnsupdateresult.txt "http://free.ddo.jp/dnsupdate.php?dn=XXXXXXXX&ip=$(curl -s http://info.ddo.jp/remote_addr.php | cut -s -d: -f2)&pw=XXXXXXXX"

やっていないけど1:
ddnsupdateresult.txtに「FAIL:」という文字列が出力されていたらエラー、「SUCCESS:」という文字が出力されていたら成功、という判定でいいんじゃなかろうか。

やっていないけど2:
curlコマンドがない場合、wgetコマンドでも同じようなことができるはず(結果を標準出力に出すには、ファイル名として「-」を指定、つまり「wget -O - "http://……"」で良いはず)。

私は、とりあえず結果を画面に出力させようと思ったので、curlの-oオプションを外し、iconvで文字コードを変換してsedでタグを除去。

#! /bin/bash
curl -s "http://free.ddo.jp/dnsupdate.php?dn=XXXXXXXX&ip=$(curl -s http://info.ddo.jp/remote_addr.php | cut -s -d: -f2)&pw=XXXXXXXX" | iconv -f eucJP -t UTF8 | sed "s/<[^>]*>//g;/^$/d"

プロセス

1.IPアドレス更新には、ドメイン名とパスワードの他に現在のグローバルIPアドレスが必要
2.Dynamic DO!.jpでは「http://info.ddo.jp/remote_addr.php」でグローバルIPアドレスが取得できるようにしてくれているようだ

コマンドラインで実行してみる(以下ずっとコマンドライン)。

curl http://info.ddo.jp/remote_addr.php

curlの処理ステイタスが表示されるのを避けるためのオプションを探す(curl --help)。-sオプションだとわかった。

実行。

curl -s http://info.ddo.jp/remote_addr.php

結果、どうやら「改行」「REMOTE_ADDR:125.xxx.xxx.xxx」と出力されている。欲しいのは「125.xxx.xxx.xxx」部分。

考えたこと:
1.「REMOTE_ADDR:125.xxx.xxx.xxx」の行の「:」以降だけ出力するフィルタを作ろう
2. cutコマンドで、フィールド区切りを「:」にして(-d:)、2番目のフィールドを出力すればよいだろう(-f2
3.空の行は出力しないようにするオプションを「man cut」で探す。こういうオプションは大抵あるはず→-sオプションとわかった

試してみる。

curl -s http://info.ddo.jp/remote_addr.php | cut -s -d: -f2

無事「125.xxx.xxx.xxx」が出力されたようなので、「curl "http://free.ddo.jp/dnsupdate.php?dn=ドメイン名&ip=IPアドレス&pw=パスワード"」を開くことにする。

「IPアドレス」部分をさっきの「curl … | cut …」の結果と置き換えるために、「$()」を使うことにする。具体的には、「$(curl … | cut …)」のように$(と)の間にそのまま入れる。
(環境変数に保存しておけば別の用途にも便利かなと思ったけど使いたいと思ったことがほとんどなかったのでやめた)

curl -s "http://free.ddo.jp/dnsupdate.php?dn=XXXXXXXX&ip=$(curl -s http://info.ddo.jp/remote_addr.php | cut -s -d: -f2)&pw=XXXXXXXX"

試してみる。

文字化けしているけどSUCCESS:という表示があるのでおそらく成功(curl -s -o ファイル名 "http://…"で結果をファイルに出力して確認可能)。

メッセージがおそらく日本語EUCで出力されており、UTF-8にすればTerminalでも化けずに表示されるだろうから、iconvで変換(iconv -f eucjp -t utf8)してみよう。

curl -s "http://free.ddo.jp/dnsupdate.php?dn=XXXXXXXX&ip=$(curl -s http://info.ddo.jp/remote_addr.php | cut -s -d: -f2)&pw=XXXXXXXX" | iconv -f eucjp -t utf8

OK。
なんとなく、出力結果からタグ(<〜>)を除去したくなる。

安直にやるとこう。

sed "s/<.*>//"

sedは最長一致なので、タグとタグに挟まれた部分(<xxx>〜</xxx>)もきれいさっぱりなくなっていて、なんとなく結果オーライな気がしてしまったが、blogに書くので最短一致になるよう修正。

sed "s/<[^>]*>//g"

最初にやった正規表現:
<.*>は、「<という文字、任意の1文字、←その0回以上の繰り返し、>という文字」となっている箇所にマッチする。<a>でも<aaa>でも<aaa<aaa>aaa>でもマッチする。
「<aaa<aaa>aaa>」は「<aaa<aaa>」でも「<aaa<aaa>aaa>」でも条件に合う。この場合、できるだけ長いパターンが採用される(これが最長一致)。

2つめの正規表現(<[^>]*>):
[^>]は、「>という文字以外」という意味
[^>]*は、「>という文字以外、←その0回以上の繰り返し」という意味
したがって、<[^>]*>は、「<という文字、>という文字以外、←その0回以上の繰り返し、>という文字」となっている箇所にマッチする。
先ほどの<aaa<aaa>aaa>の場合、1つめの>が登場した地点(<aaa<aaa>)でパターンから外れる。結果、最短一致になる(一番短くなるパターンが採用されるのが最短一致)
※sedは最短一致と最長一致を指定し分ける正規表現やオプションが使用できない

1行の中に「<ほにゃらら>」が何回も登場するので、s///ではなくs///で置き換えるようにする。gは複数回適用させる、という意味(gがついていないと、1回置き換えたら終了し次の行に処理が移ることになる)。

gがないとこうなる。


せっかくなので、空行を除去したくなった。
空行は「^$」で表すことができる。^は行頭、$は行末、つまり^$は行頭のあとすぐ行末になってる行すなわち空行に一致。
^$というパターンに一致する行(「/^$/」と表現)を削除(d)。この処理を、先ほどの「s///g」の後ろにくっつける。区切り文字は「;」。

sed "s/<[^>]*>//g;/^$/d"


さらなる細工をしようと思った場合
・タグの間に改行があるとうまく除去できない(いったん改行をとっぱらってから置き換え処理、というのが簡単)
・空行じゃなくて空白文字だけの行どうしたい?(残して良いような気もするし、削除しても良い気もするし)
・<br>を改行に置き換えたくなる
・っていうかSUCCESSかFAILの部分だけ表示すればよくね? ← これだ

じゃぁ今までのsed部分を全部忘れて、
まずはSUCCESSについて

sed -n "s/^SUCCESS:.*/SUCCESS/p"

「行頭にSUCCESS:という文字列」、「任意の文字とその0回以上の繰り返し」に合致する行でSUCCESSと出力(p)、それ以外の行は出力しない(-nオプション)。FAILDも同様に書いてくっつける。

sed -n "s/^SUCCESS:.*/SUCCESS/p;s/^FAIL:.*/FAIL/p"

実行してみる。

#気づいたこと

Mac OS X 10.5.5 に収録されているsedコマンドは、正規表現の¥|(または、の意)に対応していない。↓バージョン3.02(コマンド名sed302)との比較画面


sed302は、以前、OSX収録版のsedコマンドで日本語処理が微妙におかしかった時、Ver3.02のソースをダウンロードしてコンパイルしたもの。念のため普段はオリジナル版を使うようにしようと思いsed302という名前にしておいた。

| | Comments (0)

がむしゃらに作ってみる時の様子

AppleScriptやりたいなーと思いつつ、既存のスクリプトのお世話になるだけで自分じゃいっこも作らず書かず。よし勉強しようとヘルプやらリファレンスやら読もうと思うがなんとなく身が入らず「また今度にしよう」で今に至る。

だからとにかく闇雲に1つ書いてみよう、ということにした。ほんのちょっとでもいいから「わかんないよー」と試行錯誤しながら書くと、リファレンスを読む気にもなるというもの。

やってることをそのままメモします。前提条件は「HTMLは一応わかる」「JavaScriptの存在を知っている(紹介サイトにあるブックマークレットをブラウザに登録したことがある程度)」「なんらかのプログラミング(シェルスクリプト含む)に触れたことがある」。レッツトライ。

【やりたいと思ったこと】
DDNSサービス「Dynamic DO!.jp(無料用)」で、IPアドレスの更新をしたい。
基本手動でいいけど、起動時には自動で更新したい。
AppleScriptを作って、起動時なりなんなりにそれを実行するようにしよう。

ガイドページを見つける。

AppleScript をはじめよう!」※2003年の記事でバージョンはMac OS X 10.2.6

……"スクリプトエディタ"(Mac OS 9.x では スクリプト編集プログラム) を起動し、実行や編集ができます。

そういえばスクリプトエディタってどこにあるんだ? ユーティリティー?

参考ページを見ると、どうも、そのまま「スクリプトエディタ」という名前のようだ。呼び名だと思ってた。
Finderでスクリプトエディタを検索 → 発見
右クリックで「内包しているフォルダをフォルダを開く」

アプリケーション → AppleScript(/Application/AppleScript)の中にあるらしい。

「AppleScript をはじめよう!」の次のページを開く

サンプルスクリプト

tell application "Help Viewer"
  activate
  search looking for "AppleScript"
end tell
アプリケーションがAppleScript に対応していれば“用語辞書”という物をもっています。いま起動した ヘルプビューアをスクリプトエディタにドラッグ&ドロップしてみましょう。

ほのかにわかったこと
・「tell application "アプリケーション名?"」〜「end tell」の間に、アプリケーションへの命令?を出せば良いようだ。
・スクリプトエディタにアプリケーションをドラッグ&ドロップするといろんなことがわかるらしい。

やってみたこと
・Safariをスクリプトエディタにドロップしてみる。
・「Safari.sdef」なるウィンドウが開く。

なんかいろいろあるな、と思う。

「Standard Suite」の中に、closeやopen、documentなどを見つける。closeやopenには青丸の[C]マークがついている。なんとなくコマンドの「C」なのだろうな、と思う。documentについている紫四角の[C]マークは何なのだろう?と思う。

「Safari suite」の中に do JavaScriptを見つける。なるほど青丸の[C]だ。きっとこれを使えばなんとかできるんだろう、と思う。
「Safari suite」には紫四角の[C]が3つ(document、tab、window)がある。3つだけだし、とりあえずそれぞれをクリックして眺める。紫四角の[P]はプロパティでオレンジ四角の[E]はエレメント、あ、紫四角の[C]はクラスか。などと思う。

AppleScriptでどういうことをやればいいのか考える。

手動でやっている時に何をしているかというと、


・Safariで「Dynamic DO!.jp」のIPアドレスの更新(無料用)のページ(http://ddo.jp/?free=on)を開く
・[登録ドメイン]にドメインを入れる
・[パスワード]にパスワードを入れる
・[IPアドレス]には現在のグローバルIPアドレスが自動で入っているので何もしない
・[更新]をクリックする


[更新]をクリックするとhttp://free.ddo.jp/dnsupdate.php?dn=ほにゃらら&ip=ほにゃらら&pw=ほにゃららというページが開き、「IPアドレス更新完了」などと表示される。

ってことは、「safari "http://free.ddo.jp/dnsupdate.php?dn=ほにゃらら&ip=ほにゃらら&pw=ほにゃらら"」的なことをすればいいんじゃなかろうか、と思う。

グローバルIPアドレスはIPアドレスの更新ページを開くとわかるのであるからして、

1.「safari http://ddo.jp/?free=on」的なことをして、
2.グローバルIPアドレスの値を見て
3.「safari "http://free.ddo.jp/dnsupdate.php?dn=ほにゃららe&ip=ほにゃらら&pw=ほにゃらら"」的なことをする
4.Safariを閉じる ←手動でもやることだし、更新されたかどうかも確認したいので、これはやめよう

でどうだろうか、と思う。

1.「safari http://ddo.jp/?free=on」的なことをするためのチャレンジ

超シンプルに考えて


tell application "Safari"
	open "http://ddo.jp/?free=on"
end tell

で実行してみると、「ページを開けませんでした」「“/http/::ddo.jp:?free=on”にファイルがありません。」となる。

あーそうか、と思う。こゆんじゃないのね。

ていうかお手本を見てみよう、と思う。

Googleで「AppleScript Safari」を検索する。
見つける。

Safari 2.0 Help: AppleScript を使って Safari のタスクを自動化する - 7:02 「Safari」には、スクリプトを使ってタスクを自動化できる AppleScript 言語が含まれています。スクリプトでこれらの言語を使って、Web ページを開いたり、別のアプリケーションで使用するために一連の画像をダウンロードしたりできます。 ... docs.info.apple.com/jarticle.html?path=Safari/2.0/jp/ibr1078.html

docs.info.apple.comだ。見ておこう。

「Safari」の AppleScript 用語説明を表示するには:
  1. 「Finder」で、「スクリプトエディタ」のアイコンを示しているウインドウを開きます。これは「/アプリケーション/AppleScript」フォルダにあります。
  2. 「Safari」のアイコンを示している別のウインドウを開きます。
  3. 「Safari」のアイコンを「スクリプトエディタ」のアイコンの上にドラッグします。

あ、これはさっきのだな。
ふたたび検索画面に戻る。
見つける。

[ AppleScript ] Firefox で開いているページを Safari で開く | Bowz ... - 5:18 2008年2月25日 ... AppleScript を使って Firefox から URL を取得する方法が見つかったので、Firefox で開いて...

これはなんだか参考になりそうだ。

Firefox で開いているページを Safari で開く AppleScript

tell application "Firefox"
    set theURL to «class curl» of window 1
end tell
tell application "Safari"
    activate
    open location theURL
end tell

おおー!
「open location アドレス」ってやるノカ!
そして「set theURL to なんとかなんとか」で値を保存してるぽい

じゃあさっそく。


tell application "Safari"
	open location "http://ddo.jp/?free=on"
end tell

で実行してみたら、OK、開いたぜ!

 

じゃぁ次。

ここに表示されているグローバルIPアドレスが欲しいのであるな。ちなみにHTMLを見てみると、この辺にあるようだ。


<form name=dnsupdate action="http://free.ddo.jp/dnsupdate.php" method=get target="_new">
  <tr>
    <td align=center>
      IPアドレスの更新 (無料用)    </td>
  </tr>
  …
      登録 ドメイン      
      <input type=text name=dn size=30>
      IPアドレス
     <input type=text name=ip value="121.xxx.xxx.xxx" size=30>
      パスワード
     <input type=password name=pw size=30>
      <input type=submit value="更新">
  …
</form>

グローバルIPアドレスは、dnsupdateっていう名前のformの、ipって名前のinput type=textにあるvalueだ。

あるいは、dnっていう名前のinput type=textのvalueに登録ドメイン名、pwって名前のinput type=textのvalueにパスワードを入れてsubmit……いや、わかんないからやっぱり最初の方針通りにしよう。

じゃぁ、グローバルIPアドレスを、変数に、変数に、変数に、えーと変数名どうしよう。お作法がさっぱりわからないよ。わからないからさっきみたソースの真似してtheIPにしておこう。

tell application "Safari"
	open location "http://ddo.jp/?free=on"
	set theIP to ・・・・ どうすんだ?
end tell

ブラウザ内の要素は、document.なんちゃら でアクセスできたような気がする。
Safari.sdefで見たdocumentクラスのsourceプロパティはなんか違うような気がする。
いやでもわからんぞ?

とにかく何かスクリプトを眺めてみよう。

Googleで「AppleScript Safari 要素」を検索
※documentなんちゃら、というキーワードが思いつかない場合の検索

[ AppleScript ] Safari で表示中のページを再読み込み ( リロード ... - 7:43 Safari で表示中のページを再読み込み(リロード)する AppleScript のサンプルです。 mi で CSS... ... [ JavaScript ] jQuery でテーブルのセルをストライプ ( しましま ) にするサンプル · [ CSS ] 指定した要素の後に属性値を表示する :after と ... bowz.info/1216 - 53k - キャッシュ - 関連ページ

あ、さっきの人のblogだ。ブックマークしておこう。

Safari で表示中のページを再読み込み(リロード)する AppleScript のサンプルです。
tell application "Safari"
  do JavaScript "location.reload(true);" in document 1
end tell

あー、JavaScriptってこう使うんだ。なるほど。
それはさておき、form要素の……

ううむわからん。もう少しキーワードが必要ぽい。

Googleで「AppleScript Safari 値 取得」を検索

けんしのページ - AppleScript と iTunes - 2 - - - 5:41 節の中では Safari を呼び出してページのロードが終了したかどうか問い合わせています。AppleScript からは直接ページのロード状況を取得する事はできません。そこでここでは "do JavaScript コマンド in document 番号" コマンドで JavaScript を ... www2.airnet.ne.jp/~kenshi/mac08.html - 16k - キャッシュ - 関連ページ

歌詞を検索するスクリプトの一部のようだ。

…… 本文の最初ではこれまでと同様にトラックの取得をしています。そして取得したトラックの曲名を song_name に取得しています。
tell application "Safari"
	open location "http://www.uta-net.com/members/index2.html" -- Safari で検索ページを開く
end tell
wait_load(10) -- ロードの終了を待つ
if result is true then
   tell application "Safari" -- 最初のページは検索が失敗するので空の検索を行う
      set script_text to "document.KeyForm.MA_text1_de.value=" & "\"\"" -- 検索フィールドを空白にする
      do JavaScript script_text in document 1
      set script_text to "document.KeyForm.submit()" -- 検索を開始
      do JavaScript script_text in document 1
      delay 1
   end tell

「"document.KeyForm.MA_text1_de.value="」なんてのがとてもそれっぽい。

でももう少しヒント欲しい。

Googleで「AppleScript Safari 要素 form input」を検索

おもろい、めずらしいアップルスクリプト発表会 5 http://pc11.2ch.net ... - 7:52 230 名前: 名称未設定 Mail: 投稿日: 04/10/07 10:12:06 ID: m8rgYK90 UI Elementなんですけど、Safariで表示させたページ上のフォームなどはAppleScriptでは扱えないんでしょうか? Inspectorでは見えてるのに探してもみつからないorz 231 名前: 名称未 ... members.at.infoseek.co.jp/obto/script/omoroi5.txt - 287k - キャッシュ - 関連ページ

これは?!

230 名前: 名称未設定 Mail: 投稿日: 04/10/07 10:12:06 ID: m8rgYK90 UI Elementなんですけど、Safariで表示させたページ上のフォームなどはAppleScriptでは扱えないんでしょうか? Inspectorでは見えてるのに探してもみつからないorz 231 名前: 名称未設定 Mail: 投稿日: 04/10/07 20:57:06 ID: Cjni9kAp >>230 答えになってないかもしれんが、do JavaScript "..." in document ... で扱える。 ここら辺を参考に http://www.apple.com/applescript/safari/

これか?

(JavaScriptで、「document.form名.input名.value」という書き方がうっすらわかってくる)
(※ここで検索+超拾い読みだけで行けるか挑戦したけれど難しかったです。DOM大事)

in document ... の所がわからないので、試しに書かずにやってみる。

tell application "Safari"
	open location "http://ddo.jp/?free=on"
	set theIP to do JavaScript "document.dnsupdate.ip.value"
end tell

実行。

エラー。

Safari でエラーが起きました:application "Safari" のタイプを reference に変換できません。

試しに、「in document 1」を真似してみる。

tell application "Safari"
	open location "http://ddo.jp/?free=on"
	set theIP to do JavaScript "document.dnsupdate.ip.value" in document 1
end tell

実行。

エラー出ない。

あ、スクリプトエディタの下画面に「"121.xxx.xxx.xxx"」と表示されている!(たまたま[結果]を選択していたのかデフォルトでこうだったのかはまだスクリプトエディタを把握していないため不明)

なんかとにかくできてるようだ。

とはいえ、「in document 1」部分は「できたからいいや」では良くない気がする(なぜなら「1」という数値を、意味を理解しないまま使っているからである)。あらためて、Safari.sdefを見る。

document n [inh. document > item; see also Standard Suite] : A Safari document representing the active tab in a window.


document 数値
tab
window

↑THE★拾い読み

ああそうか、Safariは複数のページ開くんだものな。じゃぁ開いた直後の画面ならdocument 1でいいのか。いいのか?

とりあえずそういうものなのだろうと思った所で、では「"http://free.ddo.jp/dnsupdate.php?dn=ほにゃららe&ip=ほにゃらら&pw=ほにゃらら"」という文字列を作るぞの巻であるな、と思う。

この文字列は、というと、「"http://free.ddo.jp/dnsupdate.php?dn=ほにゃららe&ip="」、theIP、「"&pw=ほにゃらら"」をくっつけたらできるわけで……

Googleで「AppleScript 文字列 連結」で検索

Tutorials of AppleScript ... リスト」として連結されます。また文字列と数のように異なったデータ型のデータを連結しようとすると、左側のデータ型に型変換してから連結します。 ... AppleScriptの使い方に戻る このホームページに関するお問い合わせは www.drycarbon.com/applescript/tutorial/tutorial_08.html - 7k - キャッシュ - 関連ページ

これはとても読むべきページを引き当てた気配。でも今は超拾い読みしかしない。

データを連結するときには「&」を使用します。例えば"AAA"と"BBB"を連結したいときは
"AAA" & "BBB"
 と記述すれば"AAABBB"となります。ただし数のようにそれ自身が連結できないものは次に説明する「リスト」として連結されます。また文字列と数のように異なったデータ型のデータを連結しようとすると……

ああ、このサイトは全部読むべきだ。と思いつつ、「よし。& を使うのだな」、と把握。そして再びGoogle結果を眺める。

Xcode & アップルスクリプト虎の巻 - 5:37 文字連結, &, 文字列をつなげます (配列の場合はitemが増えます), set obj to "あいう" & "ABC". "あいうABC"となる. 比較, contains, 左に文字列から右の文字列があるか探す。 あればtrue、なければfalseを返す, set a to "AppleScript" contains "esc" ... homepage3.nifty.com/kajiwa-nya/applescript/anchoco.html - 38k - キャッシュ - 関連ページ

というのを見つけ、最終更新2007.12.26なこのページはきっととても後に便利に参照するに違いないと予感しブックマークしておくことにする。

さて、ほにゃららしてみます。



tell application "Safari"
	open location "http://ddo.jp/?free=on"
	set theIP to do JavaScript "document.dnsupdate.ip.value" in document 1
	open location "http://free.ddo.jp/dnsupdate.php?dn=XXXXXXXX&ip=" & theIP & "&pw=XXXXXXXX"
end tell

実行。

なんだかうまくいった気配です。

IPアドレス更新完了. ※IPアドレスに変更がありませんため、パスワードはチェックしておりません。

グローバルIPアドレスを変更するためにルータをリブートする。
(ここでルータが眠り込んでしまいしばらく途方にくれた。熱暴走って季節じゃないぞ?)

IPアドレス更新完了.

OKぽい※。

さて、リファレンスやヘルプをもう少しきちんと読むことにします。
この状態で読めば、今までぼんやり眺めていたヘルプも「そうだったのか!」やら「なるほど!」の嵐になるはずです。←ここ大事

※ddo.jpへのアクセスに時間がかかると、ページを読み込む前にdocument.dnsupdate.ip.valueを参照しようとする→参照できない→theIP変数が未定義になりエラーとなるため、上の方で参考にしたiTunes用の歌詞検索スクリプトにあったwait_load(10)関数を見ながら下記のように修正。あまりリトライすると(スクリプトのテストも含め)ddo.jpさんに大変な迷惑をおかけしてしまうので、ひとまず目的は果たせると言うことで今回は終了。

tell application "Safari"
	set retryCount to 3
	repeat with i from 1 to the retryCount
		open location "http://ddo.jp/?free=on"
		tell application "Safari"
			if (do JavaScript "document.readyState" in document 1) is "complete" then
				exit repeat
			end if
		end tell
		delay 1
	end repeat
	set theIP to do JavaScript "document.dnsupdate.ip.value" in document 1
	open location "http://free.ddo.jp/dnsupdate.php?dn=XXXXXXXX&ip=" & theIP & "&pw=XXXXXXXX"
end tell

そして今、「http://info.ddo.jp/remote_addr.php」でグローバルIPアドレスが取得できることを知り、とりあえずいったん寝ることにした。
(それが何時頃であったかは、上の画像をクリックすると確認していただけるようになっています)

↑(12/3夕方追記)とりあえずシェルスクリプトにて。

↑(12/3そのしばらく後に追記)わたしなりの決着

| | Comments (0)

«OSX:sed 日本語処理に問題? → 3.02コンパイル