#Twitter のつぶやきを #Mastodon に流す,たとえWindowsしか使えなくても! #マストドン #初心者向け

やってみたけど意外に簡単だったのと,よく使う人もいそうなので超初心者向けメモ.

必要なもの

  • IFTTTに接続されたTwitterのアカウント
  • 適当なインスタンスのMastodonのアカウント(この場合,mstdn.io というインスタンスの o_ob というアカウント )
  • 最新のcurlが利用できるLinux等の環境もしくはWindows版curl

ネット上のドキュメントにはLinux環境でのドキュメントが多いので,Windows版curlでの説明で書いてみます.

なお「curl」とはコマンドラインで使えるブラウザのようなもので,curl http://… とすることでそのURLを叩くことができます.ダウンローダーなど嬉恥ずかしい使い方もたくさんあるのですが,今回はPOSTメソッドでMustodonのAPIを叩くのに使います.

ちなみにLinux環境で,curlが入っているけどなんかエラーが出る人は,最新のTLS対応でない可能性があります.管理者権限で yum update してください.

Macの人はターミナルに入っているはずです.

Windows版のcurlを入手する

https://curl.haxx.se/download.html

こちらで「Packages」の「Windows 64bit」の適当な圧縮形式のファイルを入手してください.
64ビット版の7.53.1はこちらにありました.例えばこのZIPをDownloadsにダウンロードして解凍した場合で,以下説明します.

バイナリファイル自体は Downloads\curl-7.53.1\src 」に curl.exe として入っていますが,これはコマンドライン用のEXEですのでダブルクリックしても何も起きません(見えない).

Windows+R で「ファイル名を指定して実行」→「cmd」でコマンドラインを開く.

2017-04-23 (1).png

黒こわい画面が開いたら,curl.exe をこの画面にドロップしてみましょう.

C:\Users\yourname>C:\Users\yourname\Downloads\curl-7.53.1\src\curl.exe

と出ていたら,エンターキーを軽快に叩いてみてください.

なんかメッセージが出たら,今度は curl.exe の後ろに「http://google.com」など適当なURLを打ち込んでみてください.

2017-04-23 (2).png

GoogleのトップページにアクセスしたときのHTMLが表示されます.まあ実際には「引越ししました,302エラー」が表示されています.

これで準備は終わりです,いろいろ細かいこともありますが,以下,このcurl.exe があるディレクトリで作業してしまいましょう.

まず独自アプリの登録

先ほどのディレクトリで curl(…curl.exe+半角スペース)に続いてコマンドを打っていきます.curl.exeをcurlと見なして以下真似てみてください(太字は変更).

curl -X POST -d “client_name=MSTDN2Twitter&redirect_uris=urn:ietf:wg:oauth:2.0:oob&scopes=write&website=https://aki.shirai.as” -Ss https://mstdn.io/api/v1/apps

なお,コマンドラインでは「↑」キーを押すと過去のコマンドを再利用することができます.マウスの右クリックでクリップボードに張り付けることもできます.

なお,client_nameは好きにつけられます,これから皆さんが作る独自アプリの名前ですのでせいぜいかっこいい名前にしましょう.websiteは自分のサイトでも大丈夫なはず,mstdn.ioは自分が利用したいインスタンスに置き換えてください.

うまくいくと以下のような文字列が出てきます.

{“id”:xxxx,”redirect_uri”:”urn:ietf:wg:oauth:2.0:oob”,”client_id”:”[A]64文字の英数字“,”client_secret”:”[B]64文字の英数字“}

マウスドラッグして変な改行や,テキストエディタなどに(Windows+Rで「notepad」)スペースが入らないようにして張り付けておきましょう.

アプリにユーザを登録する

次に,このアプリを使ってご使用のインスタンスでユーザID,パスワードを使って認証をし,トークン(Token)という専用回数券を入手します.

curl -X POST -d “scope=write&client_id=[A]&client_secret=[B]&grant_type=password&username=[メールアドレス]&password=[パスワード]” -Ss https://[mstdn.io]/oauth/token

  • [A]はひとつ前のプロセスで発行されたclient_id
  • [B]はひとつ前のプロセスで発行されたclient_secret
  • [メールアドレス]はご利用のMastodonサーバ(インスタンス)で登録したメールアドレス
  • [パスワード]はご利用のMastodonインスタンスで登録したパスワード
  • [mstdn.io]はご利用のMastodonインスタンスのURL本体に置き換えてください

[~]や”~”などのカッコや引用符はつける必要がありません.ドラッグと右クリックを使って丁寧に作業してください.正しく設定できているとエンターを押した後に,

{“access_token”:”[C:英数64文字のトークン]“,”token_type”:”bearer”,”scope”:”write”,”created_at”:[日付数字]}

という反応が出ます.これも大事な文字列なのでテキストエディタに拾っておきましょう.トークンは大事なものなので,ネットなどに撒くのは危険です!

間違っていた場合は以下のような文字列になります.メールアドレスやパスワードをよく確認してください,ちゃんとログインできますか?

<html><body>You are being <a href=”https://mstdn.io/auth/sign_in”>redirected</a>.</body></html>

どうしてもうまくいかないときは上記のcurl の後に続く「-Ss」(反応を表示しない)のところを「-v」(全部表示)にしてみるといろいろ分かるかもしれません.

 

試しにcurlから投稿してみる

さてせっかくですので試しにcurlから投稿してみましょう!

curl.exe -X POST -d “access_token=[C]&status=Hello World!&visibility=private” -Ss https://[mstdn.io]/api/v1/statuses

  • [C]はアクセストークンです.長い英数文字列です.引用符などは不要です.
  • Hello World!はつぶやきたい文字列です,日本語はここでは問題があります.
  • privateにしておくと,ほかの人には見えません.
  • [mstdn.io]はご使用のインスタンスに置き換えてください.カッコは不要です.

いかがでしょうか?うまく投稿されましたか?うまくいかなかった場合は「Oops.png」や「We’re sorry, but something went wrong.」といったHTMLが反応として返ってくるはずです.

IFTTTで自動化投稿設定をする

IFTTTについて細かく解説が必要な方は,ほかのサイトなども見てください.If [何か起きたら] then [何かする]というシンプルな部品の組み合わせで強力なWebサービスが作れます.個人利用や試作としては十分な機能を持っており,プログラミング不要です.

