カテゴリ: program

接続はできた。
投稿には「1 label has been placed on this content」というのが勝手につけられた。
「ポルノグラフィ」だそうな。マジか。
ともあれ、ラベルを付けるだけなら投稿しても問題無さげ?…かどうかは分からない。BANに備えて3月の、メアドだけでアカウントが作れた時期に10個位作っておくべきだった。

0408
0408b

このエントリーをはてなブックマークに追加 mixiチェック

TiwtterやMastodonと違い、BlueSkyのOGPカードに表示するタイトルや説明、画像はクライアントが準備してPOSTする時に沿えてやらねばならない。
その場合の画像のフォーマットはJPEGは駄目、PNGは大丈夫だった。JPEGデータだと以下のようなエラーが返ってくる。
BadRequestError: Response(success=False, status_code=400, content=XrpcError(error='InvalidMimeType', message='Wrong type of file. It is */* but it must match image/*.'), headers={'Date': 'Sat, 23 Mar 2024 14:55:59 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': '96', 'Connection': 'keep-alive', 'X-Powered-By': 'Express', 'Access-Control-Allow-Origin': '*', 'RateLimit-Limit': '5000', 'RateLimit-Remaining': '4997', 'RateLimit-Reset': '1711209359', 'RateLimit-Policy': '5000;w=3600', 'ETag': 'W/"60-naTgCWHhjZ5FGkdXs552SxW2Nug"', 'Vary': 'Accept-Encoding'})

メモ:Pythonで画像フォーマットを変換するにはPillowを使う模様
https://note.nkmk.me/python-pillow-basic/


このエントリーをはてなブックマークに追加 mixiチェック

1.Nostrには「クライアントが表示する前にユーザにワンクッション置かせる指定」の規格NIP-36があるのでそれを設定する。
2.iPad用のメジャーなNostrクライアント、Damusは上記のNIP-36に対応しておらず、代わりにハッシュタグ「#nsfw」を見ているらしいので、それを付けるのが親切。
3.ハッシュタグ(NIP-12)は本文中にTwitter同様「#hoge」等と記載し、更にtag「t」を使う。tのvalueには「#」は含めない
4.画像を添付したい時は、インターネット上のどこかに画像をアップロードしてその画像のURIを本文中に含める

つまりはこう





このエントリーをはてなブックマークに追加 mixiチェック

先日Nostrへの書き込みに成功したのでボットをNostrに対応させた
リレーはよくわからんのでこの辺からを適当に羅列したものの中から毎回2つをランダムに選択して送信させているんだけど、同一イベントを例えば100カ所に送るとか駄目なのかしら。あるいは各クライアントのデフォルトに全部送るとか?ただしい作法がわからない。
あと、ハッシュタグに未対応なのでこれは対応しないとな。
まぁ…Nostrのノリが分からぬのでまぁ最初は静かにしておくべし。



このエントリーをはてなブックマークに追加 mixiチェック

