📝

Nostrの投稿を取得して自サイトで時系列順に並べてみる

2023/02/09に公開

更新履歴

2023/2/10 <script>srcをCDNのリンクに変更

筆者について

pubkey: npub1tcrsspumwynmtp80r9aezp9dzwy4m079xkauccvfvx74fgegaxtsylxlgc

完成したデモ(雑)

ihasq.com/nostr-posts-viewer にあります
深夜に意識朦朧としながら書いたのでかなり杜撰です

実装内容

・Hexに変換したユーザIDで複数リレーから投稿を取得
・取得した投稿を時系列順に並び替えて表示

index.html
  <body>
    <div class="container">
      <header>@ihasq's trackable latest nostr posts:<br></header>
    </div>
    <script src="https://unpkg.com/nostr-tools/lib/nostr.bundle.js"></script>
    <!-- nostr-toolsはcdnにもある -->
    <script src="main.js"></script>
  </body>
main.js
const container = document.querySelector(".container");
// 複数リレーから非同期に取得する場合はpool()を使う
const pool = window.NostrTools.pool();
// poolに登録するurl一覧
const relays = [
  "wss://relay.snort.social",
  "wss://relay.nostr.bg",
  "wss://nos.lol",
  "wss://nostr.fmt.wiz.biz",
  "wss://nostr.wine"
];
let postIdList = [];
let postIdPast = [];
let postDatePast = [0];
let postDOM = [];
let clear = 0;
relays.forEach(async url => {
  let postIdListBuffer = [];
  // 複数リレーに繋ぐにはensureRelay()メソッドが必要らしい
  let relay = pool.ensureRelay(url);
  // 以下通常通り
  await relay.connect();
  relay.on('connect', () => {
    console.log(`connected to ${relay.url}`)
  });
  relay.on('error', () => {
    console.log(`failed to connect to ${relay.url}`)
  });
  let sub = relay.sub([
    {
      "authors": ["5e0708079b7127b584ef197b9104ad13895dbfc535bbcc618961bd54a328e997"],
      "kinds": [1]
    }
  ]);
  let postCreatedDate = [0];
  sub.on('event', event => {
    postIdListBuffer.push(event);
  });
  sub.on('eose', () => {
    sub.unsub()
    // EOSEしたリレーが半数を超えるまでとにかく送りまくる
    if(clear <= relays.length / 2)contract(postIdListBuffer);
  });
});
// forEach asyncから受け取ったコンテンツリストをとりあえずconcat()しておく
const contract = buffer => {
  postIdList = postIdList.concat(buffer)
  clear++;
  // リレーの半数以上がEOSEした瞬間に締め切ってソート処理を開始
  if(clear > relays.length / 2){
    console.log(postIdList)
    postIdList.forEach(e => {
      // idで重複していないか確認
      if(!postIdPast.includes(e.id)){
        postIdPast.push(e.id)
        postDOM.push(e)
        postDatePast.push(e.created_at)
      }
    })
    postIdPast = [];
    // あらかじめ入れておいた[0]を削除
    postDatePast.shift();
    console.log(postDatePast)
    // created_atで昇順に並び替え
    postDatePast.sort((a,b)=> a - b)
    let dom, date;
    postDatePast.forEach(e=>{
      dom = postDOM.find(f => f.created_at === e)
      if(!postIdPast.includes(dom.content)){
        postIdPast.push(dom.content)
	date = new Date(dom.created_at * 1000);
        document.querySelector("header").insertAdjacentHTML("afterend",`
          <div class="line">
            <div class="id">${date.toLocaleDateString()} ${date.toLocaleTimeString()} ID: ${dom.id}<br></div>
            ${dom.content}<br>
          </div>
        `)
      }
    })
  }
}

一連の作業を通して得た知識

EOSE:リレーから送られる「もうこれ以上送るものはないお」という便利なイベント

課題点

・キャッシュせず更新毎に取得するので通信量が馬鹿にならない

Discussion