使用するモジュールは「maker_webhooks」です.

  • 名称は[My Twitter2Mastodon]など自由にどうぞ.
  • [New Tweets by You]:RTを対象にする,@返信を対象にする場合はそれぞれチェック.
  • [URL]:[https://mstdn.io/api/v1/statuses] お使いのインスタンスに合わせて変更してください
  • [Methods]:[POST]
  • [Content Type]:[application/x-www-form-urlencoded]
  • [Body]:[visibility=public&access_token=[Cのトークン]&status={{Text}}

 

{{Text}}の前後にはスペースを入れないことをお勧めします.「Ingredient」を使えばURLなども入ります.

Saveしたら適当なつぶやきをTwitter側でやってみて実験しましょう!(意味なくつぶやくのは意外と大変…)

うまくいかないときはIFTTTのApplet側のLogを確認しましょう.変なスペースが入ってたりすると事故の元です.

visibilityは{private, public, unlisted, direct}をとります.小文字で書くことに注意.

ちなみにIFTTTを使いこなしている人は,Wordpressの投稿をつないだり,出勤簿にしたりと,いろいろ使い道を思いつくはずです!

Happy Tweeting and Tooting!

 

おまけ1:作業の後片付けをしたい

はい,終わったらcurlのフォルダごと捨ててしまいましょう.

特に,Mastodonのパスワードやトークンがその辺に残っているのは気持ち悪いですので!

ここまで解説した方法の良いところは,コマンドライン上で操作していますので,パスワードを保存したりすることが一切ない点なのです.

 

おまけ2:Mastodon → Twitterもやりたい

ちなみに Mastodon→Twitter はできるけどあまり利点がないので検証してません.

簡単に言うと,Mastodonには標準でATOMを吐いてくれますので,それを拾えば既存の各種サービスでできることが多いからです.

具体的には,ご自身のアカウントの後ろに「.atom」を付けたURLにアクセスすれば,フィードが取れます.

https://mstdn.io/@o_ob.atom

以上です!

何か間違いやフィードバックがありましたら,こちらのTOOTまでどうぞ.

https://mstdn.io/@o_ob/815348

さくらのレンタルサーバーで立ち上げたWordPressを完全SSL対応(鍵緑)にするメモ

レンタルサーバーで安く安定した環境を構築する週末

世間はMastodonインスタンス立ち上げがDockerDockerおきているわけですが、この週末は仕事で使うWordpressの整備などに充ててしまいました.

ちょうど最近はレンタルサーバー各社を比較していて,さくら・GMO・Kagoyaなど,月1万円以下のVPSやレンタルサーバーの運用上の評価を行っておりました.レンタルサーバーで安く安定した環境を構築する週末,まあ時間の浪費かもしれませんが「趣味の盆栽」とでも思えば耐えられる.

この業界横並びで,ここ2年ぐらいの傾向としては,SSDなどの増量,共有SSL(SNI;Server Name Indication)対応,Webフォント対応などが主な機能です.あとはコントロールパネルが見やすいかとか,値段は実は結構違いますが,年間数千円の違いといったところでしょうか.

最近の共有SSLはリバースプロキシで実装されている

昔はSSLといえば,固定IPとった上にお高い(数万円/年)印象がありますが,SNIであれば年間数千円,キャンペーンで初年度無料といった価格で入手できます.そもそも2015年以降,GoogleがSSL非対応サイトの評価を大きく下げたことも影響しています(参考:GMOによる解説).

そもそも純然たるSSLと違いって,共有SSLは「保護された通信」とだけ出ます.表示は鍵と緑です.EV証明書がある接続はブラウザのURLの左に鍵緑に加えて,証明機関の名前が出ます.共有SSLはそもそもIPアドレス共有していても名前ベースの認証(SNI)で解決しているので,もしかすると同じIPをもったレンタルサーバーのユーザが(鍵をすべて入手するなど)成り済まそうと思えばできなくもない仕組みです(参考:富士通による詳細で簡単な解説).

また,通常,HTTPとHTTPSはアクセスするサーバーポートが80番と443番と異なります.自サーバーでのHTTPSの設定は,あくまで別のディレクトリ,別のサーバーとして設定していくことが多いのですが,さくらのレンタルサーバーの場合は,明示的にHTTPとHTTPSの仮想サーバー設定(具体的にはhttpd.conf)を触らせてくれません.例えば「www.domain.org」と「domain.org」のHTTPとHTTPSのアクセス先の挙動4種類をそれぞれ変える設定がしたい場合などが大変.デフォルトはwwwとwwwなしを同じディレクトリ,さらにHTTPとHTTPSをリバースプロクシで自動設定,です.それをコントロールパネルだけで設定できなくもないですが,ログも見えないのでとても作業は大変です.レンタルサーバーはそういうところがつらい,一方でサーバスペック的にはお安く上がっているし,セキュリティもさくら任せなので文句も言いづらいところ.

さくらのmod_rewriteの仕様が変わっている(うまくいかない設定例)

もちろん共有SSLが流行り始めて2年ですので,いろいろな人々がさくらのレンタルサーバーWordpress環境で共有SSLに挑戦しています.鍵緑状態になった実績がある設定としては,.htaccessにリバースプロクシによってHTTPSになった状態を判定する「HTTP:X-Sakura-Forwarded-For」を使って mod_rewriteを書く方法,そしてwp-config.phpに消えてしまったサーバ変数を明示的に書く方法.でも結論から言うとこれはうまくいきません(2017/4/16確認).

.htaccess全体
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ – [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/robots.txt$
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{ENV:HTTPS} !^on$
RewriteCond %{HTTP:X-SAKURA-FORWARDED-FOR} ^$
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
wp-config.php 追記分
// プロクシでIPが入るとSSLアクセス状態をセットする
if( isset($_SERVER[‘HTTP_X_SAKURA_FORWARDED_FOR’]) ) {
 $_SERVER[‘HTTPS’] = ‘on’;
 $_ENV[‘HTTPS’] = ‘on’;
 $_SERVER[‘HTTP_HOST’] = ‘domain.org’;
 $_SERVER[‘SERVER_NAME’] = ‘domain.org’;
 $_ENV[‘HTTP_HOST’] = ‘domain.org’;
 $_ENV[‘SERVER_NAME’] =’domain.org’;
}
上記の参考にしたURLのいくつかでも2017年4月の追記で「さくらではリバースプロクシのmod_rewriteをサポートしない」という話がいくつか出ています.で,そういうサイトはURLが鍵緑ではなく「混在」になっています(このサイトはまだSSL設定していないので「保護されていません」です).
なお,これのおかげで最強のバックアッププラグイン「BackWPup」なども動かない説もあり.

WordPressのCSSだけ崩れる問題と Really Simple SSL

上記の設定でも「共有」まではいきます,少なくともサイトのアクセストップだけは鍵緑になりますが,ソースを見てみると,テーマのCSSへのアクセスだけが「http://自サイト」になっており,クリックするとリダイレクトでHTTPSになります.設定によってはHTTPとHTTPSでリダイレクトループに入り,サイトが表示されなくなります.問題は上記のリバースプロクシと,rewriteに起因するのですが,細かい設定ができないから仕方ない.
このような問題を解決するためのプラグインとしていくつかのサイトでは「Really Simple SSL」プラグインが便利であったと書かれています.ですがこれは時間の浪費でした.このプラグインは3つの機能があり,Wordpressによるリライト,301リダイレクト,JavaScriptによるリライトを実施し,サイトやコンテンツを一つ一つ書きなおさなくてもよい…という触れ込みです.実際には .htaccess を勝手に書き換えますので,上記の特殊仕様が入っている環境では慎重に使ったほうが良いと思います.私はこいつのおかげでとてもはまりました.上記のさくら特有の設定などは無視ですので.

どうやって解決するか?

さて,今回の問題ですが,結局以下のような解決策をとりました.
(1) コントロールパネルは[www]と[wwwなし]を共用,.htaccess はできるだけシンプルに
(2) wp-config.php には上記のサーバー変数を一応書いておく.コントロールパネル「一般」のサイトアドレスはhttpsのwwwなしで統一.
(3) サイトにアクセスして,ソース表示して,CSS以外はhttpsでアクセスできていることを確認する(表示は崩れている).
(4) 「SSL Insecure Content Fixer」というSSL用のリライトプラグインを導入
 インストールして設定で「Capture, unable to detect HTTPS」にチェックするだけ.おそらくリライト後のHTML全部をキャプチャして http:// になっているところを書き換えている,とても重い処理と思うのだが,たくさんあるわけではないし,要はこれをやってほしかっただけなので,もうこれでいいと思う!.htaccessに怪しい設定するほうが危険だし,リダイレクトループに入っている頃に比べれば全然快適に動いています.一日悩んだけど解決してよかった!

おまけ:レンタルサーバ公式のプラグインがあればいいのに…と思ったら

さくらのサーバー向けにSSL対応させるWordpressプラグインがあればいいんですよね,実際WordpressのプラグインでSSL関連を見ていると「CloudFlare Flexible SSL」などCDN業者提供のプラグインもちらほら見えます.最近提供されているWebフォント「TypeSquare」なんかもさくらGMOお名前.comも同じようなサポートプラグインをWordpressのプラグインで出しているから,できることなんじゃないかと思います.ちなみにTypeSquareはどちらの会社も月間25000PVまでは無料という微妙な設定ですが,さくらは9,000インストール,GMOは100以下….
っていうか「Sakura」でWordpressプラグイン検索してたら「さくらのレンタルサーバ 簡単SSL化プラグイン 作者: SAKURA Internet Inc.」ってプラグインがあったのですけど!早くいってよ!…ってことで試してみました.

重大なエラーを引き起こしたため、プラグインを有効化できませんでした。

Fatal error: Arrays are not allowed in class constants in /home/user/www/wordpress/wp-content/plugins/sakura-rs-wp-ssl/modules/model/force-ssl.php on line 32

おあとがよろしいようで….そもそもインストールできないレベルだと試せない,まあこのドキュメントは役に立ちそう.

 

Mastodonに見るサーバー技術の民主化(=個人運営化)について潜在的技術リスクと反省・加筆版

Mastodonはじめました。

https://mstdn.io/@o_ob/

以下、サーバ立ち上げようと思ってhogehogeしてみた結果、反省の再掲です。雑言です。

ちなみにMastodonインスタンス立ち上げのHowToではありません!このエントリーもMastodonが流行り始めて3日ぐらいの2017/4/16の記事です。もう古びていると思います!

Mastodonに至るまでのノスタルジー

ある種の大学の先生というのはいわゆるフルスタック系エンジニアである必要があって、自分の研究室やらIT環境やらを常に実験材料にしてhogehogeするものです。

自分もサーバー関係はいろいろやってきていて、古いところではISDNテレホーダイを使った自宅サーバーとか,FirstClassを使ったSNSのはしりとか。フランス留学中はリモート録画サーバーとか、帰国後のこの10年強でも自宅/自作サーバー系はいろいろやってきていて、光ファイバー、固定IPからDDNSなどなど。

ハードウェア的には自宅(自作、サーバー設計機)、1U→エアコン含めた電気代が機材を上回る→VM→レンサバ、VPSRasPiときていて、OS的にはRedhat→Debian→Ubuntu→CentOSと経験しそれなりに実用レベルの運用経験&セキュリティも維持してきたのだけど、メールサーバー運用するのをできるだけ避けてきたのとDockerで実運用してこなかったので、Mastodonインスタンスを立ち上げようとなると、なかなか勇気いる。

他にもAWSGoogle Compute Engineなどリソース課金型サービス、SSLなども同様で有料となると、なかなか時間と作業時間が折り合わない。貧乏な学生時代は自作だけでサーバー立てるのはなかなかに利益あったのですが。

サービス的には自作CMS、会員サービス、チャットやロビーなどはモリモリ書いてきたけど、最近はセキュリティ維持のためにOSSでもLAMP+Wordpressのようにパッケージ管理が行き届いた信頼性の高いソリューションに頼って来た感じです。

単にインストールするだけなら何とかなったとしても、実際の運用、それにまつわるトラブルなども含めてリスク管理ではありますし、実際に運用してみるとなると、結局インストールが簡単で、ユーザが多くてセキュリティ対策が早くて、どんな寝ぼけていても実施できるような堅牢性が重要になります。

なのでLAMPも、試すだけならともかく実運用ならできるだけソースから入れないし、できればVPSもゼロから立ち上げるのではなくて、レンタルサーバーで終わらせたい。
(本当はそういう人こそDockerのほうが向いているのかもしれないけど、ISPレンサバ各社がそこまでDockerをサポートしていないのも現状)

例えば昨今ではPHPで素のログイン画面やファイルアップロード書くのは怖い。学校では教えるかもしれないけど、そんなユルいPHPフォームはあっという間にrootkitを送りつけられたり、SQLインジェクションを食らったりします。フランスでは日常的にハッカーがSSH絨毯爆撃してくるので、よっぽどしっかりとセキュリティ対策していないとあっという間にルートをとられたりゾンビ化されてしまいます(経験談)。

「教科書に書いてあること」と「教えられること」の差分

話を日本学校の先生に戻すと,つまり「教科書に書いてある事」と「生々しくて教科書には書けない実運用」の差分は日増しに大きくなっていく、ということで。ことIT分野ではサーバ技術についてはその傾向は強いと思う。

先生としては出来るだけ新鮮で生々しい技術を教えて、技術者の価値が高いうちに色々な経験を失敗含めて体験させるべきだろう。

(誰もがやれるようになってから失敗すると責められるからな!)

まあつまりMastodonの流量によるトラフィック圧迫、もしもの時のデータ消失の責め苦などが怖くて試せないなど言い訳まじりに、最新のSNSサービスのインストールが気軽に試せなくなっていく時点で潜在的技術に対するリスクを増大させているのだなあと感じます。

まあそのうちどこのISPでもやりそうだけどね!あとキャリア各社ね。

ソフトバンクとかauとか好きそうだし。

と思ってたら mstdn.jp がすごいことになってた

mstdn.jpを立ち上げておられたぬるかる氏,どうやら筑波大学のM2さんなのですが,なんだかすごいことに.

■暫定世界一ユーザの多いmstdn.jpの管理者さんの奮闘ぶりに、思わずMSのクラウド担当者も声をかけるレベル
https://togetter.com/li/1100609

■支援先

https://enty.jp/nullkal

これは経費差し引いても不要から外れる感じがする(まあ贈与して課税にすればいいのかもしれないけど…)。

普段からDockerをどっかどっかインストールしている人にとってはそんなに難しいことじゃないでしょうね~,とはいえ1時間で2000アカウントぐらい増えていく状況を見ていたのでそれは笑いが止まらないというか、何か運命的なものを感じるのだけれど。

ぬるかる氏の将来はどこへ。いい勉強ですね。

日本人こわい,おもしろい.

さて日本人の熱狂ぶりは本当にすごいエネルギーですね。

偽のMastdonサーバーなどもっと出てきてもおかしくないです。気を付けましょう。

 

3日ほどMastodonTwitterの並列社会に暮らしてみて感じたこと

MastodonTwitterの良いところを完全に喰っているけど、実は野蛮なTwitterは野蛮さゆえに消滅はしないと思う。というか最後にはTwitterOStatus準拠になって「一つのMastodonインスタンス」となって終われば良い事だ。

あとは未成年と本人認証かな,勝手サーバーだから仕方ないのかもしれないけど、連邦タイムラインに変態が流れるのはつらい。やつらはCWとか自分から設定しないからな。

同様に年齢認証について。幼稚なユーザの実年齢になんて興味はないのだけど、Mastodonの特性と未成年Twitterの欲望はとてもよく一致すると思う。

“何かあったときに”責任とるのはサーバー管理者なのだろうか。まあ「何かあったとき」なんてことを考えてたらインスタンスなんて立てられないのだけど、なんか予想をはるかに超えるような事件が起きそうでドキドキします。

つぶやきコンテンツ漂流問題

多くの技術系カリスマTwitter諸氏が取り組んでいるのは、現状のメインつぶやきをどこにするか?という「つぶやきコンテンツ漂流問題」とでも呼ぶべきか、まあライフログをどこでまとめていこうかということです。そんなの気にしてない人はどうでもいいのだろうけど。

自分はAmaroqiPhoneつぶやきは解決したので、最初の出発点はTwitter→MastdonなのかMastodon→Twitterなのかといった問題が残ってる。

実際にはMastodonfeedを吐くようなので、すぐに解決しそう。

で自分はTwitterライフログはすべてFacebookにコピーし、WordPressにまとめられているのでこのストリームに乗るなら楽である。
しかし奇遇なことに、実はWordPressがミニブログとの競合を起こしてしまっている。

なのでこれを書いている。

Google方面もがんばれ,特にGoogle Sitesとか.

WordPressも、本当に必要なサイト以外は、どんどんGoogle Appsあたりに引っ越していくべきかと思う。

そういう意味ではGoogle Waveとかもう一度頑張って欲しい気もするよ、Google AppsSlackbot的な融合をしてくれるなら歓迎だ。

実はこの週末はMastodonのインストールに取り組むどころか,とあるサイトの完全SSL化とGoogle Apps統合をやっていたのですが、Google Appsの一部であったGoogle Sitesは”いちおう”取得したドメインでWebサイトが構築できるサービスなのですが,見た目がダサすぎるばかりか、オブジェクトが個性的過ぎてCMSとしてはホント最低限のイントラネット程度の意味しか持たないサイトが作れる。このGoogle Sitesでもいいや!って用途もあるので、久々にGoogle Sitesを独自ドメインで運用しようとしていたら、実は「新しい Google Sites」なるサービスが立ち上がっていて,これはレスポンシブデザインもできるし、Google Drive各種ともきれいに統合できているしフォントは美しいしでいうことないのだけど,なんと「独自ドメインでは運用できない」という名言付き.なんだそれ!!フッターの「このサイトはGoogle Sitesでつくりました」もどうにかしてよと思うのだけど,なんというかこんなに素敵なサイトなのに自社ドメインでしか使えないとなると,じゃあそれでGoogle Appsの契約アカウント数が増えるのかというとそんなことはないと思うぞGoogleよ.Webというのは不特定多数の人々に無料で使えてこそ意味があるのではないでしょうか.

まとめ: OStatus もっとがんばれ

Mastodonのようなオープンソースのマイクロブログサービスは実は標準化に取り組んでいる成果であって,OStatus というそうです.これはまさにLinux黎明期のころのような興奮なのですね.

実際に使う側の問題としは,この100文字ぐらいのチャットやマイクロブログから、ミニブログといった文字数と写真等のコンテンツの保管,メディアタギングをどう活用していくか。例えば「LINEブログ」とかも,こうなってくるとあまり立つ瀬がないのかもしれない。レスポンシブなBlogとフォント以外に利点がないもの(というかこういうのはアメブロがやればいい).

Mastodonはこの形のまま安定することはないと思います。インスタンスごとにカスタマイズして、厳格なユーザしかいないインスタンスも作れるだろうし、企業内インフラとしての利用もあるだろうし、個人用ローカルと連邦参照のみに絞ったスマホインスタンスによるOStatusなんかもできるかもしれないので、今後企業IT諸氏は大いにOStatus活用を検討すべきと思います。

WordPress Download Monitor「Download id not defined」と表示されるバグを発見

研究室の論文リストなどを管理するために、Wordpressのダウンロードページを管理する「download monitor」というプラグインを使っている。

フォーマットを定義すると論文の詳細なども表示できるので便利なのだけど、最近何かがおかしい…。

https://wordpress.org/support/plugin/download-monitor

Publications

一部の論文において、「Download id not defined」と表示されてしまう。

  • [相模原市市民協働事業] [Download id not defined]
  • [相模原市市民協働事業] [Download id not defined]

スクリーンショットだとこんな感じ

ちなみにソースにはこんな感じに書いてある。

  • [相模原市市民協働事業] [download id=”126″ format=”4″]
  • [相模原市市民協働事業] [download id=”125″ format=”4″]
  • [相模原市市民協働事業] [download id=”115″ format=”4″]

スクリーンショットやページを見てもわかるように、動いているものと動いていないものがある。ソースをよく確認して、コピペしてみても再現性があったりなかったりで、よくわからないが、ビジュアルエディタの「↓」ボタンから生成した場合は正しい動作をするようだ。

dl

実際の動くコードがこれ
[download id="126" format="4"]
動かないコードがこれ
[download id="126" format="4"]

この動くコードと動かないコードの間には、何か目に見えない霊的なものがあるのだろう…。

ghostinthespace

いやいやいや…テキストエディタで検索してみますよ?

a020

上の緑の検索対象が動かないコード、下の反応がないコードが動くコード。

目では見えないのですが、Unicodeの「a0」、HTMLでいうところの&nbsp(non-break space)が、半角空白「20」の代わりに入っている行が動かないようです。前は動いたのに!

とりあえず作者(Never5, Barry Kooij, Mike Jolley)にバグレポートを出してみよう…。
「a0使うな!」で終わりそうだけど。

<余談>

Download Monitorには旧バージョンのデータを変換するツールも提供されているので最初はこれかなと思ったわけです。結局使わなかったけど、これって本体のAdminページに仕込まれている変換ツールと何か違うのかな?
https://www.download-monitor.com/extensions/dlm-legacy-importer/

Old download shortcodes containing download IDs will mismatch after import because of the new data structure. Whilst active, the legacy plugin will step in if a legacy download ID is called, however it is recommended that you update your content with new IDs to prevent errors.

Evernoteが半角スペースを勝手に0xa0に変換するなんてこともあるようです。
■ ソースコード中に0xC2A0(UTF-8のNO-BREAK SPACE)が混ざり実行できなくて困った話 kk_Atakaの日記
http://d.hatena.ne.jp/kk_Ataka/20130826/1377527170

■ ASCIIコード変換器
http://web-apps.nbookmark.com/ascii-converter/

AHO='() { baka; }; echo manuke’ bash -c ‘echo Hi’

「ほとんどすべてのLinuxに使用されているシェル”bash”にとんでもない脆弱性(CVE-2014-6271)があるのだそうだ」…と言っても,動く人はいないだろう.

お手近なLinuxにログインできる人は 以下のコマンドを打ってみてほしい.

 AHO='() { baka; }; echo manuke' bash -c 'echo Hi'

「Hi」と出ず「manuke」と出れば,未対策.なお管理者権限は必要ない,ユーザで確認できる.

環境変数に任意のコマンドを突っ込んで実行できるということを示しているのだけど,

アホだの間抜けだの言われながらのほうが緊急性が刺激されることはよくわかった.

あなたが管理者なら

# yum list installed | grep bash

して該当のバージョン以降(bash-4.1.2-15.el6_5.1など)になっていることを確認しよう.なおパッチはbash3.x系にも配布されているし,ShellShock攻撃として実際に利用する被害が出始めているので,とにかく急いで対応すべし.

yumで管理しているなら,

# yum -y update bash

これ一発で終わる.

 

参考:bashの脆弱性がヤバすぎる件

https://x86-64.jp/blog/CVE-2014-6271

 

Twitter Digestを改造した

WordPressがTwitterのStatusURLを入れると,見やすい展開をはかってくれるようになったのに,
Twitter Digestは相変わらずリストでしか表示してくれないし,画像は展開してくれないので,以下のような魔改造を施しました.akiで検索してみてください.
あんまり重たいようだったらやめよう.

それにしてもどうして,定期まとめツイートが走らなかったんだろう…?しばらく様子見.

Twitter account.

Version: 2.9
Author: Tim Beck
Author URI: http://whalespine.org
*/

// Copyright (c) 2009 - 2010 Tim Beck and Paul Wlodarczyk. All rights reserved.
//
// Released under the GPL license
// http://www.opensource.org/licenses/gpl-license.php
//
// This is an add-on for WordPress
// http://wordpress.org/
//
// **********************************************************************
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// **********************************************************************
//
//=======================
// Defines

define('WS_FETCH_SPACE', 30); //X seconds between fetches
define('WS_TWITTER_LIMIT', 200); //the most tweets you can pull from the Twitter REST API
define('WS_API_USER_TIMELINE', 'https://api.twitter.com/1.1/statuses/user_timeline.json');
define('WS_STATUS_URL', 'https://twitter.com/###USERNAME###/statuses/###STATUS###');
define('WS_PROFILE_URL', 'https://twitter.com/###USERNAME###');
define('WS_HASHTAG_URL', 'https://twitter.com/search?q=###HASHTAG###&src=hash');
define('WS_VERSION', '2.9');

//========================
// Digest cron activation

register_activation_hook(__FILE__, 'ws_activate_digest');
register_deactivation_hook(__FILE__, 'ws_deactivate_digest');
add_action('ws_td_digest_event', 'ws_ping_twitter');

function ws_activate_digest() {
wp_schedule_event(time(), 'hourly', 'ws_td_digest_event');
}

function ws_deactivate_digest() {
wp_clear_scheduled_hook('ws_td_digest_event');
}

// Check Twitter to see if there are any new tweets that need to be published.
// Return values:
// null -> all good
// 1 -> Error w/ twitter
// 2 -> Too soon
// 3 -> Missing username/password
// 4 -> Post created and published now or put in draft (depending on status option)
// 5 -> No tweets to post
// 6 -> Post created but not published until later
// 7 -> Wrong day of week

function ws_ping_twitter() {

// Do we have a username?
if (!get_option("ws_td_username")) {
return 3;
}

// We don't need the password anymore, so delete it.
if (get_option("ws_td_password")) {
delete_option("ws_td_password");
}

// Has there been enough time since the last check? This avoids a race
// condition which would produce a duplicate post.

$last = get_option('ws_td_last_check');
if (time() - $last < WS_FETCH_SPACE) { return 2; } update_option("ws_td_last_check", time()); // Is this a daily or weekly post? // Need to know today's local midnight to work out start/end times $midnight = strtotime(date('Y-m-d, 00:00:00', time() + ws_gmtOffset())); $pub_day = get_option('ws_td_pub_day'); $pub_freq = get_option('ws_td_freq'); $current_day_of_week = date("w", $midnight); // Test if it's weekly, then if so is today the right day of the week. If // not, return; pub_freq=0 is daily if ($pub_freq == 1) { if ($current_day_of_week != $pub_day) { return 7; } else { // 7 days ago at midnight $startDate = strtotime('-7 days', $midnight); } } else { // yesterday at midnight $startDate = strtotime('-1 days', $midnight); // $startDate = strtotime('-15 days', $midnight); } // yesterday at 11:59:59 $endDate = strtotime('-1 second', $midnight); $startDateStr = date(ws_getDateFormat(), $startDate); $endDateStr = date(ws_getDateFormat(), $endDate); // Get the last tweet id from the last post $lastTweet = get_option('ws_td_last_tweet'); if (!$lastTweet) { $lastTweet = 0; update_option('ws_td_last_tweet', $lastTweet); } // Get the tweets $numtweets = get_option('ws_td_num_tweets'); // range check. Twitter limit is to 200 if ($numtweets > WS_TWITTER_LIMIT) {
$numtweets = WS_TWITTER_LIMIT;
}

if ($numtweets == 0) {
$numtweets = 20; //default is 20 with no count argument, so fetch 20 if NULL
}

// get the last N tweets, since the last tweet
$url = WS_API_USER_TIMELINE . "?screen_name=".get_option('ws_td_username') ."&count=". $numtweets ;

if (get_option('ws_td_includeRTs')) {
$url .= '&include_rts=1';
}

if ($lastTweet) {
$url .= "&since_id=" . $lastTweet;
}

// Fetch the tweets
$tweets = ws_fetch_tweets($url);

// Go through the array and process any tweets from the desired time period
$tweet_content = array();
$tweet_ids = array(); //aki
$newLastTweet = false;
if ($tweets && count($tweets) >= 0) {
// process the tweets

foreach ($tweets as $tw_data) {
// Convert the time to local
$tw_data->created_at = strtotime($tw_data->created_at) + ws_gmtOffset();

// Was this tweet added in the time period of interest?
if ($tw_data->created_at < $startDate) { continue; } if ($tw_data->created_at > $endDate ) {
continue;
}

// Are we dropping replies?
if (get_option('ws_td_drop_replies') && preg_match('/^@.*/', $tw_data->text)) {
continue;
}

// All good, so format and add to the content
array_push($tweet_content, ws_format_tweet($tw_data)."\n\n");
array_push($tweet_ids, ws_format_tweet_id($tw_data)); //aki

// Remember the first tweet in the list, which is actually the most
// recently tweeted.
if (!$newLastTweet) {
$newLastTweet = $tw_data->id_str;
}
}

// To avoid race conditions, check to make sure lastTweet hasn't changed
if ($lastTweet == get_option('ws_td_last_tweet')) {

$count = count($tweet_content);

if ($count != 0 && $count >= get_option('ws_td_min_tweets')) {

// Before we push the content, we need to put it in the right order
if (get_option('ws_td_chrono') == 1) {
$tweet_content = array_reverse($tweet_content);
$tweet_ids = array_reverse($tweet_ids); //aki
}

$title = get_option('ws_td_title');
if (!$title) {
$title = "Tweets for %startdate";
}

// We look for %date to be backwards compatible
$title = preg_replace("/%date/", $startDateStr, $title);
$title = preg_replace("/%startdate/", $startDateStr, $title);
$title = preg_replace("/%enddate/", $endDateStr, $title);

$excerpt = get_option('ws_td_excerpt');
$excerpt = preg_replace("/%date/", $startDateStr, $excerpt);
$excerpt = preg_replace("/%startdate/", $startDateStr, $excerpt);
$excerpt = preg_replace("/%enddate/", $endDateStr, $excerpt);

//aki start
$postcontent = join("\n", $tweet_ids);
$postcontent .= "\n\n

    \n\n";
    //aki end
    //aki $postcontent .= "

      \n\n";
      $postcontent .= join("\n\n", $tweet_content);
      $postcontent .= "\n

    ";

    // This is messy, but good enough for now.
    if (ws_create_post($postcontent, $title, get_option('ws_td_pub_time'), $excerpt)
    == 1 /* Published in future */) {
    $retval = 6;
    } else {
    // Published now or drafted.
    $retval = 4;
    }

    // Update the last tweet id
    update_option('ws_td_last_tweet', $newLastTweet);

    return $retval;

    }
    else {
    return 5;
    }
    }

    }
    else {
    return 5;
    }

    }

    // This function creates the actual post and schedules it for publishing time
    // at $pubtime, if the status option is set to 'publish'. Otherwise the post
    // is put in 'draft' status.
    // Return values:
    // 0: published now
    // 1: published in future
    // 2: drafted
    function ws_create_post($content, $title, $pubtime, $excerpt) {

    global $wpdb;
    $result = 0;

    // Are we putting this in draft or publishing (now or later)?
    if (get_option('ws_td_status') == 'draft') {
    $status = 'draft';
    $result = 2;
    } else {

    $status='publish';

    // Are we doing this now or later?
    if ($pubtime) {
    $time = date('Y-m-d ').$pubtime.":00";
    $timestamp = strtotime($time);
    if ($timestamp > current_time('timestamp')) {
    $result = 1;
    }
    } else {
    $time = current_time('mysql');
    }
    }

    // Create the post
    $post_data = array(
    'post_content' => $wpdb->escape($content),
    'post_title' => $wpdb->escape($title),
    'post_date' => $time,
    'post_category' => array(get_option('ws_td_category')),
    'post_status' => $status,
    'post_author' => $wpdb->escape(get_option('ws_td_author')),
    'post_excerpt' => $wpdb->escape($excerpt)
    );

    // Insert post
    $post_id = wp_insert_post($post_data);
    add_post_meta($post_id, 'ws_tweeted', '1', true);

    // Tag it
    wp_set_post_tags($post_id, get_option('ws_td_post_tags'));

    return $result;
    }
    // customized by aki 2014/3/26
    function ws_format_tweet_id($tweet) {
    $output = ws_status_url(get_option('ws_td_username'), $tweet->id_str);
    return $output;
    }

    // Returns an html formatted $tweet. This is almost directly borrowed from Twitter Tools
    function ws_format_tweet($tweet) {
    $output = '

  • ';
    $output .= ws_make_clickable(wp_specialchars($tweet->text));
    if (!empty($tweet->in_reply_to_screen_name)
    && (!empty($tweet->in_reply_to_status_id))) {
    $output .= ' '.sprintf(__('in reply to %s', 'twitter-digest'), $tweet->in_reply_to_screen_name).'';
    }

    // Show the date/time if the options are selected
    $showTime = get_option('ws_td_showtime');
    if (!$showTime) { $showTime = 0; }
    // Show the date if the option is selectd
    $showDate = get_option('ws_td_showdate');
    if (!$showDate) { $showDate = 0; }

    $time_display = '';
    if ($showTime == 1) {
    $time_display = gmdate('H:i:s', $tweet->created_at);

    // Add the comma if we are going to show the date as well
    if ($showDate == 1) {
    $time_display .= ", ";
    }
    }

    // Add the date
    if ($showDate == 1) {
    $time_display .= gmdate('Y-m-d', $tweet->created_at);
    }

    // Use a small arrow for the status links if time and date are off
    if ($showDate != 1 && $showTime != 1) {
    $time_display = "->";
    }

    // Add the status link
    $username = get_option('ws_td_username');
    $output .= ' '.$time_display.'';

    $output .= '

  • ';
    return $output;

    }

    // Most of the following formatting functions are borrowed from Twitter Tool
    function ws_make_clickable($tweet) {

    // $tweet = preg_replace('/\@([a-zA-Z0-9_]{1,15}) /','@\\1 ', $tweet);
    $tweet = preg_replace_callback(
    '/((?:^|\s+|[\"\'\[\(]))\@([\w]{1,30})/',
    create_function(
    '$matches',
    'return $matches[1].ws_profile_link($matches[2]);'
    ),
    $tweet
    );
    $tweet = preg_replace_callback(
    '/((?:^|\s+|[\"\'\[\(]))\#([\w]{1,30})/',
    create_function(
    '$matches',
    'return $matches[1].ws_hashtag_link($matches[2]);'
    ),
    $tweet
    );

    // make_clickable doesn't handle (url) very well, so we'll chuck some spaces in
    $tweet = preg_replace("/\((http.*)\)/", "( $1 )", $tweet);

    return make_clickable($tweet);
    }

    function ws_status_url($username, $status) {
    return str_replace(
    array('###USERNAME###', '###STATUS###'),
    array($username, $status),
    WS_STATUS_URL);
    }

    function ws_profile_link($username, $prefix = '@') {
    return ''.$prefix.$username.'';
    }

    function ws_hashtag_link($hashtag, $prefix = '#') {
    return ''.$prefix.htmlspecialchars($hashtag).'';
    }

    function ws_profile_url($username) {
    return str_replace('###USERNAME###', $username, WS_PROFILE_URL);
    }

    function ws_hashtag_url($hashtag) {
    $hashtag = urlencode('#'.$hashtag);
    return str_replace('###HASHTAG###', $hashtag, WS_HASHTAG_URL);
    }

    // Based on Alex King's implementation for the Twitter Tools plugin
    function ws_fetch_tweets($url) {

    // First get our oauth connection
    require_once("twitteroauth.php");
    $consumerkey = get_option('ws_td_conskey'); // "3JucGrvAxOuaAYbLml5nkQ";
    $consumersecret = get_option('ws_td_conssecret'); //"iIRXb7c0VFwTYmI4TCNNIrUWB2YDxmL6c11YogcqQw0";
    $accesstoken = get_option('ws_td_acctoken'); // "14198382-SnzuP0AMz3qVWTolneB8QG7D1kHt6WNMosZzb48LE";
    $accesstokensecret = get_option('ws_td_accsecret'); // "9O7sc2tbea8PEx3Py9S4VWsHkbZtDBcP0n4HvnzokqM";

    if (!$consumerkey || !$consumersecret || !$accesstoken || !$accesstokensecret) {
    update_option('ws_td_error', 'Check your account information and make sure all token fields have a valid value.');
    return false;
    }

    $connection = new TwitterOAuth($consumerkey, $consumersecret,
    $accesstoken, $accesstokensecret);

    // Now make the request
    $tweets = $connection->get($url);

    // Check the error code
    if(empty($connection->http_code) || $connection->http_code != 200) {
    update_option('ws_td_error', 'Error retrieving tweets.
    Status: '.$connection->http_code.' Check your authentication tokens.
    '.print_r($connection->http_info, true));
    return false;
    }

    // Everything is ok
    update_option('ws_td_error','');

    return $tweets;
    }

    // Simply resets the 'ws_td_last_tweet' option
    function ws_td_resetDatabase() {
    update_option('ws_td_last_tweet',0);
    }

    // The menu hook
    function ws_menu_item() {
    if (current_user_can('manage_options')) {
    add_options_page(
    __('Twitter Digest Options', 'twitter-digest')
    , __('Twitter Digest', 'twitter-digest')
    , 'manage_options'
    , basename(__FILE__)
    , 'ws_options_form'
    );
    }
    }
    add_action('admin_menu', 'ws_menu_item');

    function ws_options_form() {

    // Check here if we are going to do the check now
    global $wpdb;

    // Reset the database if necessary
    if (isset($_POST["action"]) && $_POST["action"] == 'resetdb') {
    ws_td_resetDatabase();
    }

    // Get some variables together
    $ping_message = "(This may take a while if there are tweets to process.)";
    $ping_style="color: black";

    // Range check the publish day
    $pub_day_value = get_option('ws_td_pub_day');
    if ($pub_day_value < 0 || $pub_day_value > 6) { $pub_day_value = date("w"); }

    // yesterday or last week?
    if (get_option('ws_td_freq') == 0) {
    $periodStr = 'yesterday';
    } else {
    $periodStr = 'last week';
    }

    // Ping if necessary, and show the correct message.
    if (isset($_POST["action"]) && $_POST["action"] == "ping") {
    switch(ws_ping_twitter(true)) {
    case 1:
    $pwuser = get_option('ws_td_username');

    $ping_message = "Something went wrong with Twitter. Check for an error message above. Current Twitter username is " . $pwuser. "." ;
    $ping_style = "color: red";
    break;
    case 2:
    $ping_message = "To keep things sane, we're going to wait ".WS_FETCH_SPACE." seconds between pings.";
    $ping_style = "color: red";
    break;
    case 3:
    $ping_message = "Missing Twitter username and/or password";
    $ping_style = "color: red";
    break;
    case 4:
    $ping_message = "Post containing ".$periodStr."'s tweets has been ".(get_option('ws_td_status'). 'ed.');
    $ping_style ="color: green";
    break;
    case 6: $ping_message = "Post containing ".$periodStr ."'s tweets scheduled.";
    $ping_style = "color: green";
    break;
    case 5: $ping_message = "No new tweets found from " . $periodStr;
    break;
    case 7:
    $daylabel = date("l");
    $pub_day_str = ws_getDoW($pub_day_value);
    $now = date("H:i:s");
    $pub_time_str = get_option('ws_td_pub_time');
    if (!$pub_time_str) { $pub_time_str = '00:00:00'; }

    $ping_message = "Today is " . $daylabel . " and the local time is " . $now . " and you are configured to publish on " . $pub_day_str . " at " . $pub_time_str . ".";
    $ping_style = "color: red";
    }
    }

    // Get all the options together
    $categories = get_categories('hide_empty=0');
    $cat_options = '';
    foreach ($categories as $category) {
    if ($category->term_id == get_option('ws_td_category')) {
    $selected = 'selected="selected"';
    }
    else {
    $selected = '';
    }
    $cat_options .= "\n\t";
    }

    // get_users_of_blog is deprecated.
    $authors = get_users_of_blog();
    $author_options = '';
    foreach ($authors as $user) {
    $usero = new WP_User($user->user_id);
    $author = $usero->data;
    // Only list users who are allowed to publish
    if (! $usero->has_cap('publish_posts')) {
    continue;
    }
    if ($author->ID == get_option('ws_td_author')) {
    $selected = 'selected="selected"';
    }
    else {
    $selected = '';
    }
    $author_options .= "\n\t";
    }

    // Set up the options for the status. Just draft or publish for now.
    $status_options = '';
    if (get_option('ws_td_status') == 'draft') {
    $status_options ='';
    } else {
    $status_options ='';
    }

    // Drop replies options
    $drop_replies_check = get_option('ws_td_drop_replies') == 1 ? 'checked="true"' : '';
    // Chrono options
    $chrono_check = get_option('ws_td_chrono') == 1 ? 'checked="true"' : '';
    // Show date options
    $showDate_check = get_option('ws_td_showdate') == 1 ? 'checked="true"' : '';
    $showTime_check = get_option('ws_td_showtime') == 1 ? 'checked="true"' : '';
    $includeRTs_check = get_option('ws_td_includeRTs') == 1 ? 'checked="true"' : '';

    // Publishing frequency options
    if (get_option('ws_td_freq') == 1) {
    $freq_options = '';

    $pub_day_display = '';
    } else {
    $freq_options = '';
    $pub_day_display = 'display: none';
    }

    // DoW option
    $pub_day_options = '';
    for ($i = 0; $i < 7; $i++) { $pub_day_options .= '

    // Default the min tweet value to 1
    $min_tweets_value = get_option('ws_td_min_tweets');
    if ($min_tweets_value < 1) { $min_tweets_value = 1; } // Default the number of tweets to 20 $num_tweets_value = get_option('ws_td_num_tweets'); if (!$num_tweets_value) { $num_tweets_value = 20; } print('

    '.__('Twitter Digest Options', 'twitter-tools').'

    '.ws_getErrorMessage().'


    '.wp_nonce_field('update-options').'


    Twitter Account Info






    Publish Options




    '._('Specify the time at which the tweet post should be published in 24 hour format e.g. 2:22pm = 14:22 or leave empty for ASAP').'

    '._('Specify the day of the week the post should be published in number format (0-6). e.g. Sunday = 0').'

    Post Options



    '._('Use %startdate and %enddate to insert the date').'


    '._('Use %startdate and %enddate to insert the dates').'


    '._('Defaults to Y-m-d').'



    '._('Separate multiple tags with commas. Example: tweets, twitter').'











    '.$ping_message.'



    Clicking this button resets the Twitter Digest database and may result in duplicate posts.

    ');
    }

    // Get the date option or fall back to the default
    function ws_getDateFormat() {
    $format = get_option('ws_td_dateFormat');
    if ($format == '') {
    $format = 'Y-m-d';
    }
    return $format;
    }

    // Get the string rep of a day given the index. Sunday = 0;
    function ws_getDoW($num) {
    switch($num) {
    case 0:
    return "Sunday";
    break;
    case 1:
    return "Monday";
    break;
    case 2:
    return "Tuesday";
    break;
    case 3:
    return "Wednesday";
    break;
    case 4:
    return "Thursday";
    break;
    case 5:
    return "Friday";
    break;
    case 6:
    return "Saturday";
    }

    }

    // Returns the gmt offset for WordPress in seconds
    function ws_gmtOffset() {
    return get_option('gmt_offset') * 3600;
    }

    function ws_gmtTime() {
    return time() - date("Z");
    }

    function ws_getErrorMessage() {
    $error = get_option('ws_td_error');
    if ($error) {
    return '

    '.$error.'

    ';
    }
    }

    define('MYPLUGIN_FOLDER', dirname(__FILE__));

対策法:WordPressでテーマを選んだら真っ白になった

研究室のWordpressで、とあるユーザが「テーマを選んだら画面が真っ白になって、ダッシュボードにも入れなくなりました!!」とヘルプを求めてきたので、以下メモ。

現象としては、ライブプレビューで「deep-silent」というテーマを選んでいて、

『なんかよさそうだな―』とおもって適用したとたんに真っ白になった、とのこと。

まあここまで状況説明できるユーザならまだいいのですが、管理者としてサーバにログインできるなら、

# tail /var/log/httpd/error-log
を見てみてほしい。

今回の場合はこんなエラーが出ていました。

[Mon Feb 18 17:20:39 2013] [error] [client 223.218.158.67] PHP Parse error:  syntax error, unexpected $end in /var/www/wordpress/wp-content/themes/deep-silent/functions.php on line 409

というわけで、下手人は「deep-silent」、しかもSyntax Errorとは!
とりあえず、Googleで調べてみましょう。いちおうダッシュボードからインストールしたテーマであれば公式にあるはずです。

http://wordpress.org/extend/themes/deep-silent

[browser-shot url=”http://wordpress.org/extend/themes/deep-silent” width=”600″]

よく見ると、

This theme hasn’t been updated in over 2 years. It may no longer be maintained or supported and may have compatibility issues when used with more recent versions of WordPress.

って最終更新日が2009年。もう3年近く更新されていない。
こういうテーマは新しいWordpressに対応していないばかりか、作者がやる気を失っているか、お亡くなりになったか、そもそも責任感がない人の作品なので継続して使い続けるのは危ないです。

サーバーログが見れないレンタルサーバーなどであった場合、wp-config.phpでデバッグモードをONにすれば、当該ページは真っ白ではなく、エラーが表示されます。
//define('WP_DEBUG', false);
define('WP_DEBUG', TRUE);

としておけばよいでしょう。viの使い方がわからない人はWinSCPなどで編集すればいいです。

で、問題のページを開けば、真っ白ではなくエラーが出るはずです。
今回の問題はログで指摘されている通り、functions.php の書式エラーで409行にいきなり終了が現れたと。
vi ./wp-content/themes/deep-silent/functions.php
とするとviが開きますので「ESC-:-409」と打って、409行目に飛びましょう。エスケープキーを押して「:409」です。

[php]
function coba(){
global $wpdb;
if($_POST[‘send’]==’setting’){
$tmp_name =& $_FILES[‘gambar’][‘tmp_name’];
$author_id ="test";
$ext="jpg";
$name = ABSPATH . ‘wp-content/authors/’ . $author_id . ‘.’ . $ext;
@move_uploaded_file($tmp_name, $name);
@chmod($name, 0666);
}
?>
<div class="wrap">
<form action="#" method="post" enctype="multipart/form-data">
<input type="hidden" name="send" value="setting">
<table width="80%" border="">
<tr><td align="center" colspan="2"><b>Upload photo</b></td></tr>
<tr><td align="center"><input type="file" name="gambar"></td></tr>
<tr><td align="center"><input type="submit"></td></tr>
</table>
</form>
</div>
<?
}
?>
[/php]

これは…!何が間違っているかわかりますか?
下から2行目を「 (viでは、「ESC-i」として編集モードにして、書き足したら、「ESC-:wq」で書き込み終了)

環境によってはphpは
<?~?> でも通りますが、それはphp.iniで設定しないと無理ですね。

これにて一件落着!

無事にダッシュボードが見れたので、他のテーマに移行するか自分で書いてな~。

wordpress3.5の新機能すごい

とりあえずサーバメンテついでに色々試しているけど、これはすごいね!

特にメディアライブラリの機能。

いちばんビックリしたのが、Twitterとの連携。

http://t.co/6MTAnVgR

という感じでiPhoneを使って画像添付で呟いた投稿が、そのままメディアライブラリに取り込まれている…!

ちなみにt.coなどの短縮URLも自動展開されるっぽいが、今のところ内部動作はよくわからない、既存のプラグインが持っている機能とぶつかってる気もする。

http://t.co/6MTAnVgR

画像ファイルは pic.twitter.com からではなく、実際のファイルがサーバ側に落ちているようです。

これは撮った写真のバックアップにもなるではないか。

一生懸命、nextGen Galleyプラグインとか、単体でGallery3とかインストールしてたけど、これらは非公開ギャラリー向けの機能になりそうですね。

他にはHTMLソースモードが「テキスト」なったとか。テーマが2013になったとか。

マルチサイトにも変更があったようですが、まだよくわかりません。継続調査中。

いずれにせよ、たくさん写真を撮る年末年始に良い機能だわ~!

オリジナルフォントでBlogを書いてみた

メディアアート講義20121206板書より
【メディアアート講義20121206板書より】

メディアアートの講義と3年生向けゼミで「手書き文字の重要性」について語ったので、
かねてからの夢であった「自分の文字でBlogを書く」という行為を実現してみた。

【使ったソフト】
おれん字 2

まるで手書き

http://www.amazon.co.jp/exec/obidos/ASIN/B000BDKUEM/
[browser-shot url=”http://www.amazon.co.jp/exec/obidos/ASIN/B000BDKUEM/amazonas-22/” width=”600″]

実際に使ったのは「手作りおれん字」。
このバージョンはちょっと安いけど、不在の場合は豆腐「・」になってしまうので高いヤツのほうがいいと思う。

原稿ファイルも横書きだとうまくスキャナが読み取ってくれないので、縦書のほうが良い。
ソフトの説明には「1ミリ程度」と書いてあるけど、細すぎて読みづらい。
作りたい人にもよるかもしれないが。
あと、半角英数は時間かけたほうが良い。記号もないと長音がないので読めない。

Blogで使うにはTTFだけでなくEOTも作っておくほうが良い。

【TTFフォントからEOTフォントに変換するサイト】
http://ttf2eot.sebastiankippe.com/
[browser-shot url=”http://ttf2eot.sebastiankippe.com/” width=”600″]

WordPress/wp-content/theme/–テーマ名–/にTTFとEOTファイルを置く。
style.cssを編集。

ファイル名は「aki2012」とした(歳を追うごとに字体が変わって来ているので…)

[css]
@font-face {
font-family: ‘aki2012’;
src: url(aki2012.eot);/* IE */
}
@font-face {
font-family: ‘aki2012’;
src: local(‘aki2012’),
local(‘aki2012’),
url(‘aki2012.ttf’) format(‘truetype’); /* Safari、Chrome、Firefox、Opera */
}

body {
background:#fff url(&amp;quot;images/top.gif&amp;quot;;) repeat-x;
font-family: &amp;quot;aki2012&amp;quot;,略,Helvetica, Arial, sans-serif;
font-size:20px; //大きめの方が良い
color:#222;
padding:0;
margin:0;
}
[/css]

で、Wordpressに仕込むとこんな感じのサイトになった。
もちろんChromeだと表示できる。iPhoneでもOK。
で、もちろん読みづらくなった。
でもいいんだ、このサイトはこれで。私の魂の叫びなので。

ちゃんとGoogleにもひっかかるし、フォント変えたり、コピペすれば読めますよね?

そのうち第2水準漢字まで頑張ってみようと思います。

20121206-001653.jpg

写経の山が出来上がる。

20121206-001701.jpg

最初のバージョン。よくみると分かるのですが、ひらがなカタカナが細すぎます。キャラじゃない感じ。

マッキーの極細から太い側に持ち替えて書き直しです。

20121206-005226.jpg
だいぶんとできあがってきました。

iPhone/携帯版なら読めるという選択性も残したゾ。