Java製のクライアントを作ってNostrに投稿したい(既存のJava製のボットにNostr投稿機能を追加したいので調べた。
一応動作したのでNostrのJava製クライアントに関する日本語情報が少ない事だしここに記す。ちなみにエラー対応不足のため書き込みに失敗すると子スレッドが残ってしまう。
■1.nostr-javaのClientクラス
やれ~/.nostr-java配下にプロパティファイルを作れだのハンドラを作れだのmodule-info.javaを作れと喧しい上に実行したら
Exception in thread "main" java.lang.NoClassDefFoundError: org/bouncycastle/jce/provider/BouncyCastleProvider
と言われhttps://www.bouncycastle.org/latest_releases.htmlからjarをダウンロードして入れたり
java.util.concurrent.ExecutionException: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested targetr
と言われ実行時に
-Djavax.net.ssl.trustStore=C:\Program Files\Java\jdk-21.0.1\lib\security\cacerts
を入れたはいいがその後もトラブルが起きて解決できず。

■2.JavaでNostrのリレーに1件だけテキストイベントを投稿するコードを書いてみた
…nostr-javaのサンプルよりは分かりやすいけれど、まずビルドが通らない。多分JettyのWebSocketのライブラリとHTTPClientのライブラリのバージョンの不一致あたりだと思うけどそもそもimport文も省略されているので想像で入れたりしたけれど結局通らず。

■3.Jetty 12.x のWebSocketClientのサンプルコードを育てて自作
2番はビルドできないとは言え大いに参考にしつつ、まずはビルドや実行が通るように慎重に本家Jettyのサンプルから育てていく。
とりあえず最後まで動作して相手リレーサーバから
["OK","fdf83a3115ae12c3331acd6390641a2ab075b85bf21a44d7e10ce4d0379b0dc6",true,""]
なる電文が返って来たしNostterにも表示されたので動作はした模様
0103c

ちなみにWebSocketのクローズの辺りは少々あやしい。
NostrSendMain.java
/**
 * Nostr書き込み処理サンプル。
 */
package nekora.nostr;

import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.websocket.api.Callback;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.client.WebSocketClient;

import nostr.base.PrivateKey;
import nostr.base.PublicKey;
import nostr.crypto.schnorr.Schnorr;
import nostr.util.NostrUtil;

/**
 * 
 */
public class NostrSendMain {
	//final String RELAY_URL = "wss://relay.nostr.wirednet.jp";
	final String RELAY_URL = "wss://relay-jp.nostr.wirednet.jp";
	final String CONTENT = "初カキコ…ども…from My Java Client";

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		NostrSendMain my = new NostrSendMain();
		my.exec(args);
	}

	private void exec(String[] args) {
		// Use a standard, HTTP/1.1, HttpClient.
		HttpClient httpClient = new HttpClient();

		// Create and start WebSocketClient.
		WebSocketClient webSocketClient = new WebSocketClient(httpClient);
		try {
			webSocketClient.start();
			// The client-side WebSocket EndPoint that
			// receives WebSocket messages from the server.
			MyAutoDemandListenerEndPoint myendp = new MyAutoDemandListenerEndPoint();
			// The server URI to connect to.
			URI serverURI = URI.create(RELAY_URL);

			// クライアントエンドポイントをサーバーに接続.
			CompletableFuture clientSessionPromise = webSocketClient.connect(myendp, serverURI);

			// 接続処理完了を待つ
			// Thread.sleep(1500); // これ要らない気がするので削除してみた

			// セッションを取得
			try (Session session = clientSessionPromise.join()) {
				if (!session.isOpen()) {
					System.err.println("接続できませんでした");
					return;
				}
				System.out.println("接続OK…" + session);

				// Nonstr特化通信
				nostrExec(session);
				System.out.println("Nostrに送信完了");

			} catch (Exception e) {
				throw e;
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// クローズ
			try {
				webSocketClient.stop();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		System.out.println("すべて完了");
	}

	/**
	 * Nostrリレーサーバと通信 https://zenn.dev/memory_of_snow/articles/1f0ddd964c5f83 参考
	 * 
	 * @param webSocketClient
	 * @param session
	 */
	private void nostrExec(Session session) {
		// テスト用秘密鍵(=アカウント)をランダムに作成。
		PrivateKey secKey = new PrivateKey(Schnorr.generatePrivateKey());
		//PrivateKey secKey = new PrivateKey("アカウント固定する場合はエンコードされた秘密鍵文字列");

		try {
			// 秘密鍵から公開鍵を作成
			PublicKey publicKey = new PublicKey(Schnorr.genPubKey(secKey.getRawData()));
			String publicKeyHex = publicKey.toString();

			System.out.println("SecKey(HEX) = " + secKey);
			System.out.println("publicKey(HEX) = " + publicKey);

			long created_at = Instant.now().getEpochSecond();
			int kind = 1;
			List> tags = new ArrayList<>();

			// id計算に使う要素を集める
			// [0,"公開鍵(HEX)",投稿時間,kind(プレーンテキスト投稿は1),タグ群,"投稿内容"]
			String partsForId = "[0,\"" + publicKeyHex + "\"," + created_at + "," + kind + "," + tags + ",\"" + CONTENT
					+ "\"]";
			byte[] idSourceBytes = partsForId.getBytes(StandardCharsets.UTF_8);
			String id = NostrUtil.bytesToHex(NostrUtil.sha256(idSourceBytes));
			byte[] signedHashedSerializedEvent = Schnorr.sign(NostrUtil.sha256(idSourceBytes), secKey.getRawData(),
					NostrUtil.createRandomByteArray(32));
			String sig = NostrUtil.bytesToHex(signedHashedSerializedEvent);

			// メッセージは["EVENT",{
			// "id":"id",
			// "pubkey":"公開鍵(Hex)",
			// "created_at":作成時間(UnixTimeStamp),
			// "kind":kind(プレーンテキストは1),
			// "tags":[],
			// "content":"投稿内容",
			// "sig":"署名"
			// }]
			String message = "[\"EVENT\",{\"id\":\"" + id + "\",\"pubkey\":\"" + publicKeyHex + "\","
					+ "\"created_at\":" + created_at + ",\"kind\":" + kind + ",\"tags\":" + tags + ","
					+ "\"content\":\"" + CONTENT + "\",\"sig\":\"" + sig + "\"}]";

			String displayMessage = message.replace(",", ",\n");
			System.out.println("組み立てたメッセージ:\n" + displayMessage);

			// session.getRemote().sendString(message); // こんなメソッドは無いと言われる
			MySendCallBack calbak = new MySendCallBack(message);
			session.sendText(message, calbak);

			// 受信時間稼ぎ
			Thread.sleep(1500); // これ必要なのか分からないがサンプルに従い残す。

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
MyAutoDemandListenerEndPoint.java
package nekora.nostr;

import org.eclipse.jetty.websocket.api.Session;

/**
 * 接続用エンドポイント 以下をコピペ
 * https://eclipse.dev/jetty/documentation/jetty-12/programming-guide/index.html#pg-client-http
 */

public class MyAutoDemandListenerEndPoint implements Session.Listener.AutoDemanding {
	private Session session;

	@Override
	public void onWebSocketOpen(Session session) {
		this.session = session;
		// No need to demand here, because this endpoint is auto-demanding.
	}

	@Override
	public void onWebSocketText(String message) {
		System.out.println("受信メッセージ=" + message);
		// No need to demand here, because this endpoint is auto-demanding.
	}

}
MySendCallBack.java
package nekora.nostr;

import org.eclipse.jetty.websocket.api.Callback;

/**
 * 送信用コールバック
 * https://eclipse.dev/jetty/documentation/jetty-12/programming-guide/index.html#pg-websocket-session-send
 */
public class MySendCallBack implements Callback {
	private String message;

	public MySendCallBack(String message) {
		this.message = message;
	}

	@Override
	public void succeed() {
		System.out.println("テキスト送信成功." + message);
	}

	@Override
	public void fail(Throwable x) {
		// No need to rethrow or close the session.
		System.out.println("テキスト送信失敗." + message + " エラー=" + x.getMessage());
	}

}
ビルド時にIvy.xmlに指定するのは以下のみ(Mavenならpom.xml)。
dependency org="org.eclipse.jetty.websocket" name="jetty-websocket-jetty-client" rev="12.0.5"
あと、nostr-javaも使っているので事前にimportして、参照させる必要がある。
0104

自分の秘密鍵をhexにしたい時は下記を使う
https://nak.nostr.com/



このエントリーをはてなブックマークに追加 mixiチェック

クローリングする先のサイトのHTML変更に合わせて解析箇所も随時追従しないといけないんだけどサボってた。
HTMLは直接解析は中々厳しいので、HTMLCleanerで一旦XMLにして、それを解析するのが楽。
Pythonは数人以上でとりくむような代物だとやってられないけれど、この程度の処理だとJavaよりも楽ですな。全部設定ファイルなので引数を受け取る処理が先ず不要になるし。

このエントリーをはてなブックマークに追加 mixiチェック

2023/10/9時点では「280バイトまで投稿可能で日本語だと2バイトになり140文字」は誤り。
日本語だろうが英語だろうが280文字まで投稿可能なのだ。何も難しく考える事はなかった。
https://twitter.com/nekora2520/status/1711256997017133307
https://twitter.com/nekora2520/status/1711257528997499270


このエントリーをはてなブックマークに追加 mixiチェック

たまにボットのTweetにTwitterのカードが表示されない事がある
F6ebYXXXYAELkoz
こういうの。
以前から、ちらほらあったけれどまぁNWの状態かな?と放置していたが、最近、やたら増えてきたのでやっぱバグっぽいと思い直し調査して修正。
うむ、バグだった。
どうも最近は作品IDが以前より2桁増えたタイプのが増えている。以前の作品IDの8桁固定処理で失敗していたのね。


このエントリーをはてなブックマークに追加 mixiチェック

これまでは我が家のバッチサーバでデータファイル群を更新すると、それをWEBサーバとボットサーバにSCPで転送していたが、我が家の回線が細いので遅い。
ボットサーバは別に最新状態でなくても構わないので、バッチサーバから最新ファイル群を送信するのはWEBサーバのみとし、ボットサーバは夜間に別途WEBサーバからファイル群をダウンロードすることにした。
LinuxでWinSCPのように更新したファイルだけをアップロード/ダウンロードするにはlftpを別途インストールするらしい。rsyncは無理か。
lftp -f コマンドファイル名

コマンドファイルの例
open ftp://ユーザ名:パスワード@ホスト名
mirror --parallel=2 --skip-noaccess /接続先ディレクトリフルパス /ローカルディレクトリフルパス
close
exit

FTPサーバ構築ガイド (A nutshell handbook)
Nye,Adrian
オライリー・ジャパン
1996-10-30


このエントリーをはてなブックマークに追加 mixiチェック

修正が結構大変。
データファイルの拡張子文字列は別途自分で定義すべきだったか…。
まぁ面倒くさいのと見落としが起こるだけで、別に技術的困難は無いので大丈夫だろう。

Python+JSON データ活用の奥義
クジラ飛行机
ソシム
2023-01-06


このエントリーをはてなブックマークに追加 mixiチェック

↑このページのトップヘ