ラベル Twitter の投稿を表示しています。 すべての投稿を表示
ラベル Twitter の投稿を表示しています。 すべての投稿を表示

2025年3月8日土曜日

Twitter(現X)にAPI経由でつぶやく2025年版

Twitter(現X)のAPIが大きく制限されてから久しいですが、久しぶりに最新のAPIを触りましたので記録しておきます。

まず、Twitter APIの認証にはOAuth 1.0aとOAuth 2.0の2種類がサポートされていますが、ユースケースによってそれらを使い分ける必要があります。2024年10月頃まではOAuth 2.0経由のツイートの投稿ができていたようですが、その後使えなくなったようです。

2025年現在の対応表は以下のようになっています。

今回はツイートを投稿しますので、OAuth 1.0a認証を行います。

APIキーの取得

何はともあれAPIキーを取得しなければ始まりません。Twitter Developer Accountにアクセスして、アプリを登録し、キーを確認します。

ここにある「API Key and Secret」と「Access Token and Secret」の計4種類を控えておきます。 API Key and Secretはアプリ特有のもの、Access Token and Secretは投稿するアカウント特有のものですが、そもそも無料アプリでは1アプリ&1アカウントまでしか使用できないので、これをそのまま使えば良いでしょう。

コード

投稿は以下のコードにて行います。

public class TwitterClient(HttpClient httpClient, string consumerKey, string consumerSecret, string accessToken, string tokenSecret)
{
    public async Task<HttpResponseMessage> PostTweetAsync(string text)
    {
        var timstamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
        var nonce = CreateNonce();
        var body = JsonSerializer.Serialize(new Dictionary<string, string>() { { "text", text } });
        var uri = new Uri("https://api.twitter.com/2/tweets");

        var request = new HttpRequestMessage {
            RequestUri = uri,
            Method = HttpMethod.Post,
            Content = new StringContent(body, Encoding.ASCII, "application/json")
        };

        var signatureBase64 = CreateSignature(uri.ToString(), "POST", nonce, timstamp);

        request.Headers.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("OAuth",
                $@"oauth_consumer_key=""{Uri.EscapeDataString(consumerKey)}""," +
                $@"oauth_token=""{Uri.EscapeDataString(accessToken)}""," +
                $@"oauth_signature_method=""HMAC-SHA1""," +
                $@"oauth_timestamp=""{Uri.EscapeDataString(timstamp)}""," +
                $@"oauth_nonce=""{Uri.EscapeDataString(nonce)}""," +
                $@"oauth_version=""1.0""," +
                $@"oauth_signature=""{Uri.EscapeDataString(signatureBase64)}""");

        return await httpClient.SendAsync(request);
    }

    private string CreateSignature(string url, string method, string nonce, string timestamp)
    {
        var query = HttpUtility.ParseQueryString("");
        query.Add("oauth_consumer_key", consumerKey);
        query.Add("oauth_nonce", nonce);
        query.Add("oauth_signature_method", "HMAC-SHA1");
        query.Add("oauth_timestamp", timestamp);
        query.Add("oauth_token", accessToken);
        query.Add("oauth_version", "1.0");

        var signatureBaseString = $"{method}&{Uri.EscapeDataString(url)}&{Uri.EscapeDataString(query.ToString() ?? "")}";
        var compositeKey = $"{Uri.EscapeDataString(consumerSecret)}&{Uri.EscapeDataString(tokenSecret)}";

        using var hasher = new HMACSHA1(Encoding.ASCII.GetBytes(compositeKey));
        return Convert.ToBase64String(hasher.ComputeHash(Encoding.ASCII.GetBytes(signatureBaseString)));
    }

    static readonly Random random = new Random();

    private static string CreateNonce()
    {
        var nonce = new StringBuilder(32);

        for(int i = 0; i < 32; i++) {
            var rnd = random.Next(62);
            if(rnd < 26)
                nonce.Append((char)('A' + rnd));
            else if(rnd < 52)
                nonce.Append((char)('a' + rnd - 26));
            else
                nonce.Append((char)('0' + rnd - 52));
        }

        return nonce.ToString();
    }    
}

コンストラクタに引き渡すconsumerKey, consumerSecret, accessToken, tokenSecretは先ほど控えた4つのキーです。そのうえでPostTweetAsyncメソッドに投稿内容を投げてあげればつぶやけます。意外と簡単ですね。Twitter Developer Accountのサイトからキーを全て入手できるので、認証の面倒な手続きを実装する必要もありません。

