IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

はてなダイアリー新機能「Twitter記法」を使って、トゥギャッターを引用するブックマークレット

はてなダイアリーがバージョンアップしましたね!!!!

やったー!

主な新機能・改善の内容

バージョンアップに伴う新機能・改善の主な内容は以下の通りです

  • 管理画面を大幅刷新しました。デザインを調整し、ナビゲーション改善など使い勝手の改善を行いました
  • iPhone 専用の閲覧画面 (iPhone ビュー) を追加しました
  • Twitter と連携する各種新機能を追加しました
  • 2つめ以上のブログを追加できる複数ブログ作成機能を追加しました (はてなダイアリープラスのみ)
  • サイトマップ作成、meta タグの設定などが行えるSEO設定画面を追加しました (はてなダイアリープラスのみ)
  • サーバー増強、アプリケーション改善による応答速度改善を実施しました
  • ほか、新しい設定項目の追加など細かな改善を実施しました
はてなダイアリーのバージョンアップを実施しました ― iPhone、Twitter、SEO、複数ブログ、応答改善など - はてなダイアリー日記

やったー!!まじでうれしい!!!

と言うわけで

新しく追加された Twitter 記法を使って Togetter の引用をするブックマークレットを作ってみました!!!

使いかた

1. ブックマークを追加

以下のページに飛んで、ブックマークレットをお気に入りに追加します。
このトゥギャりを引用 - Hatena::Let

2. 引用したい Togetter のページに行く

引用したい Togetter のまとめページhttp://togetter.com/li/* のページ)に行きます。

3. ブックマークレットを実行!

そのページでブックマークレットを実行します!

4. はてなダイアリーを編集

すると、はてな記法で引用が生成されるので、編集します。

5. はてなダイアリーを投稿

あとは、投稿するだけ!
おおおお!簡単ですね!

と言うわけで

今回は、はてなダイアリーの新機能 Twitter 記法を使って Togetter の引用を自動生成してみました!
Twitter 記法、すごい便利ですね!
しかし、この機能ははてなダイアリーバージョンアップのほんの一部の機能でしかありません!
な、なんといことでしょう。
実は、もっともっとすごい機能がたくさんあります!
ああ、僕はどうしたらいいんだー!
はてなダイアリーほんとに素晴らしいですね!

追記

今週のお題に便乗しときます><

ついったったーの情報をデータベースに突っ込む!

久々に Perl 書いた

#! /usr/bin/perl

use strict;
use warnings;
use utf8;

use DBI;
use HTTP::Date;
use Net::Twitter;
use File::Basename;
use Data::Dumper;
use Config::Pit;

my $config = pit_get('twitter.com');

my $dbh = DBI->connect('dbi:SQLite:twitter.db', '', '', { AutoCommit => 0 });
die DBI->errstr unless $dbh;
my $twitter = Net::Twitter->new( username => $config->{username}, password => $config->{password} );

my $limit = $twitter->rate_limit_status;
if ($limit->{remaining_hits} == 0) {
    while($limit->{reset_time_in_seconds} - time() > -10) {
        sleep(1);
        print "limit remaining time: " . ($limit->{reset_time_in_seconds} - time()) . " s \n";
    }
}