ちなみに、 無料アカウントでは24時間あたり17件までしかツイートできません。実装したアプリをいろいろ試しているうちにあっという間に返ってくるHTTPステータスコードが429 Too Many Requestsになってしまいます。そうなったら諦めて24時間待ちましょう。HTTP Responseの詳細の中に"x-user-limit-24hour-reset"というものがあり、これをUnix timeとして読むと制限が解除される日時がわかります。

まあこんなんじゃ無料でAPIをいじってみようかとか、ライブラリ作って公開しようかとかいう人が全然現れなくなるのもやむを得ないですよね。まあ、APIを叩いてほしくないからこういうふうにしているのでしょうが。

2015年8月4日火曜日

Janetterで自分あて@無しリプライをする

※今回はプラグインではなくJanetterの改造となります。 より慎重にお願いします。

さて、Janetterの登録アカウント数がTwitterの定める上限に達してから久しいですが、最近Twitter公式への複数画像の投稿機能、DMの文字数制限撤廃対応など最低限のメンテナンスはしてくれています。とてもありがたいです。

しかし1つ、Twitterの機能に追従していないことがあります。それが「自分あて@無しリプライ」です。Twitterは基本的に対応するユーザーの「@screen_name」が本文に入っていないとInReplyToが付きません。一昔前までは自分あてリプライでもそうだったと記憶していますが、最近(と言っても2年以上前ですが)、自分あてリプライに限ってその機能が撤廃されました。
それによって、140字で収まらないような長文ツイートをするときに、InReplyToを付けることによってよりわかりやすく一連のツイートを関連付けることができます。

さて、どうやるのかですが、これはJanetterの改造になります。ですが、とてもシンプルです。
Janetter4.3.1.0の場合ですが、

C:\Program Files (x86)\Janetter2\Theme\Common\js\janet\tweetedit.js

の540、541行目が該当するコードになっております。
if(self._replayId>0 && textarea.val().indexOf('@' + self._replayTo)==-1)
    self.removeReplyTo();

InReplyToが付いていて、かつそのscreen_nameがテキストボックスに含まれてなければReplyToを削除するようにできています。

ここに条件を加えてあげましょう。「さらに、そのscreen_nameが自分のアカウント名じゃなかったら」を追加することで想定した動作になります。
if(self._replayId>0 && textarea.val().indexOf('@' + self._replayTo)==-1 && self._replayTo.toLowerCase()!=self._account.screen_name.toLowerCase())
    self.removeReplyTo();

これだけで完成です。自分あてリプライのときに限って、InReplyToが削除されなくなりました。

おそらくこのファイルはJanetterの更新で入れ替えられますから(このファイル内でDM字数制限撤廃機能が実装されていますし)、そのたびに修正する必要が出てくるかと思います。 その場合、もちろん何行目かも変わってくる可能性がありますが、まあその辺は臨機応変に。

2014年7月9日水曜日

突然の死プラグイン

_人人人人人_
> 突然の死 <
 ̄Y^Y^Y^Y^Y ̄