my $rv = $dbh->do('CREATE TABLE IF NOT EXISTS status (id INTEGER PRIMARY KEY, last_cursor INTEGER)');
die DBI->errstr unless $rv;
$rv = $dbh->do(q/
        CREATE TABLE IF NOT EXISTS user (
            id INTEGER PRIMARY KEY ON CONFLICT REPLACE,
            screen_name TEXT,
            name TEXT,
            created_at DATETIME,
            description TEXT,
            url TEXT,
            lang TEXT,
            location TEXT,
            profile_image_url TEXT,
            followers_count INTEGER,
            friends_count INTEGER,
            favourites_count INTEGER,
            statuses_count INTEGER,
            time_zone TEXT,
            utc_offset INTEGER,
            geo_enabled BOOLEAN,
            profile_background_image_url TEXT,
            profile_text_color INTEGER,
            profile_link_color INTEGER,
            profile_sidebar_fill_color INTEGER,
            profile_sidebar_border_color INTEGER,
            profile_background_color INTEGER,
            profile_background_tile INTEGER,
            notifications BOOLEAN,
            following BOOLEAN,
            protected BOOLEAN,
            verified BOOLEAN,
            contributors_enabled INTEGER,
            last_status_source TEXT,
            last_status_truncated INTEGER,
            last_status_favorited INTEGER,
            last_status_created_at DATETIME,
            last_status_text TEXT,
            last_status_in_reply_to_user_id INTEGER,
            last_status_id INTEGER,
            last_status_in_reply_to_status_id INTEGER,
            last_status_in_reply_to_screen_name TEXT
            )
            /);
            die DBI->errstr unless $rv;

            my $sth_save_status = $dbh->prepare(q/
                    INSERT OR REPLACE INTO status (
                        id, last_cursor
                        )
                    VALUES (1, ?)
                    /);
            die DBI->errstr unless $sth_save_status;
            my $sth_load_status = $dbh->prepare(q/
                    SELECT id, last_cursor FROM status WHERE id = 1
                    /);
            die DBI->errstr unless $sth_load_status;
            my $sth_insert_user = $dbh->prepare(q/
                    INSERT OR REPLACE INTO user (
                        id, screen_name, name, created_at, description, url, lang, location, profile_image_url, followers_count,
                        friends_count, favourites_count, statuses_count, time_zone, utc_offset, geo_enabled, profile_background_image_url,
                        profile_text_color, profile_link_color, profile_sidebar_fill_color, profile_sidebar_border_color, profile_background_color,
                        profile_background_tile, notifications, following, protected, verified, contributors_enabled, last_status_source,
                        last_status_truncated, last_status_favorited, last_status_created_at, last_status_text, last_status_in_reply_to_user_id,
                        last_status_id, last_status_in_reply_to_status_id, last_status_in_reply_to_screen_name
                        )
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    /);
            die DBI->errstr unless $sth_insert_user;
            my $sth_check_user = $dbh->prepare(q/
                    SELECT id FROM user WHERE id = ?
                    /);
            die DBI->errstr unless $sth_check_user;

            my $followers;
            my $cursor = -1;
            $rv = $sth_load_status->execute;
            die $sth_load_status->errstr unless $rv;
            my $status_row = $sth_load_status->fetchrow_arrayref;
            if ($status_row) {
                my ($id, $last_cursor) = @$status_row;
                $cursor = $last_cursor;
                print "loaded $cursor\n";
            }

while (1) {
    last unless $cursor;
    eval {
        $followers = $twitter->followers({ cursor => $cursor });
        if (defined($followers)) {
            eval {
                for my $tweeter (@{$followers->{users}}) {
                    insert_user($tweeter);
                }
                my $rv = $sth_save_status->execute($followers->{next_cursor});
                die $sth_save_status->errstr unless $rv;
                $dbh->commit;
            };
            if ($@) {
                $dbh->rollback;
                die $@;
            }
        }
        else {
            print "current_cursor = " . $cursor . "!!!!\n";
            print "try again!!!!\n";
            print $twitter->http_message . "\n";
            if ($twitter->http_message eq 'Bad Request') {
                die $twitter->http_message;
            }
            $followers = { next_cursor => ($cursor - 100000000000000) }
        }
    };
    if ($@) {
        die $@;
    }
    $cursor = $followers->{next_cursor};
}

$cursor = -1;
while (1) {
    last unless $cursor;
    my $follower_ids;
    my $tweeter_id;
    eval {
        $follower_ids = $twitter->followers_ids({ cursor => $cursor });
        if (defined($follower_ids)) {
            for $tweeter_id (@{$follower_ids->{ids}}) {
                eval {
                    my $rv = $sth_check_user->execute($tweeter_id);
                    die $sth_check_user->errstr unless $rv;
                    my $tweeter_row = $sth_check_user->fetchrow_arrayref;
                    unless ($tweeter_row) {
                        my $tweeter = $twitter->show_user($tweeter_id);
                        if ($tweeter) {
                            insert_user($tweeter);
                        }
                        else {
                            print "id = $tweeter_id , message = " . $twitter->http_message . "\n";
                            if ($twitter->http_message eq 'Bad Request') {
                                die $twitter->http_message;
                            }
                        }
                    }
                    $dbh->commit;
                };
                if ($@) {
                    $dbh->rollback;
                    die $@;
                }
            }
        }
        else {
            print "current_cursor = " . $cursor . "!!!!\n";
            die $twitter->http_message;
        }
    };
    if ($@) {
        die $@;
    }
    $cursor = $follower_ids->{next_cursor};
}

sub insert_user {
    my $tweeter = shift;
    my $rv = $sth_insert_user->execute(
        $tweeter->{id},
        $tweeter->{screen_name},
        $tweeter->{name},
        create_iso_time($tweeter->{created_at}),
        $tweeter->{description},
        $tweeter->{url},
        $tweeter->{lang},
        $tweeter->{location},
        $tweeter->{profile_image_url},
        $tweeter->{followers_count},
        $tweeter->{friends_count},
        $tweeter->{favourites_count},
        $tweeter->{statuses_count},
        $tweeter->{time_zone},
        $tweeter->{utc_offset},
        $tweeter->{geo_enabled},
        $tweeter->{profile_background_image_url},
        $tweeter->{profile_text_color},
        $tweeter->{profile_link_color},
        $tweeter->{profile_sidebar_fill_color},
        $tweeter->{profile_sidebar_border_color},
        $tweeter->{profile_background_color},
        $tweeter->{profile_background_tile},
        $tweeter->{notifications},
        $tweeter->{following},
        $tweeter->{protected},
        $tweeter->{verified},
        $tweeter->{contributors_enabled},
        $tweeter->{status}->{source},
        $tweeter->{status}->{truncated},
        $tweeter->{status}->{favorited},
        create_iso_time($tweeter->{status}->{created_at}),
        $tweeter->{status}->{text},
        $tweeter->{status}->{in_reply_to_user_id},
        $tweeter->{status}->{id},
        $tweeter->{status}->{in_reply_to_status_id},
        $tweeter->{status}->{in_reply_to_screen_name},
    );
    die $sth_insert_user->errstr unless $rv;
    print "data insert " . $tweeter->{screen_name} . "\n";
}

sub create_iso_time {
    my $created_at = shift;
    if (defined $created_at) {
        $created_at =~ s/\+0000/UTC/;
        $created_at = HTTP::Date::time2iso(HTTP::Date::str2time($created_at));
    }
    else {
        $created_at = undef;
    }
    return $created_at;
}

「ツイート」を「ツイート(笑)」に置換するブックマークレット

【急募】twitterトップページのツイートをツイート(笑)に置換するグリモン

notfunc on Twitter: "【急募】twitterトップページのツイートをツイート(笑)に置換するグリモン"

というわけで書いてみた

javascript:var r=document.evaluate("//text()[contains(., 'ツイート')]", document, null, 7, null);for(var i=0;i<r.snapshotLength;i++)r.snapshotItem(i).nodeValue=r.snapshotItem(i).nodeValue.replace(/ツイート/g, 'ツイート(笑)');void(0);

Firefox 系と WebKit 系と Opera 系で動きます。たぶん。
ロケーションバー(URL が書いてあるところ)に貼付けてエンターで起動できます!

Twitter の半径数クリック以内の情報収集

ちょっと

現実頭皮的に自己満足的プログラムを書きたくなったので Twitterクローラーを書いてみた。
C++ にしては、割とすっきり書けて満足。

使ったライブラリ

ソース

#include <cassert>
#include <soci.h>
#include <soci-sqlite3.h>
#include <unistd.h>
#include <iostream>
#include <sstream>
#include <picojson.h>
#include <boost/scoped_ptr.hpp>
#include <boost/asio.hpp>
#include <boost/cast.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