とかもうあまり流行らないですかね。
でも、割と私はまだTwitterで使っています(

はい、でもこれ、案外作るのめんどくさいんですよね。
一応辞書登録はしていても、複数行は登録できませんし、そもそも、中に入れる文字列を変更したら人の数やY^の数も変わってきますからね。一発で囲ってくれたらとても楽ですよね。

Androidには良いアプリがあります。Cerisierというアプリです。

Cerisier - Google Play の Android アプリ

このアプリはなんか中段の紙飛行機マーク(ツイートボタン)の左にある、それっぽいやつを押すと自動的にこうやって人とY^で囲ってくれるのです。なにか強調したいときにとても役に立ちますね。


これを、Janetterプラグインとして実装してしまおうというのが今回のお話です。

国産Twitterクライアント「Janetter」

最も有名なTwitterクライアントの1つで、そのユーザー数も非常に多いです。
しかし、いわゆるTwitterのクライアント締め出し政策の一環で設定されたユーザー数上限に達してしまい、すでに新規ユーザーが登録するのは難しい状態になっています。
また、そのような状態になっているためか、事実上開発が終了したような形になっており、ここ暫くの間一切のアップデートがされておりません。

ですが、私のTwitterライフはJanetter無しには語れないというほど、ずっとJanetterでTwitterをしてきた身です。これを手放すわけにはなかなか行かず、未だに使っております。

はい、そしてこのソフト、どうもChromeとJavaScriptベースでできているみたいなんですね。
詳しくは私もわかっていないのですが、そのため、JavaScriptのプラグインを動かす機能があります。

しかし、プラグイン作製のチュートリアルがあるわけでもなく、ちょっとしたWikiがある程度でChromeやJavaScriptの開発経験が皆無な私にとってはまさに手探り状態。ですが、見よう見まねでなんとか作り上げました。

(function(jn){
    $('.tweet-button')    
        .before(
            $('<a class="button" id="totsuzen_no_shi" style="float:right; width: 70px; height:17px; font-weight: bold; line-height:1.5em; margin: 3px 0 2px 3px;">突然の死</a>')
        );
    $('#totsuzen_no_shi').click(function(){
        var lines = String($('#tweet-edit-container > .expanded textarea').val()).trim2().split('\n');
        var lengths = [];    //各行の文字列の長さ
        for(var i = 0; i < lines.length; i++) {
            var length = 0;
            for(var j = 0; j < lines[i].length; j++) {
                var c = lines[i].charAt(j).charCodeAt(0);
                
                if((c >= 0x0 && c < 0x81) || (c == 0xf8f0) || (c >= 0xff61 && c < 0xffa0) || (c >= 0xf8f1 && c < 0xf8f4))
                    length += 0.5;    //半角だ
                else
                    length += 1;
            }
            lengths.push(length);
        }

        var maxlength = Math.max.apply(null, lengths);    //行の長さの最大値

        var totsuzen = '_';
        for(var i = 0; i < maxlength + 2; i++)
            totsuzen += '人';
        totsuzen += '_\n';

        for(var i = 0; i < lines.length; i++) {
            totsuzen += '> ' + lines[i];
            for(var j = 0; j < maxlength - lengths[i]; j++)
                totsuzen += ' ';
            totsuzen += ' <\n';
        }

        totsuzen += ' ̄Y';
        for(var i = 0; i < ((maxlength + 2) * 1.4 - 1) / 2; i++)
            totsuzen += '^Y';
        totsuzen += ' ̄';

        jn.editor._normalTweetBox(totsuzen, false);

        return false;
    });
})(janet);

totsuzen_no_shiというidのボタンを定義し、そのクリック時のイベントを書く形で作っています。
JavaScriptはほとんど開発の経験が無いので、頭が良いコードを書くより動くコードを書くことを前提に作っています。

まずは、冒頭でツイート欄の内容を取得し、それを改行コードでsplitして行ごとに配列としています。
そして、その各行の文字数を取得していますが、ここで、文字コードを判別して全角文字は1文字、半角文字は0.5文字としています。そもそもJanetterのフォントは等幅フォントではないのでこういった書き方はあまりよろしくないのかもしれませんが、実際に表示される文字幅を取得する方法がわからないので仕方ないです。

続いて、その各行の文字数の最大値に合わせて人やY^を並べていっています。それだけです。

最後は_normalTweetBox関数を呼び出して表示内容を更新しています。
なんていうか、先頭にアンダーバーを付けた関数名なあたり、プライベートにしたい感がめっちゃある関数ですが、仕方ない、これを使うしかないので使っています。
他にshowTweetBoxという関数がありますが、これはすでにツイート欄に文字列があった場合、下書きに保存するか確認するダイアログを出してくるので鬱陶しいです。中の文字列に文字を付け足してるだけなのにわざわざ下書きに保存する奴なんていないわ!というわけで、この関数は使うのをやめました。

はい、そんな感じでとりあえず形になっています。

Janetterでこのプラグインを使う場合は、このコードをコピペして適当なファイル(例えばtotsuzen_no_shi.jsなど)にUTF-8として保存し、C:\Program Files (x86)などのフォルダの中にあるJanetter2\Theme\Common\js\pluginsフォルダに突っ込んでJanetterを再起動したらおkです。


こんな風にツイート欄に突然の死ボタンが現れ、それを押すたびにツイート欄の内容をどんどん人とY^で囲っていってくれます。(クソネミキャッスルボタンはまた別のプラグインです)

ぜひJanetterユーザーはお試しあれ。