using namespace std;
using namespace SOCI;
using namespace boost;
using namespace boost::posix_time;
using namespace boost::gregorian;
namespace j = picojson;
typedef asio::ip::tcp::iostream tcpstream;

void interval(unsigned int t) {
    cout << "Interval " << t <<"[s]" << endl;
    sleep(t);
}

template<class T>
j::value request(const std::string &api_path, const T &id) {

    interval(30);

    // Request Friends API
    cout << "Request http://twitter.com" << api_path << "/" << id << ".json" << endl;
    tcpstream s("twitter.com", "http");
    s << "GET " << api_path << "/" << id << ".json HTTP/1.0\r\nHost: twitter.com\r\n\r\n" << flush;

    if (s) {
        // Skip Header
        string line;
        while (getline(s, line)) if (line == "\r") break;

        if (s) {
            // Parse JSON
            j::value v;
            string err = j::parse(v, s);

            return v; // Expect RVO
        }
    }

    j::value null;
    return null;
}


template<class T> bool exists(Session& sql, T id);

template<>
bool exists<string>(Session& sql, string screen_name) {
    int exists = 0;
    cout << "Ckeck conflict " << screen_name << endl;
    sql << "SELECT count(id) FROM user WHERE screen_name = :screen_name", use(screen_name), into(exists);
    return static_cast<bool>(exists);
}

template<>
bool exists<unsigned long>(Session& sql, unsigned long id) {
    int exists = 0;
    cout << "Ckeck conflict " << id << endl;
    sql << "SELECT count(id) FROM user WHERE id = :id", use(id), into(exists);
    return static_cast<bool>(exists);
}

template<class T>
bool check(const j::value& v) {

    // Error check
    if (v.is<j::object>()) {
        j::value error = v.get("error");
        if (!error.is<j::null>()) {
            cout << "Oops API error!! (" << v << ")" << endl;
            interval(1000);
            return false;
        }
    }

    // Type check
    if (!v.is<T>()) {
        cout << "Oops Type mismatch!! (" << v << ")" << endl;
        interval(100);
        return false;
    }

    return true;
}

template<class T>
T get(const j::value& v) {
    if (v.is<T>()) {
        return v.get<T>();
    }
    return T();
}

const unsigned int end_depth = 2;

template<class T>
void fetch(Session& sql, T id, unsigned int depth = 0) {

    while(true) {

        if (exists(sql, id)) {
            cout << "Ignore (" << id << ")" << endl;
        }
        else {
            // First fetch self data
            const j::value& value = request("/users/show", id);

            if (!check<j::object>(value)) continue;

            j::object v = value.get<j::object>();

            unsigned long uid = numeric_cast<unsigned long>(get<double>(v["id"]));
            unsigned long friends_count = numeric_cast<unsigned long>(get<double>(v["friends_count"]));
            unsigned long followers_count = numeric_cast<unsigned long>(get<double>(v["followers_count"]));
            unsigned long favourites_count = numeric_cast<unsigned long>(get<double>(v["favourites_count"]));
            unsigned long statuses_count = numeric_cast<unsigned long>(get<double>(v["statuses_count"]));
            unsigned long created_at = (lexical_cast<ptime>(get<string>(v["created_at"])) - ptime(date(1970, Jan, 1))).total_seconds();
            string time_zone = get<string>(v["time_zone"]);
            string screen_name = get<string>(v["screen_name"]);
            string profile_image_url = get<string>(v["profile_image_url"]);

            cout << "Insert " << screen_name << "(" << uid << ")" << endl;

            // Insert data
            sql << "INSERT INTO user VALUES("
                        ":id, :screen_name, :friends_count, :followers_count, :favourites_count, "
                        ":statuses_count, :created_at, :time_zone, :profile_image_url)",
                        use(uid), use(screen_name), use(friends_count), use(followers_count), use(favourites_count), 
                        use(statuses_count), use(created_at), use(time_zone), use(profile_image_url);
        }

        // Next fetch children data
        if (depth < end_depth) {
            cout << "Next depth" << endl;

            while (true) {
                const j::value &value = request("/friends/ids", id);

                if (!check<j::array>(value)) continue;

                j::array ids = value.get<j::array>();

                for (j::array::const_iterator it = ids.begin(); it != ids.end(); ++it) {
                    unsigned long uid = numeric_cast<unsigned long>(it->get<double>());
                    fetch(sql, uid, depth + 1);
                }
                break;
            }
        }
        break;
    }
}

int main() {
    // Setting ptime locale for lexical_cast
    locale::global(locale(locale(), new time_input_facet("%a %b %d %H:%M:%S +0000 %Y")));

    try {
        Session sql(sqlite3, "twitter.db");

        // Create table
        sql << "CREATE TABLE IF NOT EXISTS user ("
                    "id INTEGER PRIMARY KEY, screen_name TEXT UNIQUE, friends_count INTEGER, followers_count INTEGER, favourites_count INTEGER, "
                    "statuses_count INTEGER, created_at INTEGER, time_zone TEXT, profile_image_url TEXT)";

        fetch(sql, std::string("amachang"));
    }
    catch(const std::exception &e) {
        cerr << "Error: " << e.what() << "\n";
    }
}

みんなの願い事が分かる API

みなさん!七夕ですね!

七夕といえば、願い事!
みなさんは、どんな願い事をしますか?
そして、気になるあの子はどんな願い事をしているのでしょうか?
気になりません?

それならこれ!

Twitter ユーザーみんなの願い事が以下の API に集まって来るようです。
http://search.twitter.com/search.json?q=%23tanzaku
なんだかワクワクしますね!

さっそく

僕もこんなものを作ってみました(IE は 8 以上でしか確認していません><)
http://amachang.sakura.ne.jp/misc/tanabata/

というわけで

みなさんも一年に一度だけの、 API 試してはいかがでしょうか☆キラッ

はてブの Web Hook で Twitter を更新する

はてなブックマークWeb Hook というのがリリースされましたね!

(開発者さま向け) はてなブックマーク Web Hook 機能を公開しました - はてなブックマーク日記 - 機能変更、お知らせなど
はてなブックマーク Web Hookとは - はてなキーワード
これを設定しておくと、自分がはてブしたタイミングではてブ側から CGI 等を叩いてくれます。
CGI から様々なプログラムを起動するようにしておけば、あんなことやこんなことが可能になっちゃいますね><
こ、これはすごい!
ということでさっそく、自分がはてブしたページの URL を Twitter に POST する CGI を書いてみた。

ご自由にお使いください

#!/usr/bin/env perl

use utf8;
use strict;
use warnings;

use CGI;
use Net::Twitter;

my $req = CGI->new;
$req->charset('utf-8');

if ($req->param('key') ne 'API_PASSWORD') {
        die "Authentication failed";
}

my $url    = $req->param('url');
my $title = $req->param('title');
my $comment     = $req->param('comment');

Net::Twitter->new( username => "TWITTER_USER", password => "TWITTER_PASSWORD" )->update("[Hatena::B] $comment $url $title");

print header('text/plain');
print 'ok';

ご自由にお使いください

HANDAN☆メーカーというサービスを作った

はじめに

はてブのコメントで客観ぶって「判断は保留」とか言ってるやつが一番チキンで最悪。はてブみたいにあれだけ個人が保護された環境下においても自分の中にまともな判断基準を持てないような輩は普通にポピュリズムに突き動かされて差別とかヘイトスピーチとか何なら虐殺とか平気で荷担しそうな感じ。

津田大介 on Twitter: "はてブのコメントで客観ぶって「判断は保留」とか言ってるやつが一番チキンで最悪。はてブみたいにあれだけ個人が保護された環境下においても自分の中にまともな判断基準を持てないような輩は普通にポピュリズムに突き動かされて差別とかヘイトスピーチとか何なら虐殺とか平気で荷担しそうな感じ。"

うほっ。これはいいテンプレ

というわけで

またもや脊髄反射で作ってみました
http://amachang.sakura.ne.jp/misc/hanpo/
ご自由にお使いください。