<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <updated>2026-04-06T06:02:12Z</updated>
  <generator>https://nostr.ae</generator>

  <title>Nostr notes by </title>
  <author>
    <name></name>
  </author>
  <link rel="self" type="application/atom+xml" href="https://nostr.ae/npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzqujme.rss" />
  <link href="https://nostr.ae/npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzqujme" />
  <id>https://nostr.ae/npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzqujme</id>
  <icon></icon>
  <logo></logo>


  <title>Nostr notes on relay.shadowbip.com</title>
  <link href="https://nostr.ae/r/relay.shadowbip.com" />
  <link rel="self" type="application/atom+xml" href="https://nostr.ae/r/relay.shadowbip.com.rss" />
  <id>https://nostr.ae/r/relay.shadowbip.com</id>
  <icon></icon>
  <logo></logo>



  <entry>
    <id>https://nostr.ae/nevent1qqsvknafr20nhyurttqf75mx3gdjpdtetkafv4lz928d8hgckag247spr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzphvyfptle77uzf33mnvkal0nmvjemarx2v0eh09zlkn05kjmz0u2qz3nd5</id>
    
      <title type="html">It will work. It&amp;#39;s just being pulled from the app store.</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsvknafr20nhyurttqf75mx3gdjpdtetkafv4lz928d8hgckag247spr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzphvyfptle77uzf33mnvkal0nmvjemarx2v0eh09zlkn05kjmz0u2qz3nd5" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqs240cu2607pqay579vpeja223j27ve2nsr8gk9p40ufqkv92dchugpzamhxue69uhhyetvv9ujuurjd9kkzmpwdejhgtct3qfak&#39;&gt;nevent1q…qfak&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;It will work. It&amp;#39;s just being pulled from the app store.
    </content>
    <updated>2026-04-06T06:02:12Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsdzg9cc2jt8cm72q8d9ffcc7huwupghm8yresmzqgyu07tfaczvvgpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpra3gz6w3h00jl8yhqsay3e83gdyx5ekyc3lvsppfp9nwtu5sqqvt355d7</id>
    
      <title type="html">Recently I had an experience with a service where I was a paid ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsdzg9cc2jt8cm72q8d9ffcc7huwupghm8yresmzqgyu07tfaczvvgpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpra3gz6w3h00jl8yhqsay3e83gdyx5ekyc3lvsppfp9nwtu5sqqvt355d7" />
    <content type="html">
      Recently I had an experience with a service where I was a paid subscriber. The service decided to artificially limit the features that they had for my tier, and where I go used to them and was glad to pay. The basic thing is, they took away what I already had access to, and locked it behind a new tier. In general I have no issues if free features are gradually adjusted to maintain costs (I understand it’s costly), but I have no mercy for rug pulls like this. If you are the owner of the SaaS, please be considerate and don’t just add features without thinking ahead. Want to charge more, add more to the next tier, make me want to upgrade. Pricing is hard, splitting features is hard, not fucking your customers over is easy. 🫡
    </content>
    <updated>2026-04-06T05:58:26Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs9ql4eaf55uefnt2zxuz83ch3rsws928rpugm590564rn2xga9gkgpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzphtxf40yq9jr82xdd8cqtts5szqyx5tcndvaukhsvfmduetr85ce9426x9</id>
    
      <title type="html">Okay, I&amp;#39;ll translate that as outboxes being higher. ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs9ql4eaf55uefnt2zxuz83ch3rsws928rpugm590564rn2xga9gkgpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzphtxf40yq9jr82xdd8cqtts5szqyx5tcndvaukhsvfmduetr85ce9426x9" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqsqqq9q039pmqanpy7qd799znl92c4c56y80a8q8t3pw65z3037ukqpzemhxue69uhkzem8wghxummnw3ezumrpdejz75cd3uq&#39;&gt;nevent1q…d3uq&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Okay, I&amp;#39;ll translate that as outboxes being higher. They&amp;#39;re supposed to be checked by default, so someone who doesn&amp;#39;t care to add more relays doesn&amp;#39;t need to do anything. The misordering was a big. I&amp;#39;ll fix that.&lt;br/&gt;&lt;br/&gt;I hadn&amp;#39;t even thought about including sets in the optional relays, just favorites, but that is an obvious one.
    </content>
    <updated>2026-04-06T05:57:25Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs9hvhw38eka78kg5axyv93fjnjdx6cf04ru7elylmpyegg4d0m7gcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzphtxf40yq9jr82xdd8cqtts5szqyx5tcndvaukhsvfmduetr85cex3gvtx</id>
    
      <title type="html">I was thinking of adding a full pfp upload algo, yeah. I needed ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs9hvhw38eka78kg5axyv93fjnjdx6cf04ru7elylmpyegg4d0m7gcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzphtxf40yq9jr82xdd8cqtts5szqyx5tcndvaukhsvfmduetr85cex3gvtx" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqszgmyts3cwku9mz873ah8j7c3pleyj07mfg8n8qnl6wf2ezr959xcpzemhxue69uhkzem8wghxummnw3ezumrpdejz7d9etx2&#39;&gt;nevent1q…etx2&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;I was thinking of adding a full pfp upload algo, yeah. I needed to implement something for onboarding new users, anyway, so that they don&amp;#39;t start off with the fallback. Both ideas kinda merged in my head. 😂
    </content>
    <updated>2026-04-06T05:51:40Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsx2qvqwjenzr3er6gmgc0sltfl6ze99xgmn2k9l33xse2h7ukcxjcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzp7v96vy3jlyqtct3n3e3sk6hflp7usra0sdkz477axwx4n39nxamqs45kc</id>
    
      <title type="html">you gotta add the nostr: before you nevent for most clients homie</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsx2qvqwjenzr3er6gmgc0sltfl6ze99xgmn2k9l33xse2h7ukcxjcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzp7v96vy3jlyqtct3n3e3sk6hflp7usra0sdkz477axwx4n39nxamqs45kc" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqsy039pxllzmu558flv6xhrlzqucuad823vj34r74n4zzfdaqf6tegpz4mhxue69uhhyetvv9ujuerpd46hxtnfduhsyasqr8&#39;&gt;nevent1q…sqr8&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;you gotta add the nostr: before you nevent for most clients homie
    </content>
    <updated>2026-04-06T05:55:48Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs2gjhk5jul7qjlelj8gp9t08rtldgq0s28qz6ex6sejf7d6eer6gqpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzphtxf40yq9jr82xdd8cqtts5szqyx5tcndvaukhsvfmduetr85cetezysn</id>
    
      <title type="html">My own. Aitherboard and Imwald. They both have gitstuff in the ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs2gjhk5jul7qjlelj8gp9t08rtldgq0s28qz6ex6sejf7d6eer6gqpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzphtxf40yq9jr82xdd8cqtts5szqyx5tcndvaukhsvfmduetr85cetezysn" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqsytla63y0gyqwyjvwcsnajv2fvdh9chsq3v7g69jhuh8v0zay062gpzemhxue69uhkzem8wghxummnw3ezumrpdejz7de8u3d&#39;&gt;nevent1q…8u3d&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;My own. Aitherboard and Imwald. They both have gitstuff in the feeds and notifications. GitRepublic also shows them, on a particular repo.
    </content>
    <updated>2026-04-06T05:53:55Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqszcwnxwsss2p4qyljedc8tlh26dvyq60x2hj5smn494ysfsz9euaspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzp55yzdcjzuwr8cgh6j7sjv9vqkev2xesavczrmudfufr8upvjz3ttkvsut</id>
    
      <title type="html">You dont actually tell everyone, just thought it was funny</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqszcwnxwsss2p4qyljedc8tlh26dvyq60x2hj5smn494ysfsz9euaspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzp55yzdcjzuwr8cgh6j7sjv9vqkev2xesavczrmudfufr8upvjz3ttkvsut" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqsg72t8cn7a5pzrfq40rrsgetvh4ztn2xy23s206emz348aycysz5gpz3mhxue69uhhyetvv9ujuerpd46hxtnfdu9jjhle&#39;&gt;nevent1q…jhle&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;You dont actually tell everyone, just thought it was funny
    </content>
    <updated>2026-04-06T05:55:41Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqstrdk7twmn673cu3sfxh384z8hhvnmwd7g5067y0txzmrvupsncqgpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzp55yzdcjzuwr8cgh6j7sjv9vqkev2xesavczrmudfufr8upvjz3te072zs</id>
    
      <title type="html">GN 👋</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqstrdk7twmn673cu3sfxh384z8hhvnmwd7g5067y0txzmrvupsncqgpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzp55yzdcjzuwr8cgh6j7sjv9vqkev2xesavczrmudfufr8upvjz3te072zs" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqsycwemqpfclrnt5p28rzlek7rh6chd4nwdk7ueqjq2hp6zw0jxnhqpz3mhxue69uhhyetvv9ujuerpd46hxtnfdu5w3nqn&#39;&gt;nevent1q…3nqn&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;GN 👋
    </content>
    <updated>2026-04-06T05:55:55Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsthzvkfguknn4fu3w3fgp4katunkfmnn56unzphhpt3mw838kgxespr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpqv3xzqjgmge9jd9t9ghqsnsw44jygy5gu9nzu093w6783pward4gmsaz8</id>
    
      <title type="html">转 ———————— # ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsthzvkfguknn4fu3w3fgp4katunkfmnn56unzphhpt3mw838kgxespr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpqv3xzqjgmge9jd9t9ghqsnsw44jygy5gu9nzu093w6783pward4gmsaz8" />
    <content type="html">
      转&lt;br/&gt;————————&lt;br/&gt;&lt;br/&gt;# 孙子会如何看待对伊朗开战？&lt;br/&gt;&lt;br/&gt;但凡想正经将孙子兵法应用于现代战争，首先就要摒弃大众对孙子的固有认知。《孙子兵法》如今沦为一堆鸡汤语录，被套用在商业战略、体育竞技与心灵励志书籍中，大多断章取义、不求甚解。但这绝非其能流传两千余年的根本原因。孙子论述的从来不是抽象博弈，而是国与国之间的战争。&lt;br/&gt;&lt;br/&gt;关于孙子其人，历来众说纷纭。传统记载认为，公元前6世纪末（约公元前540年至前496年），名将孙武效力于吴国。彼时战争规模有限，作战主力多为精锐小股部队，而非后世的庞大常备军。但这一说法从未形成共识。学界至今无法确定，《孙子兵法》是否为孙武一人所作，亦或是历代诸多思想家不断编撰、打磨而成。多数学者认为，该书更可能成书于公元前4世纪的战国时期。当时列国林立，秦、楚、燕等诸侯国为求生存、谋求一统，陷入长期大规模厮杀。彼时的战争不再是局部征伐，而是全民动员、职业化军队参战、结局生死攸关的持久决战。因此不少学者推断，历史上或许确有孙武其人，但整部兵法，是历代作者顺应战争形态演变，逐步汇总完善而成的智慧结晶。&lt;br/&gt;&lt;br/&gt;《孙子兵法》开篇便振聋发聩：“兵者，国之大事，死生之地，存亡之道，不可不察也。”孙子所处的时代，战败即意味着亡国灭种；军队持续扩张，战事绵延日久，战争早已不是偶发冲突，而是关乎国运的持久博弈。立足这一背景，他构建的战争理论，至今仍被严重曲解。孙子从不推崇为战而战，也不执着于硬碰硬的决战，核心思想是以最低代价，让军事行动服务于政治目标。他早已点明：“夫兵久而国利者，未之有也。”&lt;br/&gt;&lt;br/&gt;其核心要义常被简化为一句话，实则意蕴深远：“兵者，诡道也。”这绝非单纯指战场诡计，而是一套完整的战争思维。在孙子看来，诡道就是误导敌军认知，迷惑对手对你的战略意图、军事实力与作战短板的判断，逼迫敌方在犹疑不定中做出误判。&lt;br/&gt;&lt;br/&gt;而诡道，必然依托情报洞察。孙子所言“知己知彼，百战不殆”，绝非单纯比拼武器装备，而是看透敌方整套体系——领导层格局、军队凝聚力、资源储备与应变能力。在此层面，情报不只是摸清底细的工具，更是施加影响的筹码。&lt;br/&gt;&lt;br/&gt;分析任何一场战争前，孙子必先追问一个至今仍争议不休的核心问题：政治目标究竟是什么？他始终将战争与政治紧密绑定，提出“夫未战而庙算胜者，得算多也”，这无关战术排布，重在目标对齐。须知战略从来不止是动用武力，更是搭建手段与政治目的的桥梁。&lt;br/&gt;&lt;br/&gt;套用在美以对伊朗的军事行动上，这个问题依旧是关键：终极目标是什么？是颠覆政权，还是逼迫其改弦更张？是摧毁核计划，还是迫使其接受约束？是战略威慑，还是武力逼降？这些目标绝非一回事，对应的投入规模、作战周期与风险承受底线，全然不同。&lt;br/&gt;&lt;br/&gt;美国总统近期公开表态，明确一系列核心诉求：摧毁伊朗导弹库存与生产能力、瓦解其袭扰国际航运的海上战力、杜绝其研发核武器的可能、切断其扶持指挥代理人武装的渠道。这些目标，精准针对伊朗的军事实力与挑衅行径。&lt;br/&gt;&lt;br/&gt;与此同时，美方部分言论又乐见伊朗现政权倒台、呼吁“伊朗人民争取自由”，还声称铲除伊朗高层便能催生“新政权”、倒逼其改变行事风格。这类表态，并未新增作战目标，却埋下模糊空间——乐见政权更迭，不等于将颠覆政权定为核心政治目标。&lt;br/&gt;&lt;br/&gt;但这份模糊，本身就是一种施压手段。它让伊朗现政权不仅要考量持久战的代价，更要直面自身存亡危机。此举大幅加剧伊朗决策层的焦虑，威胁早已超出军力损耗，直指政权存续根基。&lt;br/&gt;&lt;br/&gt;孙子绝不会全盘否定这种操作，只会审视其实际效用。“兵者，诡道也”，刻意制造战略模糊，便能误导敌方判断、打乱其盘算、强化威慑力度，同时不突破既定政治目标。这种战略性模糊，本身就是一种施压谋略。美方一边释放有限打击的信号，一边暗示存亡危机，倒逼伊朗决策层权衡多重风险，将政权存续纳入核心考量，让对手忌惮的不再只是战事损耗，更是政权覆灭的终极后果。&lt;br/&gt;&lt;br/&gt;但孙子也会划出一条清晰红线：迷惑敌军是上策，自身战略含糊是大忌。倘若政界高层、军方统帅、盟友阵营对作战目标无法达成共识，谋略便会沦为混乱。战略层面的含糊不清，终将导致行动一盘散沙。&lt;br/&gt;&lt;br/&gt;孙子还定下一套经典作战优先级，至今仍是解读战争的绝佳框架：“上兵伐谋，其次伐交，其次伐兵，其下攻城。”万般皆无效，才会走到最后一步——“攻城之法，为不得已”。这套逻辑，完全适用于伊朗局势。&lt;br/&gt;&lt;br/&gt;第一，伐谋。伊朗的对抗逻辑，从来不靠常规战场决胜，而是依托持久战、分散部署与非对称战力，依靠代理人武装、导弹部队、经济施压，靠日积月累消耗对手。仅靠摧毁武器装备，根本打不垮这套体系。孙子早有箴言：“百战百胜，非善之善者也。”终极目的不是毁灭，而是瓦解敌方整套作战逻辑。因此，只打击军力远远不够，还要精准重创其指挥网络、财源命脉、内部管控体系与战力再生能力。&lt;br/&gt;&lt;br/&gt;这正是孙子推崇的迂回制胜之道：提前布局造势，让决战尚未打响，胜负便已注定；必要时依旧果断出手，借战力扩大优势、抬高对手代价。核心是避实击虚，靠机动牵制、体系瓦解与心理施压击溃敌方。放到现代，这套思路早已延伸至信息战、网络战，以及低烈度非常规博弈，全方位削弱对手的战力与抗争意志。&lt;br/&gt;&lt;br/&gt;当前针对伊朗的诸多行动，已然贴合这套思路，只是局势仍存极大不确定性。美以精准打击伊朗核心军事节点，但伊朗剩余战力、内部凝聚力、高层决策动向，依旧迷雾重重。不过诸多线索（虽未实锤、尚存争议）显示，施压已初见成效：伊朗大规模齐射导弹与无人机的频次明显下降，多方消息称其协同作战紊乱、高层内部分歧加剧、联合作战难以推进；伊朗亦悄然向美方释放缓和信号，同时传出人员叛逃传闻，当局更是强化管控，在核心城区增设民兵检查站维稳。单一线索不足为凭，但串联来看，伊朗政权承受的压力，早已蔓延至军事之外，深入内部稳定与决策核心。&lt;br/&gt;&lt;br/&gt;第二，伐交。伊朗的影响力，深度绑定真主党、伊拉克与叙利亚民兵、胡塞武装等全套代理人网络。这些势力绝非边缘角色，而是伊朗非对称作战的核心支柱。孙子将伐交置于伐兵之前，自有深意：瓦解盟友体系，无需决战便能削弱敌方根基，彻底孤立对手。如今美以行动已然践行此道：以色列持续清剿真主党，定点清除其高层、摧毁基础设施、瓦解协同作战能力；也门胡塞武装袭扰航运、反击以色列的举动也警示世人——即便代理人战力受损，依旧能持续制造麻烦，彻底斩断伊朗外围势力，才有望击溃其整体战略。而维系盟友间的步调一致，更是重中之重。&lt;br/&gt;&lt;br/&gt;第三，伐兵。精准打击伊朗导弹阵地、海上军力、防空系统与军工设施，贴合既定作战目标，既能短期削弱其域外投送战力，也能形成长期震慑。但单纯军事打击无法一锤定音：战术胜利，不等于战略决胜。不仅要销毁现有战力，更要改写敌方决策逻辑，断绝其重建军力、重启挑衅的可能。精准铲除伊朗军政核心人物（现代战法，远超孙子所处时代的认知），恰好直击要害：既削弱其用兵能力，也瓦解其抗争意志。由此可见，军事行动必须融入整套体系打击战略，恪守孙子“伐谋为先，不唯伐兵”的核心。&lt;br/&gt;&lt;br/&gt;最后，攻城。“攻城之法，为不得已。”孙子将攻城列为下策，只因耗时耗力、代价惨重。古代围城战拖沓奢靡、变数极大，会彻底丧失机动主动权，极易陷入持久战——而这正是孙子极力规避的局面。这套逻辑至今成立：现代城市战同样繁复烧钱、政治代价巨大，会消耗大量战力、拉长战事周期。对追求高效达成政治目标的战略家而言，攻城从来不是首选。即便迫不得已出兵（比如为颠覆政权速占首都），也必须速战速决、精准施策。&lt;br/&gt;&lt;br/&gt;在孙子眼中，时间永远是关键。一场短促凌厉、足以促成政治和谈的战事，才是最优解：敲定刚性约束，杜绝伊朗重启核计划、接受全面核查、限制导弹研发、禁止袭扰国际航运、削减扶持代理人武装。&lt;br/&gt;&lt;br/&gt;达成上述目标，作战初衷便已实现；若未能如愿，唯有升级施压。孙子深知，摧毁伊朗的作战手段，从来不是终点，而是瓦解其开战能力、动摇其抗争决心的筹码。顺着这套思路，终极施压必然直击伊朗命脉——关乎其存续的核心利益：石油收入、维系军力与维稳大局的关键基建。譬如封锁哈尔克岛石油出口要道、斩断其借霍尔木兹海峡拿捏全球航运的经济施压筹码；瓦解其维系内部团结的通讯与信息网络。这类施压，绝非为毁灭而毁灭，而是通过抬高战事成本，倒逼伊朗政权妥协，动摇其统治根基，无力再对外挑衅。&lt;br/&gt;&lt;br/&gt;纵观全程，孙子评判胜负的标准始终简单直白：不在于摧毁多少装备，而在于达成多少目标。若敌方决策彻底转向，战略便大功告成；若无改观，再亮眼的战术战果，终究徒劳无功。&lt;br/&gt;&lt;br/&gt;这便是《孙子兵法》流传千古的底气：它从不是一本战场杀敌手册，而是一套博弈思维框架——战争是意志的较量，受政治目标牵引、被代价底线约束，最终决胜于决策博弈，而非武力屠戮。&lt;br/&gt;&lt;br/&gt;孙子亦深谙武力背后的政治局限，懂战场之外舆论人心的分量。他再三警示“夫兵久而国利者，未之有也”，强调军事行动必须紧扣国家核心利益，绝不能肆意扩大作战目标。他极度看重情报预判，主张提前洞悉局势、掌控走向——这套理念，如今早已延伸至信息舆论领域，左右敌我高层与民众的认知。&lt;br/&gt;&lt;br/&gt;他也明白，威慑从来不是口头狠话，而是精准拿捏人心、适度施压。“不战而屈人之兵，善之善者也”，核心是让敌方看清持久战的惨痛后果。但他亦告诫切莫赶尽杀绝：“围师必阙，穷寇勿迫。”施压的初衷，不是断绝所有退路，而是引导对手做出理性选择。放到当下，便是适度动武倒逼伊朗妥协，同时预留契合既定目标的政治和解通道。&lt;br/&gt;&lt;br/&gt;这点至关重要：即便战火导致伊朗政权自然崩塌，也不等同于早早将颠覆政权定为作战目标。倘若美方目标从“逼其改弦更张”悄然转为“大举地面出兵颠覆政权”，便会重蹈过往战争覆辙——有限目标无限扩张，深陷战后重建、长期反恐泥潭，对手凭借灵活分散、隐蔽周旋持续反击。这般局面，只会利于防守方、拉长战事、瓦解内部共识。&lt;br/&gt;&lt;br/&gt;孙子的警示掷地有声：战略必须锚定政治初心，目标务必恪守底线，否则前期战果，终将在持久战中消磨殆尽。&lt;br/&gt;&lt;br/&gt;附记：若想深入研读原著，塞缪尔·B·格里菲思译本的《孙子兵法》，仍是当下最权威、流传最广的经典版本。&lt;br/&gt;&lt;br/&gt;---&lt;br/&gt;作者简介：&lt;br/&gt;约翰·斯宾塞，城市作战研究所执行董事。&lt;br/&gt;著有《九死一生：绝境求生的人生启示》，合著《读懂城市战争》。&lt;br/&gt;更多资讯可查阅官网：&lt;br/&gt;www.johnspenceronline.com
    </content>
    <updated>2026-04-06T05:50:12Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsg72t8cn7a5pzrfq40rrsgetvh4ztn2xy23s206emz348aycysz5gpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzp077yffgxvqumamxcjt8wthpu0vxfnn29vnz4xju3733g3k8hrwzlatl2t</id>
    
      <title type="html">Sometimes it syncs 😅</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsg72t8cn7a5pzrfq40rrsgetvh4ztn2xy23s206emz348aycysz5gpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzp077yffgxvqumamxcjt8wthpu0vxfnn29vnz4xju3733g3k8hrwzlatl2t" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqs2twujh2dyegrwqv4zzuxq7d5vf3duyee3dx60tw5s9efdyzu9cjcpz4mhxue69uhhyetvv9ujuerpd46hxtnfduhsag4wvp&#39;&gt;nevent1q…4wvp&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Sometimes it syncs 😅
    </content>
    <updated>2026-04-06T05:50:00Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsgxrty8ftag5ycxeeqnn7sw8sr6n58sxnn593f8fkqcn99n08hkjqpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzphtxf40yq9jr82xdd8cqtts5szqyx5tcndvaukhsvfmduetr85cejhc43x</id>
    
      <title type="html">What would be the best file format? PNG?</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsgxrty8ftag5ycxeeqnn7sw8sr6n58sxnn593f8fkqcn99n08hkjqpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzphtxf40yq9jr82xdd8cqtts5szqyx5tcndvaukhsvfmduetr85cejhc43x" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqs2hyf3z9ten5acjqpaj0nfndre2gd6fzzla3f3qgkzyxuvksk4e8qpzemhxue69uhkzem8wghxummnw3ezumrpdejz7eldgjp&#39;&gt;nevent1q…dgjp&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;What would be the best file format? PNG?
    </content>
    <updated>2026-04-06T05:47:50Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsv5g48yq52m2mzr0qklufrcnxe4lynykxw4eq8pwwvtfepf9wytlqpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqkj540wgff0684cp4js7l73ke5djns8rhq4crm2djwqdjg7934gs5ngx3c</id>
    
      <title>Nostr event nevent1qqsv5g48yq52m2mzr0qklufrcnxe4lynykxw4eq8pwwvtfepf9wytlqpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqkj540wgff0684cp4js7l73ke5djns8rhq4crm2djwqdjg7934gs5ngx3c</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsv5g48yq52m2mzr0qklufrcnxe4lynykxw4eq8pwwvtfepf9wytlqpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqkj540wgff0684cp4js7l73ke5djns8rhq4crm2djwqdjg7934gs5ngx3c" />
    <content type="html">
       &lt;img src=&#34;https://blossom.yakihonne.com/e6c0118e5123669cc376c995f4eed651ceb00b22a7dbfded29133f9347557a6f.jpeg&#34;&gt; 
    </content>
    <updated>2026-04-06T05:27:24Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs8upqvtv424enx9vh2sjq0jxw3k04w49lnfmy5l62llga7m8maqrspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqkj540wgff0684cp4js7l73ke5djns8rhq4crm2djwqdjg7934gsq53qru</id>
    
      <title>Nostr event nevent1qqs8upqvtv424enx9vh2sjq0jxw3k04w49lnfmy5l62llga7m8maqrspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqkj540wgff0684cp4js7l73ke5djns8rhq4crm2djwqdjg7934gsq53qru</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs8upqvtv424enx9vh2sjq0jxw3k04w49lnfmy5l62llga7m8maqrspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqkj540wgff0684cp4js7l73ke5djns8rhq4crm2djwqdjg7934gsq53qru" />
    <content type="html">
       &lt;img src=&#34;https://cdn.azzamo.media/27201b3a8213077ac8ba34aa235f64270a28b800d5487d84e61e594e9f5173f2.jpg&#34;&gt; 
    </content>
    <updated>2026-04-06T05:23:03Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsxf7cau2dut3udr9txrs7t4dcnyrmd5e3jyucdpmjzcer80j0emdgpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpeal3kknvzpg7q5fk76tagdph55wkm2dv53059le2lsdlwpeau7mu9usg7</id>
    
      <title type="html">Block 943873 3 - high priority 1 - medium priority 1 - low ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsxf7cau2dut3udr9txrs7t4dcnyrmd5e3jyucdpmjzcer80j0emdgpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpeal3kknvzpg7q5fk76tagdph55wkm2dv53059le2lsdlwpeau7mu9usg7" />
    <content type="html">
      Block 943873&lt;br/&gt;&lt;br/&gt;3 - high priority&lt;br/&gt;1 - medium priority&lt;br/&gt;1 - low priority&lt;br/&gt;1 - no priority&lt;br/&gt;1 - purging&lt;br/&gt;&lt;br/&gt;#bitcoinfees #mempool
    </content>
    <updated>2026-04-06T05:20:51Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsxllh2r4fqtqqz0xp9hupq5y5tms9z6rvkayf3djfyzz732yxqkdcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpupa702pxs3sggxd77ktkd0edgj5ysjzg64s22azf3h7c6jdfank640z04</id>
    
      <title type="html">Block 943873 3 - high priority 1 - medium priority 1 - low ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsxllh2r4fqtqqz0xp9hupq5y5tms9z6rvkayf3djfyzz732yxqkdcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpupa702pxs3sggxd77ktkd0edgj5ysjzg64s22azf3h7c6jdfank640z04" />
    <content type="html">
      Block 943873&lt;br/&gt;&lt;br/&gt;3 - high priority&lt;br/&gt;1 - medium priority&lt;br/&gt;1 - low priority&lt;br/&gt;1 - no priority&lt;br/&gt;1 - purging&lt;br/&gt;&lt;br/&gt;#bitcoinfees #mempool
    </content>
    <updated>2026-04-06T05:20:51Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsd4yv8p87nur5zstr4yugyv62djhywh09cj220edz7njjvfp5qtrcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzp3av40cla5sp55cctexutcxxht3tchd3n4e6h7zq2d0nqhv0q5vqgxvre6</id>
    
      <title type="html">hmm.. in China when people fight each other and they have their ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsd4yv8p87nur5zstr4yugyv62djhywh09cj220edz7njjvfp5qtrcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzp3av40cla5sp55cctexutcxxht3tchd3n4e6h7zq2d0nqhv0q5vqgxvre6" />
    <content type="html">
      hmm.. in China when people fight each other and they have their kids with them, they seem to fight each other then attack the kids as well&lt;br/&gt;&lt;br/&gt;in most countries the kids are usually off limits, but in the US i do see young black kids join the fight to help their parents 😂
    </content>
    <updated>2026-04-06T05:19:28Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqswv6z4tdxqvvn9wa2twpyxkncnpwjat8s4m6umgdrumr3t3thaw9cpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqhzs3s60tzrxa3e5r2h3pnq675hfyv4mn7zeeqgref0v724f80mcpc6vh4</id>
    
      <title type="html">just use it in your linkedin, and keep it hush-hush</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqswv6z4tdxqvvn9wa2twpyxkncnpwjat8s4m6umgdrumr3t3thaw9cpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqhzs3s60tzrxa3e5r2h3pnq675hfyv4mn7zeeqgref0v724f80mcpc6vh4" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqsv4px82v7lrw7pjrpdths4wmxy5vxg8gl38neq2nygu3hkzmw2z8cpz3mhxue69uhhyetvv9ujuerpd46hxtnfdux6f8e8&#39;&gt;nevent1q…f8e8&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;just use it in your linkedin, and keep it hush-hush
    </content>
    <updated>2026-04-06T05:18:36Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsqqqqqeqes8tyn30802kddvfryngcr73yeth26epu8gk792m8keyspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqrz96l29akc04hdyy9wndjsdn2aqcacmshfhzamyhzsj340yg0jdpwuapz</id>
    
      <title type="html">When your Palestine supporting Bitch Wifoid (Wife Foid) won&amp;#39;t ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsqqqqqeqes8tyn30802kddvfryngcr73yeth26epu8gk792m8keyspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqrz96l29akc04hdyy9wndjsdn2aqcacmshfhzamyhzsj340yg0jdpwuapz" />
    <content type="html">
      When your Palestine supporting Bitch Wifoid (Wife Foid) won&amp;#39;t let you be healthy by avoiding Goyslop, and eating Kosher because eating Kosher funds The Jews 😔✊️.&lt;br/&gt;&lt;video controls width=&#34;100%&#34; class=&#34;max-h-[90vh] bg-neutral-300 dark:bg-zinc-700&#34;&gt;&lt;source src=&#34;https://npub1p3za04z7mv86mkjzzhfkegxe4wsvwudct5m3wajt3gfg6hjy8exslltqmk.blossom.band/ea117af3de4a9eb80cdd85d9d8c4dbe901d28b3d96e69e36c44e5b2355abc69a.mp4&#34;&gt;&lt;/video&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;video controls width=&#34;100%&#34; class=&#34;max-h-[90vh] bg-neutral-300 dark:bg-zinc-700&#34;&gt;&lt;source src=&#34;https://npub1p3za04z7mv86mkjzzhfkegxe4wsvwudct5m3wajt3gfg6hjy8exslltqmk.blossom.band/f4fe3a6fc425fe33b4750fdbfabb5575fe53b2be71a6a9cec5ab9c2d59451469.mp4&#34;&gt;&lt;/video&gt;
    </content>
    <updated>2026-04-06T05:03:15Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsrkd6za8m6jf50q0rhp8v2rsz3rqyufl6al3mgdn83xtemmspkz4spr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpra3gz6w3h00jl8yhqsay3e83gdyx5ekyc3lvsppfp9nwtu5sqqvjxknzx</id>
    
      <title type="html">https://v.nostr.build/z66qvnrbryDmMRtB.mov</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsrkd6za8m6jf50q0rhp8v2rsz3rqyufl6al3mgdn83xtemmspkz4spr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpra3gz6w3h00jl8yhqsay3e83gdyx5ekyc3lvsppfp9nwtu5sqqvjxknzx" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqsgyk3qp9u7dr4ewjqlx4whpcz8sdp6lrdjaa08sr8m5cdammd5aeqpz3mhxue69uhhyetvv9ujuerpd46hxtnfduvvu586&#39;&gt;nevent1q…u586&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;&lt;video controls width=&#34;100%&#34; class=&#34;max-h-[90vh] bg-neutral-300 dark:bg-zinc-700&#34;&gt;&lt;source src=&#34;https://v.nostr.build/z66qvnrbryDmMRtB.mov&#34;&gt;&lt;/video&gt;
    </content>
    <updated>2026-04-06T05:01:27Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqspt46z7ktm266rkk25fvqhk0u2k64nx5kky4u05nmhgc52607vatgpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq8n8mcm4g9csw8fulx6ykmj5d0v5l59zeglmfkamrvz5dpwfz9hyqtkxeu</id>
    
      <title type="html">Ce codec vidéo coûte un pognon de dingue aux plateformes de ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqspt46z7ktm266rkk25fvqhk0u2k64nx5kky4u05nmhgc52607vatgpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq8n8mcm4g9csw8fulx6ykmj5d0v5l59zeglmfkamrvz5dpwfz9hyqtkxeu" />
    <content type="html">
      Ce codec vidéo coûte un pognon de dingue aux plateformes de streaming, le français VideoLAN porte la riposte libre &lt;br/&gt;&lt;a href=&#34;https://www.clubic.com/actualite-607765-ce-codec-video-coute-un-pognon-de-dingue-aux-plateformes-de-streaming-le-francais-videolan-porte-la-riposte-libre.html&#34;&gt;https://www.clubic.com/actualite-607765-ce-codec-video-coute-un-pognon-de-dingue-aux-plateformes-de-streaming-le-francais-videolan-porte-la-riposte-libre.html&lt;/a&gt;&lt;br/&gt;#nostrfr
    </content>
    <updated>2026-04-06T05:01:00Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsyghamakgpc4g3wpf2djlf8u3wu7q5s26pcwuz8y746g9qezvp5fspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpupa702pxs3sggxd77ktkd0edgj5ysjzg64s22azf3h7c6jdfank62gwn0</id>
    
      <title type="html">Block 943873 1 - high priority 1 - medium priority 1 - low ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsyghamakgpc4g3wpf2djlf8u3wu7q5s26pcwuz8y746g9qezvp5fspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpupa702pxs3sggxd77ktkd0edgj5ysjzg64s22azf3h7c6jdfank62gwn0" />
    <content type="html">
      Block 943873&lt;br/&gt;&lt;br/&gt;1 - high priority&lt;br/&gt;1 - medium priority&lt;br/&gt;1 - low priority&lt;br/&gt;1 - no priority&lt;br/&gt;1 - purging&lt;br/&gt;&lt;br/&gt;#bitcoinfees #mempool
    </content>
    <updated>2026-04-06T05:00:57Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsrnl6d453rt83kzwcflmzeqdpsz2tmru4eh4tjfary4jt08tlg0mcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqxhqz89nfxv679tq9g42jfln9wuj6e0kzc0dwx4a4f84psf90f2ksughax</id>
    
      <title type="html">Block 943873 1 - high priority 1 - medium priority 1 - low ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsrnl6d453rt83kzwcflmzeqdpsz2tmru4eh4tjfary4jt08tlg0mcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqxhqz89nfxv679tq9g42jfln9wuj6e0kzc0dwx4a4f84psf90f2ksughax" />
    <content type="html">
      Block 943873&lt;br/&gt;&lt;br/&gt;1 - high priority&lt;br/&gt;1 - medium priority&lt;br/&gt;1 - low priority&lt;br/&gt;1 - no priority&lt;br/&gt;1 - purging&lt;br/&gt;&lt;br/&gt;#bitcoinfees #mempool
    </content>
    <updated>2026-04-06T05:00:57Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsva8vd2whkxcx667wve0u0n3ylmrskc5zlf7fxf265y4cp4n6n2ngpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpjatwp62qwlcn37azc37gtn9gz0rjxrx9tmvvhlzuwxf9lulp0tpkh23vw</id>
    
      <title type="html">Block 943873 1 - high priority 1 - medium priority 1 - low ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsva8vd2whkxcx667wve0u0n3ylmrskc5zlf7fxf265y4cp4n6n2ngpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpjatwp62qwlcn37azc37gtn9gz0rjxrx9tmvvhlzuwxf9lulp0tpkh23vw" />
    <content type="html">
      Block 943873&lt;br/&gt;&lt;br/&gt;1 - high priority&lt;br/&gt;1 - medium priority&lt;br/&gt;1 - low priority&lt;br/&gt;1 - no priority&lt;br/&gt;1 - purging&lt;br/&gt;&lt;br/&gt;#bitcoinfees #mempool
    </content>
    <updated>2026-04-06T05:00:57Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsgyk3qp9u7dr4ewjqlx4whpcz8sdp6lrdjaa08sr8m5cdammd5aeqpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqqwsh0u4xlh3l5xalq2lg8qcjeec7636pasq75w8s2ma3zg3xr2vw688gm</id>
    
      <title type="html">You are famous bro 😎</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsgyk3qp9u7dr4ewjqlx4whpcz8sdp6lrdjaa08sr8m5cdammd5aeqpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqqwsh0u4xlh3l5xalq2lg8qcjeec7636pasq75w8s2ma3zg3xr2vw688gm" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqsv4px82v7lrw7pjrpdths4wmxy5vxg8gl38neq2nygu3hkzmw2z8ctxnpcv&#39;&gt;nevent1q…npcv&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;You are famous bro 😎 
    </content>
    <updated>2026-04-06T04:55:39Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsxhq0jj55unj807hnp5ck7xl4spafngtk2cclgvmcyfc2prckgd0gpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqqwsh0u4xlh3l5xalq2lg8qcjeec7636pasq75w8s2ma3zg3xr2vc7mnqw</id>
    
      <title type="html">Yessss bro, this is what I am looking for! 🙌 I am not really ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsxhq0jj55unj807hnp5ck7xl4spafngtk2cclgvmcyfc2prckgd0gpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqqwsh0u4xlh3l5xalq2lg8qcjeec7636pasq75w8s2ma3zg3xr2vc7mnqw" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqs8n0az2ssf34v2q99xpmnhzdlsyzem3jyq2gzkcxvx48dk6yw9q2gp35wjy&#39;&gt;nevent1q…5wjy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Yessss bro, this is what I am looking for! 🙌&lt;br/&gt;&lt;br/&gt;I am not really crazy to replace my current phono preamp, but this looks really good. I only wish it has a headphone output, that’s the only thing missing. &lt;br/&gt;&lt;br/&gt;Thank you! 🙏 
    </content>
    <updated>2026-04-06T04:54:23Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsfh4x7lanrrnp4d7zfhn6ek6q3xdyh7r2c9jfq447k0rvmx37n37qpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpra3gz6w3h00jl8yhqsay3e83gdyx5ekyc3lvsppfp9nwtu5sqqv9fa3fc</id>
    
      <title type="html">I also can type appropriately 😭</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsfh4x7lanrrnp4d7zfhn6ek6q3xdyh7r2c9jfq447k0rvmx37n37qpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpra3gz6w3h00jl8yhqsay3e83gdyx5ekyc3lvsppfp9nwtu5sqqv9fa3fc" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqs8dw6ev4qprde6mz30vhjswaakctv2tm2hwkspj08r63zdk75d7agpzdmhxue69uhhwmm59ehx7um5wghxuet57ezfuk&#39;&gt;nevent1q…zfuk&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;I also can type appropriately 😭
    </content>
    <updated>2026-04-06T04:53:38Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs8dw6ev4qprde6mz30vhjswaakctv2tm2hwkspj08r63zdk75d7agpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzph4t08d058ptuj62d5av5y6hkm92pd6yhar26556ttjxg2y908ngnzl9wf</id>
    
      <title type="html">😂 https://media.tenor.com/nimOIXx-bAgAAAAC/robot-robots.gif</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs8dw6ev4qprde6mz30vhjswaakctv2tm2hwkspj08r63zdk75d7agpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzph4t08d058ptuj62d5av5y6hkm92pd6yhar26556ttjxg2y908ngnzl9wf" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqsv4px82v7lrw7pjrpdths4wmxy5vxg8gl38neq2nygu3hkzmw2z8cpzfmhxue69uhkummnw3eryvfwvdhk6tczrvxev&#39;&gt;nevent1q…vxev&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;😂&lt;br/&gt; &lt;img src=&#34;https://media.tenor.com/nimOIXx-bAgAAAAC/robot-robots.gif&#34;&gt; 
    </content>
    <updated>2026-04-06T04:52:48Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsv4px82v7lrw7pjrpdths4wmxy5vxg8gl38neq2nygu3hkzmw2z8cpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpra3gz6w3h00jl8yhqsay3e83gdyx5ekyc3lvsppfp9nwtu5sqqvkgx9r7</id>
    
      <title type="html">How do I have 50K followers when we don’t not have like 100 ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsv4px82v7lrw7pjrpdths4wmxy5vxg8gl38neq2nygu3hkzmw2z8cpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpra3gz6w3h00jl8yhqsay3e83gdyx5ekyc3lvsppfp9nwtu5sqqvkgx9r7" />
    <content type="html">
      How do I have 50K followers when we don’t not have like 100 people on nostr with 100 alts? 🤷‍♂️&lt;br/&gt;&lt;br/&gt; &lt;img src=&#34;https://i.nostr.build/82jjUZYHfXYDj8zQ.jpg&#34;&gt; 
    </content>
    <updated>2026-04-06T04:49:12Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs97tkdmckqj489zwxp36rsxajr67tpvw0y0ucs28wpkx6cng2z5vqpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpra3gz6w3h00jl8yhqsay3e83gdyx5ekyc3lvsppfp9nwtu5sqqv8m3ndd</id>
    
      <title type="html">Yes</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs97tkdmckqj489zwxp36rsxajr67tpvw0y0ucs28wpkx6cng2z5vqpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpra3gz6w3h00jl8yhqsay3e83gdyx5ekyc3lvsppfp9nwtu5sqqv8m3ndd" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqsxgxm5kezry48r3n5k6uqtknn39vde5y7jsamgrjtjs7xn6cpsmksppamhxue69uhkummnw3ezumt0d5pw8m33&#39;&gt;nevent1q…8m33&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Yes
    </content>
    <updated>2026-04-06T04:44:13Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsqnf557kumcc2fzetw0hcdcasulj9m4vup2xtjs4rzr9q38j9z50qpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpra3gz6w3h00jl8yhqsay3e83gdyx5ekyc3lvsppfp9nwtu5sqqvatu5uv</id>
    
      <title type="html">URL(remove spaces): https:// pfp.nostr.build ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsqnf557kumcc2fzetw0hcdcasulj9m4vup2xtjs4rzr9q38j9z50qpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzpra3gz6w3h00jl8yhqsay3e83gdyx5ekyc3lvsppfp9nwtu5sqqvatu5uv" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqsqyenke4kuajp0lcplxfnqjpjr6ed277qnnhf82mned6v5kyeq62gpz4mhxue69uhhyetvv9ujumt0wd68ytnsw43q494803&#39;&gt;nevent1q…4803&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;URL(remove spaces): https:// pfp.nostr.build /1e32e7ded1df45aa5d63919eaf0eb3c5590906e70c28180809ca921d9ea6a2a6.gif
    </content>
    <updated>2026-04-06T04:40:26Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsrrhjeg54njva6t6awehg4usnynhu9qmpp3aqgj7dhrvlvfvetpuspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqlwpvac39uz74ay4g7qx2sa3cqrvuvjhy7899vwf40lkxfcw6uz0tza2y7</id>
    
      <title>Nostr event nevent1qqsrrhjeg54njva6t6awehg4usnynhu9qmpp3aqgj7dhrvlvfvetpuspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqlwpvac39uz74ay4g7qx2sa3cqrvuvjhy7899vwf40lkxfcw6uz0tza2y7</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsrrhjeg54njva6t6awehg4usnynhu9qmpp3aqgj7dhrvlvfvetpuspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqlwpvac39uz74ay4g7qx2sa3cqrvuvjhy7899vwf40lkxfcw6uz0tza2y7" />
    <content type="html">
      やっぱダメっすよ　俺はもう
    </content>
    <updated>2026-04-06T04:39:02Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsvfqkcey8a3ngd437ftkndzwhkusqk4g8v30faua96d2zp8qmpm5qpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzph4t08d058ptuj62d5av5y6hkm92pd6yhar26556ttjxg2y908nghluqvm</id>
    
      <title type="html">This is how I Nostr ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsvfqkcey8a3ngd437ftkndzwhkusqk4g8v30faua96d2zp8qmpm5qpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzph4t08d058ptuj62d5av5y6hkm92pd6yhar26556ttjxg2y908nghluqvm" />
    <content type="html">
      This is how I Nostr&lt;br/&gt;&lt;video controls width=&#34;100%&#34; class=&#34;max-h-[90vh] bg-neutral-300 dark:bg-zinc-700&#34;&gt;&lt;source src=&#34;https://haven.downisontheup.ca/f23da71bf716585154232c720681bf1e197abc9b91b27b1622e85e30a4f14d95.mp4&#34;&gt;&lt;/video&gt;
    </content>
    <updated>2026-04-06T04:38:12Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsr33qape58thqjparzlz757my4q0fawmgf9ned84kxvm3pqy98vhcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqqm9x092su3hd9rdfe8aafxp5pzpak3cegkem9qhhvmqqm96406ck3fx55</id>
    
      <title type="html">I&amp;#39;ll be upfront and say you&amp;#39;re probably over qualified, ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsr33qape58thqjparzlz757my4q0fawmgf9ned84kxvm3pqy98vhcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzqqm9x092su3hd9rdfe8aafxp5pzpak3cegkem9qhhvmqqm96406ck3fx55" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqsqqqxuey6ckfk0jr3trt3rd3f8yxsze9nt0trurg520t0c3p4vfgqprdmhxue69uhhg6r9vehhyetnwshxummnw3erztnrdakj7t6a4za&#39;&gt;nevent1q…a4za&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;I&amp;#39;ll be upfront and say you&amp;#39;re probably over qualified, and I can also say the told me they wanted a full time employee and not consulting, so just a heads up on that. But I&amp;#39;d be happy to chat about it if you&amp;#39;re still interested!&lt;br/&gt;&lt;blockquote class=&#34;border-l-05rem border-l-strongpink border-solid&#34;&gt;&lt;div class=&#34;-ml-4 bg-gradient-to-r from-gray-100 dark:from-zinc-800 to-transparent mr-0 mt-0 mb-4 pl-4 pr-2 py-2&#34;&gt;quoting &lt;br/&gt;&lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Article&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nevent1qvzqqqqqqypzqqm9x092su3hd9rdfe8aafxp5pzpak3cegkem9qhhvmqqm96406cqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qydhwumn8ghj7argv4nx7un9wd6zumn0wd68yvfwvdhk6tcqyru7wdkwgmlcdn9pwh7l5fwmm3vc4kxwhkvepv86f63h63r3yj52va8czjq&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;nevent1q…czjq&lt;/a&gt;&lt;/span&gt; &lt;/div&gt; Are you on signal? They&#39;re going to want to chat over email probably, so we can just go straight to email if you&#39;d prefer. My public email is on my website (just click the contact button to show it). I can give you more specifics. You&#39;d be expected to pick up on those tools with little guidance. Chances are I&#39;d be consulting with them on tech stack decisions. &lt;br/&gt;&lt;br/&gt;&lt;a href=&#34;https://www.vaughnnugent.com/&#34;&gt;https://www.vaughnnugent.com/&lt;/a&gt;&lt;br/&gt;&lt;a href=&#34;https://signal.me/#eu/duR00-XpDlel3PjNSnO2E4CLlAbd3cYy0s0qOO2BYD9vmaow4kBMh79UXh7dPkgU&#34;&gt;https://signal.me/#eu/duR00-XpDlel3PjNSnO2E4CLlAbd3cYy0s0qOO2BYD9vmaow4kBMh79UXh7dPkgU&lt;/a&gt; &lt;/blockquote&gt;
    </content>
    <updated>2026-04-06T04:34:37Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsyvh2xf0quf3f2n3r763zzn05zvga70alhkp6480x6jplc60v395qpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5ymwjdk</id>
    
      <title type="html">Build manifest for get_file_hash v0.4.7</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsyvh2xf0quf3f2n3r763zzn05zvga70alhkp6480x6jplc60v395qpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5ymwjdk" />
    <content type="html">
      In reply to &lt;a href=&#39;/nevent1qqsrccerhlgp0dlq5f44sznv0npqf3esqcpkqrf8myzef4u8z2w8gwqut2ddv&#39;&gt;nevent1q…2ddv&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Build manifest for get_file_hash v0.4.7
    </content>
    <updated>2026-04-06T04:17:40Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsrccerhlgp0dlq5f44sznv0npqf3esqcpkqrf8myzef4u8z2w8gwqpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5uyw55q</id>
    
      <title type="html">&amp;lt;?xml version=&amp;#39;1.0&amp;#39; ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsrccerhlgp0dlq5f44sznv0npqf3esqcpkqrf8myzef4u8z2w8gwqpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5uyw55q" />
    <content type="html">
      &amp;lt;?xml version=&amp;#39;1.0&amp;#39; encoding=&amp;#39;windows-1252&amp;#39;?&amp;gt;&lt;br/&gt;&amp;lt;!--&lt;br/&gt;  Copyright (C) 2017 Christopher R. Field.&lt;br/&gt;&lt;br/&gt;  Licensed under the Apache License, Version 2.0 (the &amp;#34;License&amp;#34;);&lt;br/&gt;  you may not use this file except in compliance with the License.&lt;br/&gt;  You may obtain a copy of the License at&lt;br/&gt;&lt;br/&gt;  &lt;a href=&#34;http://www.apache.org/licenses/LICENSE-2.0&#34;&gt;http://www.apache.org/licenses/LICENSE-2.0&lt;/a&gt;&lt;br/&gt;&lt;br/&gt;  Unless required by applicable law or agreed to in writing, software&lt;br/&gt;  distributed under the License is distributed on an &amp;#34;AS IS&amp;#34; BASIS,&lt;br/&gt;  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br/&gt;  See the License for the specific language governing permissions and&lt;br/&gt;  limitations under the License.&lt;br/&gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;!--&lt;br/&gt;  The &amp;#34;cargo wix&amp;#34; subcommand provides a variety of predefined variables available&lt;br/&gt;  for customization of this template. The values for each variable are set at&lt;br/&gt;  installer creation time. The following variables are available:&lt;br/&gt;&lt;br/&gt;  TargetTriple      = The rustc target triple name.&lt;br/&gt;  TargetEnv         = The rustc target environment. This is typically either&lt;br/&gt;                      &amp;#34;msvc&amp;#34; or &amp;#34;gnu&amp;#34; depending on the toolchain downloaded and&lt;br/&gt;                      installed.&lt;br/&gt;  TargetVendor      = The rustc target vendor. This is typically &amp;#34;pc&amp;#34;, but Rust&lt;br/&gt;                      does support other vendors, like &amp;#34;uwp&amp;#34;.&lt;br/&gt;  CargoTargetBinDir = The complete path to the directory containing the&lt;br/&gt;                      binaries (exes) to include. The default would be&lt;br/&gt;                      &amp;#34;target\release\&amp;#34;. If an explicit rustc target triple is&lt;br/&gt;                      used, i.e. cross-compiling, then the default path would&lt;br/&gt;                      be &amp;#34;target\&amp;lt;CARGO_TARGET&amp;gt;\&amp;lt;CARGO_PROFILE&amp;gt;&amp;#34;,&lt;br/&gt;                      where &amp;#34;&amp;lt;CARGO_TARGET&amp;gt;&amp;#34; is replaced with the &amp;#34;CargoTarget&amp;#34;&lt;br/&gt;                      variable value and &amp;#34;&amp;lt;CARGO_PROFILE&amp;gt;&amp;#34; is replaced with the&lt;br/&gt;                      value from the &amp;#34;CargoProfile&amp;#34; variable. This can also&lt;br/&gt;                      be overridden manually with the &amp;#34;target-bin-dir&amp;#34; flag.&lt;br/&gt;  CargoTargetDir    = The path to the directory for the build artifacts, i.e.&lt;br/&gt;                      &amp;#34;target&amp;#34;.&lt;br/&gt;  CargoProfile      = The cargo profile used to build the binaries&lt;br/&gt;                      (usually &amp;#34;debug&amp;#34; or &amp;#34;release&amp;#34;).&lt;br/&gt;  Version           = The version for the installer. The default is the&lt;br/&gt;                      &amp;#34;Major.Minor.Fix&amp;#34; semantic versioning number of the Rust&lt;br/&gt;                      package.&lt;br/&gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;!--&lt;br/&gt;  Please do not remove these pre-processor If-Else blocks. These are used with&lt;br/&gt;  the `cargo wix` subcommand to automatically determine the installation&lt;br/&gt;  destination for 32-bit versus 64-bit installers. Removal of these lines will&lt;br/&gt;  cause installation errors.&lt;br/&gt;--&amp;gt;&lt;br/&gt;&amp;lt;?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64 ?&amp;gt;&lt;br/&gt;    &amp;lt;?define PlatformProgramFilesFolder = &amp;#34;ProgramFiles64Folder&amp;#34; ?&amp;gt;&lt;br/&gt;&amp;lt;?else ?&amp;gt;&lt;br/&gt;    &amp;lt;?define PlatformProgramFilesFolder = &amp;#34;ProgramFilesFolder&amp;#34; ?&amp;gt;&lt;br/&gt;&amp;lt;?endif ?&amp;gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;Wix xmlns=&amp;#39;&lt;a href=&#34;http://schemas.microsoft.com/wix/2006/wi&amp;#39;&amp;gt&#34;&gt;http://schemas.microsoft.com/wix/2006/wi&amp;#39;&amp;gt&lt;/a&gt;;&lt;br/&gt;&lt;br/&gt;    &amp;lt;Product&lt;br/&gt;        Id=&amp;#39;*&amp;#39;&lt;br/&gt;        Name=&amp;#39;get_file_hash&amp;#39;&lt;br/&gt;        UpgradeCode=&amp;#39;DED69220-26E3-4406-B564-7F2B58C56F57&amp;#39;&lt;br/&gt;        Manufacturer=&amp;#39;gnostr admin@gnostr.org&amp;#39;&lt;br/&gt;        Language=&amp;#39;1033&amp;#39;&lt;br/&gt;        Codepage=&amp;#39;1252&amp;#39;&lt;br/&gt;        Version=&amp;#39;$(var.Version)&amp;#39;&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Package Id=&amp;#39;*&amp;#39;&lt;br/&gt;            Keywords=&amp;#39;Installer&amp;#39;&lt;br/&gt;            Description=&amp;#39;A utility crate providing a procedural macro to compute and embed file hashes at compile time.&amp;#39;&lt;br/&gt;            Manufacturer=&amp;#39;gnostr admin@gnostr.org&amp;#39;&lt;br/&gt;            InstallerVersion=&amp;#39;450&amp;#39;&lt;br/&gt;            Languages=&amp;#39;1033&amp;#39;&lt;br/&gt;            Compressed=&amp;#39;yes&amp;#39;&lt;br/&gt;            InstallScope=&amp;#39;perMachine&amp;#39;&lt;br/&gt;            SummaryCodepage=&amp;#39;1252&amp;#39;&lt;br/&gt;            /&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;MajorUpgrade&lt;br/&gt;            Schedule=&amp;#39;afterInstallInitialize&amp;#39;&lt;br/&gt;            DowngradeErrorMessage=&amp;#39;A newer version of [ProductName] is already installed. Setup will now exit.&amp;#39;/&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Media Id=&amp;#39;1&amp;#39; Cabinet=&amp;#39;media1.cab&amp;#39; EmbedCab=&amp;#39;yes&amp;#39; DiskPrompt=&amp;#39;CD-ROM #1&amp;#39;/&amp;gt;&lt;br/&gt;        &amp;lt;Property Id=&amp;#39;DiskPrompt&amp;#39; Value=&amp;#39;get_file_hash Installation&amp;#39;/&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Directory Id=&amp;#39;TARGETDIR&amp;#39; Name=&amp;#39;SourceDir&amp;#39;&amp;gt;&lt;br/&gt;            &amp;lt;Directory Id=&amp;#39;$(var.PlatformProgramFilesFolder)&amp;#39; Name=&amp;#39;PFiles&amp;#39;&amp;gt;&lt;br/&gt;                &amp;lt;Directory Id=&amp;#39;APPLICATIONFOLDER&amp;#39; Name=&amp;#39;get_file_hash&amp;#39;&amp;gt;&lt;br/&gt;                    &lt;br/&gt;                    &amp;lt;!--&lt;br/&gt;                      Enabling the license sidecar file in the installer is a four step process:&lt;br/&gt;&lt;br/&gt;                      1. Uncomment the `Component` tag and its contents.&lt;br/&gt;                      2. Change the value for the `Source` attribute in the `File` tag to a path&lt;br/&gt;                         to the file that should be included as the license sidecar file. The path&lt;br/&gt;                         can, and probably should be, relative to this file.&lt;br/&gt;                      3. Change the value for the `Name` attribute in the `File` tag to the&lt;br/&gt;                         desired name for the file when it is installed alongside the `bin` folder&lt;br/&gt;                         in the installation directory. This can be omitted if the desired name is&lt;br/&gt;                         the same as the file name.&lt;br/&gt;                      4. Uncomment the `ComponentRef` tag with the Id attribute value of &amp;#34;License&amp;#34;&lt;br/&gt;                         further down in this file.&lt;br/&gt;                    --&amp;gt;&lt;br/&gt;                    &amp;lt;!--&lt;br/&gt;                    &amp;lt;Component Id=&amp;#39;License&amp;#39; Guid=&amp;#39;*&amp;#39;&amp;gt;&lt;br/&gt;                        &amp;lt;File Id=&amp;#39;LicenseFile&amp;#39; Name=&amp;#39;ChangeMe&amp;#39; DiskId=&amp;#39;1&amp;#39; Source=&amp;#39;C:\Path\To\File&amp;#39; KeyPath=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                    &amp;lt;/Component&amp;gt;&lt;br/&gt;                    --&amp;gt;&lt;br/&gt;&lt;br/&gt;                    &amp;lt;Directory Id=&amp;#39;Bin&amp;#39; Name=&amp;#39;bin&amp;#39;&amp;gt;&lt;br/&gt;                        &amp;lt;Component Id=&amp;#39;Path&amp;#39; Guid=&amp;#39;8DB39A25-8B99-4C25-8CF5-4704353C7C6E&amp;#39; KeyPath=&amp;#39;yes&amp;#39;&amp;gt;&lt;br/&gt;                            &amp;lt;Environment&lt;br/&gt;                                Id=&amp;#39;PATH&amp;#39;&lt;br/&gt;                                Name=&amp;#39;PATH&amp;#39;&lt;br/&gt;                                Value=&amp;#39;[Bin]&amp;#39;&lt;br/&gt;                                Permanent=&amp;#39;no&amp;#39;&lt;br/&gt;                                Part=&amp;#39;last&amp;#39;&lt;br/&gt;                                Action=&amp;#39;set&amp;#39;&lt;br/&gt;                                System=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                        &amp;lt;/Component&amp;gt;&lt;br/&gt;                        &amp;lt;Component Id=&amp;#39;binary0&amp;#39; Guid=&amp;#39;*&amp;#39;&amp;gt;&lt;br/&gt;                            &amp;lt;File&lt;br/&gt;                                Id=&amp;#39;exe0&amp;#39;&lt;br/&gt;                                Name=&amp;#39;get_file_hash.exe&amp;#39;&lt;br/&gt;                                DiskId=&amp;#39;1&amp;#39;&lt;br/&gt;                                Source=&amp;#39;$(var.CargoTargetBinDir)\get_file_hash.exe&amp;#39;&lt;br/&gt;                                KeyPath=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                        &amp;lt;/Component&amp;gt;&lt;br/&gt;                        &amp;lt;Component Id=&amp;#39;binary1&amp;#39; Guid=&amp;#39;*&amp;#39;&amp;gt;&lt;br/&gt;                            &amp;lt;File&lt;br/&gt;                                Id=&amp;#39;exe1&amp;#39;&lt;br/&gt;                                Name=&amp;#39;n34.exe&amp;#39;&lt;br/&gt;                                DiskId=&amp;#39;1&amp;#39;&lt;br/&gt;                                Source=&amp;#39;$(var.CargoTargetBinDir)\n34.exe&amp;#39;&lt;br/&gt;                                KeyPath=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                        &amp;lt;/Component&amp;gt;&lt;br/&gt;                        &amp;lt;Component Id=&amp;#39;binary2&amp;#39; Guid=&amp;#39;*&amp;#39;&amp;gt;&lt;br/&gt;                            &amp;lt;File&lt;br/&gt;                                Id=&amp;#39;exe2&amp;#39;&lt;br/&gt;                                Name=&amp;#39;n34-relay.exe&amp;#39;&lt;br/&gt;                                DiskId=&amp;#39;1&amp;#39;&lt;br/&gt;                                Source=&amp;#39;$(var.CargoTargetBinDir)\n34-relay.exe&amp;#39;&lt;br/&gt;                                KeyPath=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                        &amp;lt;/Component&amp;gt;&lt;br/&gt;                        &amp;lt;Component Id=&amp;#39;binary3&amp;#39; Guid=&amp;#39;*&amp;#39;&amp;gt;&lt;br/&gt;                            &amp;lt;File&lt;br/&gt;                                Id=&amp;#39;exe3&amp;#39;&lt;br/&gt;                                Name=&amp;#39;readme.exe&amp;#39;&lt;br/&gt;                                DiskId=&amp;#39;1&amp;#39;&lt;br/&gt;                                Source=&amp;#39;$(var.CargoTargetBinDir)\readme.exe&amp;#39;&lt;br/&gt;                                KeyPath=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                        &amp;lt;/Component&amp;gt;&lt;br/&gt;                    &amp;lt;/Directory&amp;gt;&lt;br/&gt;                &amp;lt;/Directory&amp;gt;&lt;br/&gt;            &amp;lt;/Directory&amp;gt;&lt;br/&gt;        &amp;lt;/Directory&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Feature&lt;br/&gt;            Id=&amp;#39;Binaries&amp;#39;&lt;br/&gt;            Title=&amp;#39;Application&amp;#39;&lt;br/&gt;            Description=&amp;#39;Installs all binaries and the license.&amp;#39;&lt;br/&gt;            Level=&amp;#39;1&amp;#39;&lt;br/&gt;            ConfigurableDirectory=&amp;#39;APPLICATIONFOLDER&amp;#39;&lt;br/&gt;            AllowAdvertise=&amp;#39;no&amp;#39;&lt;br/&gt;            Display=&amp;#39;expand&amp;#39;&lt;br/&gt;            Absent=&amp;#39;disallow&amp;#39;&amp;gt;&lt;br/&gt;            &lt;br/&gt;            &amp;lt;!--&lt;br/&gt;              Uncomment the following `ComponentRef` tag to add the license&lt;br/&gt;              sidecar file to the installer.&lt;br/&gt;            --&amp;gt;&lt;br/&gt;            &amp;lt;!--&amp;lt;ComponentRef Id=&amp;#39;License&amp;#39;/&amp;gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;            &amp;lt;ComponentRef Id=&amp;#39;binary0&amp;#39;/&amp;gt;&lt;br/&gt;            &amp;lt;ComponentRef Id=&amp;#39;binary1&amp;#39;/&amp;gt;&lt;br/&gt;            &amp;lt;ComponentRef Id=&amp;#39;binary2&amp;#39;/&amp;gt;&lt;br/&gt;            &amp;lt;ComponentRef Id=&amp;#39;binary3&amp;#39;/&amp;gt;&lt;br/&gt;&lt;br/&gt;            &amp;lt;Feature&lt;br/&gt;                Id=&amp;#39;Environment&amp;#39;&lt;br/&gt;                Title=&amp;#39;PATH Environment Variable&amp;#39;&lt;br/&gt;                Description=&amp;#39;Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.&amp;#39;&lt;br/&gt;                Level=&amp;#39;1&amp;#39;&lt;br/&gt;                Absent=&amp;#39;allow&amp;#39;&amp;gt;&lt;br/&gt;                &amp;lt;ComponentRef Id=&amp;#39;Path&amp;#39;/&amp;gt;&lt;br/&gt;            &amp;lt;/Feature&amp;gt;&lt;br/&gt;        &amp;lt;/Feature&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;SetProperty Id=&amp;#39;ARPINSTALLLOCATION&amp;#39; Value=&amp;#39;[APPLICATIONFOLDER]&amp;#39; After=&amp;#39;CostFinalize&amp;#39;/&amp;gt;&lt;br/&gt;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;!--&lt;br/&gt;          Uncomment the following `Icon` and `Property` tags to change the product icon.&lt;br/&gt;&lt;br/&gt;          The product icon is the graphic that appears in the Add/Remove&lt;br/&gt;          Programs control panel for the application.&lt;br/&gt;        --&amp;gt;&lt;br/&gt;        &amp;lt;!--&amp;lt;Icon Id=&amp;#39;ProductICO&amp;#39; SourceFile=&amp;#39;wix\Product.ico&amp;#39;/&amp;gt;--&amp;gt;&lt;br/&gt;        &amp;lt;!--&amp;lt;Property Id=&amp;#39;ARPPRODUCTICON&amp;#39; Value=&amp;#39;ProductICO&amp;#39; /&amp;gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Property Id=&amp;#39;ARPHELPLINK&amp;#39; Value=&amp;#39;&lt;a href=&#34;https://github.com/gnostr-org/get_file_hash&amp;#39;/&amp;gt&#34;&gt;https://github.com/gnostr-org/get_file_hash&amp;#39;/&amp;gt&lt;/a&gt;;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;UI&amp;gt;&lt;br/&gt;            &amp;lt;UIRef Id=&amp;#39;WixUI_FeatureTree&amp;#39;/&amp;gt;&lt;br/&gt;            &lt;br/&gt;            &amp;lt;!--&lt;br/&gt;              Enabling the EULA dialog in the installer is a three step process:&lt;br/&gt;&lt;br/&gt;                1. Comment out or remove the two `Publish` tags that follow the&lt;br/&gt;                   `WixVariable` tag.&lt;br/&gt;                2. Uncomment the `&amp;lt;WixVariable Id=&amp;#39;WixUILicenseRtf&amp;#39; Value=&amp;#39;Path\to\Eula.rft&amp;#39;&amp;gt;` tag further down&lt;br/&gt;                3. Replace the `Value` attribute of the `WixVariable` tag with&lt;br/&gt;                   the path to a RTF file that will be used as the EULA and&lt;br/&gt;                   displayed in the license agreement dialog.&lt;br/&gt;            --&amp;gt;&lt;br/&gt;            &amp;lt;Publish Dialog=&amp;#39;WelcomeDlg&amp;#39; Control=&amp;#39;Next&amp;#39; Event=&amp;#39;NewDialog&amp;#39; Value=&amp;#39;CustomizeDlg&amp;#39; Order=&amp;#39;99&amp;#39;&amp;gt;1&amp;lt;/Publish&amp;gt;&lt;br/&gt;            &amp;lt;Publish Dialog=&amp;#39;CustomizeDlg&amp;#39; Control=&amp;#39;Back&amp;#39; Event=&amp;#39;NewDialog&amp;#39; Value=&amp;#39;WelcomeDlg&amp;#39; Order=&amp;#39;99&amp;#39;&amp;gt;1&amp;lt;/Publish&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;/UI&amp;gt;&lt;br/&gt;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;!--&lt;br/&gt;          Enabling the EULA dialog in the installer requires uncommenting&lt;br/&gt;          the following `WixUILicenseRTF` tag and changing the `Value`&lt;br/&gt;          attribute.&lt;br/&gt;        --&amp;gt;&lt;br/&gt;        &amp;lt;!-- &amp;lt;WixVariable Id=&amp;#39;WixUILicenseRtf&amp;#39; Value=&amp;#39;Relative\Path\to\Eula.rtf&amp;#39;/&amp;gt; --&amp;gt;&lt;br/&gt;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;!--&lt;br/&gt;          Uncomment the next `WixVariable` tag to customize the installer&amp;#39;s&lt;br/&gt;          Graphical User Interface (GUI) and add a custom banner image across&lt;br/&gt;          the top of each screen. See the WiX Toolset documentation for details&lt;br/&gt;          about customization.&lt;br/&gt;&lt;br/&gt;          The banner BMP dimensions are 493 x 58 pixels.&lt;br/&gt;        --&amp;gt;&lt;br/&gt;        &amp;lt;!--&amp;lt;WixVariable Id=&amp;#39;WixUIBannerBmp&amp;#39; Value=&amp;#39;wix\Banner.bmp&amp;#39;/&amp;gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;!--&lt;br/&gt;          Uncomment the next `WixVariable` tag to customize the installer&amp;#39;s&lt;br/&gt;          Graphical User Interface (GUI) and add a custom image to the first&lt;br/&gt;          dialog, or screen. See the WiX Toolset documentation for details about&lt;br/&gt;          customization.&lt;br/&gt;&lt;br/&gt;          The dialog BMP dimensions are 493 x 312 pixels.&lt;br/&gt;        --&amp;gt;&lt;br/&gt;        &amp;lt;!--&amp;lt;WixVariable Id=&amp;#39;WixUIDialogBmp&amp;#39; Value=&amp;#39;wix\Dialog.bmp&amp;#39;/&amp;gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;    &amp;lt;/Product&amp;gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;/Wix&amp;gt;&lt;br/&gt;
    </content>
    <updated>2026-04-06T04:17:28Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsz5ech70ff7trtyu02ud79pynr0xdlaafstz96znar75zzmtqclgcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5peuvxd</id>
    
      <title type="html">use std::process::Command; use std::fs; use sha2::{Digest, ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsz5ech70ff7trtyu02ud79pynr0xdlaafstz96znar75zzmtqclgcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5peuvxd" />
    <content type="html">
      use std::process::Command;&lt;br/&gt;use std::fs;&lt;br/&gt;use sha2::{Digest, Sha256};&lt;br/&gt;&lt;br/&gt;fn calculate_sha256(file_path: &amp;amp;str) -&amp;gt; String {&lt;br/&gt;    let content = fs::read(file_path).expect(&amp;#34;Unable to read file&amp;#34;);&lt;br/&gt;    let mut hasher = Sha256::new();&lt;br/&gt;    hasher.update(&amp;amp;content);&lt;br/&gt;    hasher.finalize()&lt;br/&gt;        .iter()&lt;br/&gt;        .map(|b| format!(&amp;#34;{:02x}&amp;#34;, b))&lt;br/&gt;        .collect()&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[test]&lt;br/&gt;fn test_get_file_hash_binary_no_features() {&lt;br/&gt;    let output = Command::new(&amp;#34;cargo&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;run&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;--bin&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;get_file_hash&amp;#34;)&lt;br/&gt;        .output()&lt;br/&gt;        .expect(&amp;#34;Failed to execute command&amp;#34;);&lt;br/&gt;&lt;br/&gt;    let stdout = String::from_utf8_lossy(&amp;amp;output.stdout);&lt;br/&gt;    let stderr = String::from_utf8_lossy(&amp;amp;output.stderr);&lt;br/&gt;&lt;br/&gt;    // Assert that the command ran successfully&lt;br/&gt;    assert!(output.status.success(), &amp;#34;Command failed with stderr: {}&amp;#34;, stderr);&lt;br/&gt;&lt;br/&gt;    // Manually calculate the hash of the binary&amp;#39;s source file&lt;br/&gt;    let expected_hash = calculate_sha256(&amp;#34;src/bin/get_file_hash.rs&amp;#34;);&lt;br/&gt;&lt;br/&gt;    // Assert that the output contains the correct hash&lt;br/&gt;    // Check for the raw hash first&lt;br/&gt;    assert!(stdout.contains(&amp;amp;expected_hash), &amp;#34;Output did not contain raw expected hash. Expected: {}, Actual: {}&amp;#34;, expected_hash, stdout);&lt;br/&gt;&lt;br/&gt;    // Then check for the formatted string, including backticks&lt;br/&gt;    // Use a regex-like check for more flexibility with newlines if needed, or refine to exact match&lt;br/&gt;    let expected_hash_line = format!(&amp;#34;*   **SHA-256 Hash:** `{}`&amp;#34;, expected_hash);&lt;br/&gt;    assert!(stdout.contains(&amp;amp;expected_hash_line), &amp;#34;Output did not contain expected hash line. Expected line: {}, Actual: {}&amp;#34;, expected_hash_line, stdout);&lt;br/&gt;&lt;br/&gt;    // Assert that the output contains &amp;#34;Integrity Verified.&amp;#34;&lt;br/&gt;    assert!(stdout.contains(&amp;#34;Integrity Verified.&amp;#34;), &amp;#34;Output did not contain &amp;#39;Integrity Verified.&amp;#39;. stdout: {}&amp;#34;, stdout);&lt;br/&gt;&lt;br/&gt;    println!(&amp;#34;Output from get_file_hash binary (no features):&lt;br/&gt;{}&amp;#34;, stdout);&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[test]&lt;br/&gt;fn test_get_file_hash_binary_with_nostr_feature() {&lt;br/&gt;    let output = Command::new(&amp;#34;cargo&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;run&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;--bin&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;get_file_hash&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;--features&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;nostr&amp;#34;)&lt;br/&gt;        .output()&lt;br/&gt;        .expect(&amp;#34;Failed to execute command&amp;#34;);&lt;br/&gt;&lt;br/&gt;    let stdout = String::from_utf8_lossy(&amp;amp;output.stdout);&lt;br/&gt;    let stderr = String::from_utf8_lossy(&amp;amp;output.stderr);&lt;br/&gt;&lt;br/&gt;    // Assert that the command ran successfully&lt;br/&gt;    assert!(output.status.success(), &amp;#34;Command failed with stderr: {}&amp;#34;, stderr);&lt;br/&gt;&lt;br/&gt;    // Manually calculate the hash of the binary&amp;#39;s source file&lt;br/&gt;    let expected_hash = calculate_sha256(&amp;#34;src/bin/get_file_hash.rs&amp;#34;);&lt;br/&gt;&lt;br/&gt;    // Assert that the output contains the correct hash&lt;br/&gt;    assert!(stdout.contains(&amp;amp;expected_hash), &amp;#34;Output did not contain raw expected hash. Expected: {}, Actual: {}&amp;#34;, expected_hash, stdout);&lt;br/&gt;&lt;br/&gt;    // Then check for the formatted string, including backticks&lt;br/&gt;    let expected_hash_line = format!(&amp;#34;*   **SHA-256 Hash:** `{}`&amp;#34;, expected_hash);&lt;br/&gt;    assert!(stdout.contains(&amp;amp;expected_hash_line), &amp;#34;Output did not contain expected hash line. Expected line: {}, Actual: {}&amp;#34;, expected_hash_line, stdout);&lt;br/&gt;&lt;br/&gt;    // Assert that the output contains &amp;#34;Integrity Verified.&amp;#34;&lt;br/&gt;    assert!(stdout.contains(&amp;#34;Integrity Verified.&amp;#34;), &amp;#34;Output did not contain &amp;#39;Integrity Verified.&amp;#39;. stdout: {}&amp;#34;, stdout);&lt;br/&gt;&lt;br/&gt;    println!(&amp;#34;Output from get_file_hash binary (with nostr feature):&lt;br/&gt;{}&amp;#34;, stdout);&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-06T04:17:13Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs80g3jtm0jkzywyy64kcw2ehcqlfwt52gyaxysvusa7ccjx9kjpncpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5t8puqu</id>
    
      <title type="html">//! A crate providing the `get_file_hash!` procedural macro. //! ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs80g3jtm0jkzywyy64kcw2ehcqlfwt52gyaxysvusa7ccjx9kjpncpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5t8puqu" />
    <content type="html">
      //! A crate providing the `get_file_hash!` procedural macro.&lt;br/&gt;//!&lt;br/&gt;//! This macro allows you to compute the SHA-256 hash of a file at compile time,&lt;br/&gt;//! embedding the resulting hash string directly into your Rust executable.&lt;br/&gt;&lt;br/&gt;pub use get_file_hash_core::get_file_hash;&lt;br/&gt;&lt;br/&gt;/// The SHA-256 hash of this crate&amp;#39;s `build.rs` at the time of compilation.&lt;br/&gt;pub const BUILD_HASH: &amp;amp;str = env!(&amp;#34;BUILD_HASH&amp;#34;);&lt;br/&gt;&lt;br/&gt;/// The SHA-256 hash of this crate&amp;#39;s `Cargo.toml` at the time of compilation.&lt;br/&gt;pub const CARGO_TOML_HASH: &amp;amp;str = env!(&amp;#34;CARGO_TOML_HASH&amp;#34;);&lt;br/&gt;&lt;br/&gt;/// The SHA-256 hash of this crate&amp;#39;s `src/lib.rs` at the time of compilation.&lt;br/&gt;pub const LIB_HASH: &amp;amp;str = env!(&amp;#34;LIB_HASH&amp;#34;);&lt;br/&gt;&lt;br/&gt;/// The name of the package as specified in Cargo.toml.&lt;br/&gt;pub const CARGO_PKG_NAME: &amp;amp;str = env!(&amp;#34;CARGO_PKG_NAME&amp;#34;);&lt;br/&gt;&lt;br/&gt;/// The version of the package as specified in Cargo.toml.&lt;br/&gt;pub const CARGO_PKG_VERSION: &amp;amp;str = env!(&amp;#34;CARGO_PKG_VERSION&amp;#34;);&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;/// The git commit hash of the repository at the time of compilation.&lt;br/&gt;pub const GIT_COMMIT_HASH: &amp;amp;str = env!(&amp;#34;GIT_COMMIT_HASH&amp;#34;);&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;/// The git branch of the repository at the time of compilation.&lt;br/&gt;pub const GIT_BRANCH: &amp;amp;str = env!(&amp;#34;GIT_BRANCH&amp;#34;);&lt;br/&gt;&lt;br/&gt;#[cfg(test)]&lt;br/&gt;mod tests {&lt;br/&gt;    use sha2::{Digest, Sha256};&lt;br/&gt;&lt;br/&gt;    use super::*;&lt;br/&gt;&lt;br/&gt;    /// Verifies that the exported CARGO_TOML_HASH is not empty.&lt;br/&gt;    #[test]&lt;br/&gt;    fn test_injected_hash_exists() {&lt;br/&gt;        assert!(!BUILD_HASH.is_empty());&lt;br/&gt;        println!(&amp;#34;Verified build.rs Hash:&lt;br/&gt;{}&amp;#34;, BUILD_HASH);&lt;br/&gt;&lt;br/&gt;        assert!(!CARGO_TOML_HASH.is_empty());&lt;br/&gt;        println!(&amp;#34;Verified Cargo.toml Hash:&lt;br/&gt;{}&amp;#34;, CARGO_TOML_HASH);&lt;br/&gt;&lt;br/&gt;        assert!(!LIB_HASH.is_empty());&lt;br/&gt;        println!(&amp;#34;Verified src/lib.rs Hash:\n{}&amp;#34;, LIB_HASH);&lt;br/&gt;&lt;br/&gt;        assert!(!CARGO_PKG_NAME.is_empty());&lt;br/&gt;        println!(&amp;#34;Verified Package Name:\n{}&amp;#34;, CARGO_PKG_NAME);&lt;br/&gt;&lt;br/&gt;        assert!(!CARGO_PKG_VERSION.is_empty());&lt;br/&gt;        println!(&amp;#34;Verified Package Version:\n{}&amp;#34;, CARGO_PKG_VERSION);&lt;br/&gt;&lt;br/&gt;        #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;        {&lt;br/&gt;            assert!(!GIT_COMMIT_HASH.is_empty());&lt;br/&gt;            println!(&amp;#34;Verified Git Commit Hash:\n{}&amp;#34;, GIT_COMMIT_HASH);&lt;br/&gt;&lt;br/&gt;            assert!(!GIT_BRANCH.is_empty());&lt;br/&gt;            println!(&amp;#34;Verified Git Branch:\n{}&amp;#34;, GIT_BRANCH);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Tests that the `get_file_hash!` macro correctly computes the SHA-256&lt;br/&gt;    /// hash of `lib.rs` and that it matches a manually computed hash of the&lt;br/&gt;    /// same file.&lt;br/&gt;    #[test]&lt;br/&gt;    fn test_get_lib_hash() {&lt;br/&gt;        let file_content = include_bytes!(&amp;#34;lib.rs&amp;#34;);&lt;br/&gt;&lt;br/&gt;        let mut hasher = Sha256::new();&lt;br/&gt;        hasher.update(file_content);&lt;br/&gt;        let expected_hash = hasher&lt;br/&gt;            .finalize()&lt;br/&gt;            .iter()&lt;br/&gt;            .map(|b| format!(&amp;#34;{:02x}&amp;#34;, b))&lt;br/&gt;            .collect::&amp;lt;String&amp;gt;();&lt;br/&gt;&lt;br/&gt;        let actual_hash = get_file_hash!(&amp;#34;lib.rs&amp;#34;);&lt;br/&gt;        assert_eq!(actual_hash, expected_hash);&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-06T04:17:01Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs8cagfhdysazr8ulh4nmcy8xvx50p0m2877pe242leltw8pqfvrxgpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5s9tt3s</id>
    
      <title type="html">Relay URL,Latitude,Longitude wot.nostr.party,36.1627,-86.7816 ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs8cagfhdysazr8ulh4nmcy8xvx50p0m2877pe242leltw8pqfvrxgpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5s9tt3s" />
    <content type="html">
      Relay URL,Latitude,Longitude&lt;br/&gt;wot.nostr.party,36.1627,-86.7816&lt;br/&gt;nostr.simplex.icu,50.1109,8.68213&lt;br/&gt;relay.snort.social,53.3498,-6.26031&lt;br/&gt;nostr.stakey.net,52.3676,4.90414&lt;br/&gt;relay.mccormick.cx,52.3563,4.95714&lt;br/&gt;nostr.overmind.lol,43.6532,-79.3832&lt;br/&gt;nostriches.club,43.6532,-79.3832&lt;br/&gt;kanagrovv-pyramid.kozow.com,43.4305,-83.9638&lt;br/&gt;relay.toastr.net,40.8054,-74.0241&lt;br/&gt;relay.primal.net,43.6532,-79.3832&lt;br/&gt;r.bitcoinhold.net,43.6532,-79.3832&lt;br/&gt;wot.dtonon.com,43.6532,-79.3832&lt;br/&gt;santo.iguanatech.net,40.8302,-74.1299&lt;br/&gt;relay2.ngengine.org,43.6532,-79.3832&lt;br/&gt;nostr.oxtr.dev,50.4754,12.3683&lt;br/&gt;simplex.icu,50.1109,8.68213&lt;br/&gt;bitchat.nostr1.com,38.6327,-90.1961&lt;br/&gt;relay.nostu.be,40.4167,-3.70329&lt;br/&gt;nostr.mifen.me,43.6532,-79.3832&lt;br/&gt;relay.vantis.ninja,43.6532,-79.3832&lt;br/&gt;nostr-kyomu-haskell.onrender.com,37.7775,-122.397&lt;br/&gt;nostr-relay.amethyst.name,39.0067,-77.4291&lt;br/&gt;relay.bitcoindistrict.org,43.6532,-79.3832&lt;br/&gt;nos.xmark.cc,50.6924,3.20113&lt;br/&gt;relay01.lnfi.network,35.6764,139.65&lt;br/&gt;nostr-dev.wellorder.net,45.5201,-122.99&lt;br/&gt;nostr.rblb.it,43.7094,10.6582&lt;br/&gt;offchain.pub,39.1585,-94.5728&lt;br/&gt;wot.nostr.place,32.7767,-96.797&lt;br/&gt;fanfares.nostr1.com,38.6327,-90.1961&lt;br/&gt;ephemeral.snowflare.cc,43.6532,-79.3832&lt;br/&gt;relay.visionfusen.org,43.6532,-79.3832&lt;br/&gt;nostr.carroarmato0.be,51.0368,3.21186&lt;br/&gt;social.amanah.eblessing.co,48.1046,11.6002&lt;br/&gt;relay.cyphernomad.com,60.4032,25.0321&lt;br/&gt;nostr-rs-relay-qj1h.onrender.com,37.7775,-122.397&lt;br/&gt;relay.ngengine.org,43.6532,-79.3832&lt;br/&gt;relay.nosto.re,51.1792,5.89444&lt;br/&gt;rusty-uat.siberian-albacore.ts.net:8443,35.6764,139.65&lt;br/&gt;nostr.luisschwab.net,43.6532,-79.3832&lt;br/&gt;nostr.aruku.ovh,1.27994,103.849&lt;br/&gt;srtrelay.c-stellar.net,43.6532,-79.3832&lt;br/&gt;nostr.tagomago.me,3.139,101.687&lt;br/&gt;relay.threenine.services,51.5222,-0.62916&lt;br/&gt;nostr.robosats.org,64.1476,-21.9392&lt;br/&gt;nostr.2b9t.xyz,34.0549,-118.243&lt;br/&gt;strfry.apps3.slidestr.net,40.4167,-3.70329&lt;br/&gt;kitchen.zap.cooking,43.6532,-79.3832&lt;br/&gt;relay.homeinhk.xyz,45.5152,-122.678&lt;br/&gt;relay.seq1.net,43.6532,-79.3832&lt;br/&gt;relay.bornheimer.app,50.1109,8.68213&lt;br/&gt;relay.vrtmrz.net,43.6532,-79.3832&lt;br/&gt;nostr.red5d.dev,43.6532,-79.3832&lt;br/&gt;relay-freeharmonypeople.space,38.7223,-9.13934&lt;br/&gt;aaa-api.freefrom.space/v1/ws,43.6532,-79.3832&lt;br/&gt;dev.relay.edufeed.org,49.4521,11.0767&lt;br/&gt;nostr.myshosholoza.co.za,52.3913,4.66545&lt;br/&gt;nostr.plantroon.com,50.1013,8.62643&lt;br/&gt;wot.dergigi.com,64.1476,-21.9392&lt;br/&gt;npub1spxdug4m3y24hpx5crm0el4zhkk0wafs8kp6m0xu0wecygqej2xqq8gyhx.fips.network,43.6532,-79.3832&lt;br/&gt;nostrelay.circum.space,52.3676,4.90414&lt;br/&gt;relay.dreamith.to,43.6532,-79.3832&lt;br/&gt;relay.nostriches.club,43.6532,-79.3832&lt;br/&gt;nostr-relayrs.gateway.in.th,15.5163,103.194&lt;br/&gt;nostr.chrissexton.org,43.6532,-79.3832&lt;br/&gt;relay.flashapp.me,43.652,-79.3633&lt;br/&gt;nostr-relay-1.trustlessenterprise.com,43.6532,-79.3832&lt;br/&gt;strfry.shock.network,39.0438,-77.4874&lt;br/&gt;relay.snotr.nl:49999,52.0195,4.42946&lt;br/&gt;spatia-arcana.com,34.0362,-118.443&lt;br/&gt;nostr.computingcache.com,34.0356,-118.442&lt;br/&gt;nostr.self-determined.de,53.5,10.25&lt;br/&gt;relay.sharegap.net,43.6532,-79.3832&lt;br/&gt;spookstr2.nostr1.com,38.6327,-90.1961&lt;br/&gt;v-relay.d02.vrtmrz.net,34.6937,135.502&lt;br/&gt;bridge.tagomago.me,3.139,101.687&lt;br/&gt;antiprimal.net,43.6532,-79.3832&lt;br/&gt;relay.nostrdice.com,-33.8688,151.209&lt;br/&gt;purpura.cloud,43.6532,-79.3832&lt;br/&gt;espelho.girino.org,43.6532,-79.3832&lt;br/&gt;relay.mostro.network,40.8302,-74.1299&lt;br/&gt;temp.iris.to,43.6532,-79.3832&lt;br/&gt;pyramid.self-determined.de,53.5,10.25&lt;br/&gt;relay.jeffg.fyi,43.6532,-79.3832&lt;br/&gt;nostr.aruku.kro.kr,37.3589,127.115&lt;br/&gt;chat-relay.zap-work.com,43.6532,-79.3832&lt;br/&gt;relay.islandbitcoin.com,12.8498,77.6545&lt;br/&gt;nostr.zoracle.org,45.6018,-121.185&lt;br/&gt;relay-dev.satlantis.io,40.8302,-74.1299&lt;br/&gt;relay.earthly.city,34.0362,-118.443&lt;br/&gt;speakeasy.cellar.social,49.4543,11.0746&lt;br/&gt;relay.bnos.space,43.6532,-79.3832&lt;br/&gt;relay.henryxplace.eu.org:9988,31.2304,121.474&lt;br/&gt;relay.bitmacro.cloud,43.6532,-79.3832&lt;br/&gt;nostr.thebiglake.org,32.71,-96.6745&lt;br/&gt;relay.lab.rytswd.com,49.4543,11.0746&lt;br/&gt;nostr.nadajnik.org,50.1109,8.68213&lt;br/&gt;relay.evanverma.com,40.8302,-74.1299&lt;br/&gt;relay2.angor.io,48.1046,11.6002&lt;br/&gt;prl.plus,55.7628,37.5983&lt;br/&gt;myvoiceourstory.org,37.3598,-121.981&lt;br/&gt;relay.arx-ccn.com,50.4754,12.3683&lt;br/&gt;wot.sudocarlos.com,43.6532,-79.3832&lt;br/&gt;relayrs.notoshi.win,43.6532,-79.3832&lt;br/&gt;nostrcity-club.fly.dev,48.8575,2.35138&lt;br/&gt;relay.tagayasu.xyz,45.4215,-75.6972&lt;br/&gt;nostr.blankfors.se,60.1699,24.9384&lt;br/&gt;nrs-01.darkcloudarcade.com,39.1008,-94.5811&lt;br/&gt;relay.lightning.pub,39.0438,-77.4874&lt;br/&gt;nostr-02.yakihonne.com,1.32123,103.695&lt;br/&gt;relay.nostrverse.net,43.6532,-79.3832&lt;br/&gt;nostr.wecsats.io,43.6532,-79.3832&lt;br/&gt;relay.illuminodes.com,47.6062,-122.332&lt;br/&gt;api.freefrom.space/v1/ws,43.6532,-79.3832&lt;br/&gt;nostr-relay.psfoundation.info,39.0438,-77.4874&lt;br/&gt;relay.samt.st,40.8302,-74.1299&lt;br/&gt;nostr-relay.cbrx.io,43.6532,-79.3832&lt;br/&gt;inbox.mycelium.social,38.627,-90.1994&lt;br/&gt;relay.anmore.me,49.281,-123.117&lt;br/&gt;no.str.cr,10.074,-84.2155&lt;br/&gt;nstr.a0a1.space,52.3563,4.95714&lt;br/&gt;relay.typedcypher.com,51.5072,-0.127586&lt;br/&gt;relay.bitmacro.pro,43.6532,-79.3832&lt;br/&gt;relay.nostrzh.org,43.6532,-79.3832&lt;br/&gt;ynostr.yael.at,60.1699,24.9384&lt;br/&gt;nostr-relay.zeabur.app,25.0797,121.234&lt;br/&gt;dynasty.libretechsystems.xyz,55.4724,9.87335&lt;br/&gt;nostr.bitcoiner.social,47.6743,-117.112&lt;br/&gt;nostr.girino.org,43.6532,-79.3832&lt;br/&gt;nostr2.girino.org,43.6532,-79.3832&lt;br/&gt;nostr-verified.wellorder.net,45.5201,-122.99&lt;br/&gt;relay.fundstr.me,42.3601,-71.0589&lt;br/&gt;relay.mapboss.co.th,13.7234,100.784&lt;br/&gt;relay.qstr.app,51.5072,-0.127586&lt;br/&gt;nostr-rs-relay-ishosta.phamthanh.me,43.6532,-79.3832&lt;br/&gt;relay.klabo.world,47.674,-122.122&lt;br/&gt;relay.minibolt.info,43.6532,-79.3832&lt;br/&gt;x.kojira.io,43.6532,-79.3832&lt;br/&gt;relay-dev.gulugulu.moe,43.6532,-79.3832&lt;br/&gt;relay.nostriot.com,41.5695,-83.9786&lt;br/&gt;relayone.soundhsa.com,39.1008,-94.5811&lt;br/&gt;nr.yay.so,46.2126,6.1154&lt;br/&gt;relay.bithome.site,52.3563,4.95714&lt;br/&gt;relay.damus.io,43.6532,-79.3832&lt;br/&gt;nostr.mikoshi.de,50.1109,8.68213&lt;br/&gt;nostr.defucc.me,50.1109,8.68213&lt;br/&gt;relay.malxte.de,52.52,13.405&lt;br/&gt;relay.orangepill.ovh,49.1689,-0.358841&lt;br/&gt;bbw-nostr.xyz,41.5284,-87.4237&lt;br/&gt;kasztanowa.bieda.it,43.6532,-79.3832&lt;br/&gt;bitcoiner.social,47.6743,-117.112&lt;br/&gt;relay.lacompagniemaximus.com,45.3147,-73.8785&lt;br/&gt;relay.mostr.pub,43.6532,-79.3832&lt;br/&gt;relay.lanavault.space,60.1699,24.9384&lt;br/&gt;kotukonostr.onrender.com,37.7775,-122.397&lt;br/&gt;relay.ditto.pub,43.6532,-79.3832&lt;br/&gt;relay.erybody.com,41.4513,-81.7021&lt;br/&gt;nostr.dlcdevkit.com,40.0992,-83.1141&lt;br/&gt;ribo.us.nostria.app,41.5868,-93.625&lt;br/&gt;relay.paulstephenborile.com,49.4543,11.0746&lt;br/&gt;testnet-relay.samt.st,40.8302,-74.1299&lt;br/&gt;relay.purplefrog.cloud,35.6916,139.768&lt;br/&gt;relay.agorist.space,52.3734,4.89406&lt;br/&gt;nostr-relay.zimage.com,34.0549,-118.243&lt;br/&gt;nostr.azzamo.net,52.2633,21.0283&lt;br/&gt;strfry.elswa-dev.online,50.1109,8.68213&lt;br/&gt;wot.shaving.kiwi,43.6532,-79.3832&lt;br/&gt;okn.czas.plus,50.1109,8.68213&lt;br/&gt;bcast.seutoba.com.br,43.6532,-79.3832&lt;br/&gt;relay.sigit.io,50.4754,12.3683&lt;br/&gt;syb.lol,34.0549,-118.243&lt;br/&gt;relay.libernet.app,43.6532,-79.3832&lt;br/&gt;relay.angor.io,48.1046,11.6002&lt;br/&gt;relay.staging.commonshub.brussels,49.4543,11.0746&lt;br/&gt;strfry.atlantislabs.space,43.6532,-79.3832&lt;br/&gt;nostr.wom.wtf,43.6532,-79.3832&lt;br/&gt;nostrride.io,37.3986,-121.964&lt;br/&gt;nostr.dpinkerton.com,39.1008,-94.5811&lt;br/&gt;r.0kb.io,32.789,-96.7989&lt;br/&gt;nostr.hekster.org,37.3986,-121.964&lt;br/&gt;satsage.xyz,37.3986,-121.964&lt;br/&gt;nostr.islandarea.net,35.4669,-97.6473&lt;br/&gt;ve.agorawlc.com,50.4754,12.3683&lt;br/&gt;relay.openfarmtools.org,60.1699,24.9384&lt;br/&gt;top.testrelay.top,43.6532,-79.3832&lt;br/&gt;relay-rpi.edufeed.org,49.4521,11.0767&lt;br/&gt;pyramid.cult.cash,32.9483,-96.7299&lt;br/&gt;relay.edino.net,56.6268,47.9193&lt;br/&gt;nostr.snowbla.de,60.1699,24.9384&lt;br/&gt;relay.wavefunc.live,39.7392,-104.99&lt;br/&gt;tenex.chat,50.4754,12.3683&lt;br/&gt;relay.getsafebox.app,43.6532,-79.3832&lt;br/&gt;nostr.bond,50.1109,8.68213&lt;br/&gt;nostrelites.org,41.8781,-87.6298&lt;br/&gt;relay.plebeian.market,50.1109,8.68213&lt;br/&gt;relay.laantungir.net,-19.4692,-42.5315&lt;br/&gt;relay.decentnewsroom.com,50.4754,12.3683&lt;br/&gt;nostr-relay.nextblockvending.com,47.2343,-119.853&lt;br/&gt;relay.spacetomatoes.net,42.3601,-71.0589&lt;br/&gt;nostrbtc.com,43.6532,-79.3832&lt;br/&gt;relay.puresignal.news,43.6532,-79.3832&lt;br/&gt;relay-testnet.k8s.layer3.news,37.3387,-121.885&lt;br/&gt;relay.binaryrobot.com,43.6532,-79.3832&lt;br/&gt;relay.wavlake.com,41.2619,-95.8608&lt;br/&gt;inbox.scuba323.com,40.8218,-74.45&lt;br/&gt;nostr.spaceshell.xyz,43.6532,-79.3832&lt;br/&gt;relay.nostr.place,32.7767,-96.797&lt;br/&gt;holland-excited-charming-experiencing.trycloudflare.com,43.6532,-79.3832&lt;br/&gt;theoutpost.life,64.1476,-21.9392&lt;br/&gt;relay.fckstate.net,59.3293,18.0686&lt;br/&gt;bcast.girino.org,43.6532,-79.3832&lt;br/&gt;discovery.us.nostria.app,52.3676,4.90414&lt;br/&gt;relay.bullishbounty.com,43.6532,-79.3832&lt;br/&gt;nostr.88mph.life,51.5072,-0.127586&lt;br/&gt;nostr.tadryanom.me,43.6532,-79.3832&lt;br/&gt;nostr.sathoarder.com,48.5734,7.75211&lt;br/&gt;relay.nostr.net,43.6532,-79.3832&lt;br/&gt;zw.agorawlc.com,50.4754,12.3683&lt;br/&gt;relay.internationalright-wing.org,-22.5022,-48.7114&lt;br/&gt;nostr.vulpem.com,49.4543,11.0746&lt;br/&gt;wot.codingarena.top,50.4754,12.3683&lt;br/&gt;reraw.pbla2fish.cc,43.6532,-79.3832&lt;br/&gt;plebchain.club,43.6532,-79.3832&lt;br/&gt;orly-relay.imwald.eu,48.8575,2.35138&lt;br/&gt;relay.satnam.pub,43.6532,-79.3832&lt;br/&gt;cs-relay.nostrdev.com,50.4754,12.3683&lt;br/&gt;schnorr.me,43.6532,-79.3832&lt;br/&gt;nostr-relay.online,43.6532,-79.3832&lt;br/&gt;relay.routstr.com,43.6532,-79.3832&lt;br/&gt;relay.ohstr.com,43.6532,-79.3832&lt;br/&gt;relay.lanacoin-eternity.com,40.8302,-74.1299&lt;br/&gt;wot.nostr.net,43.6532,-79.3832&lt;br/&gt;nostr.ps1829.com,33.8851,130.883&lt;br/&gt;yabu.me,35.6092,139.73&lt;br/&gt;soloco.nl,43.6532,-79.3832&lt;br/&gt;librerelay.aaroniumii.com,43.6532,-79.3832&lt;br/&gt;relay.mmwaves.de,48.8575,2.35138&lt;br/&gt;relay.artx.market,43.6548,-79.3885&lt;br/&gt;nostr.jerrynya.fun,31.2304,121.474&lt;br/&gt;relay-arg.zombi.cloudrodion.com,1.35208,103.82&lt;br/&gt;relay.edufeed.org,49.4521,11.0767&lt;br/&gt;discovery.eu.nostria.app,52.3676,4.90414&lt;br/&gt;relay.layer.systems,49.0291,8.35695&lt;br/&gt;nostr-rs-relay.dev.fedibtc.com,39.0438,-77.4874&lt;br/&gt;relay.0xchat.com,43.6532,-79.3832&lt;br/&gt;nos.lol,50.4754,12.3683&lt;br/&gt;lightning.red,53.3498,-6.26031&lt;br/&gt;slick.mjex.me,39.0418,-77.4744&lt;br/&gt;relay.boredvictor.xyz,41.3888,2.15899&lt;br/&gt;nostr.rtvslawenia.com,49.4543,11.0746&lt;br/&gt;relay.mitchelltribe.com,39.0438,-77.4874&lt;br/&gt;nostr.4rs.nl,49.0291,8.35696&lt;br/&gt;relay.olas.app,50.4754,12.3683&lt;br/&gt;memlay.v0l.io,53.3498,-6.26031&lt;br/&gt;nostr-01.yakihonne.com,1.29524,103.79&lt;br/&gt;relay.satmaxt.xyz,43.6532,-79.3832&lt;br/&gt;nostrcheck.tnsor.network,43.6532,-79.3832&lt;br/&gt;relay.guggero.org,46.0037,8.95105&lt;br/&gt;ai.techunder.tech:56711,22.5429,114.06&lt;br/&gt;premium.primal.net,43.6532,-79.3832&lt;br/&gt;nostr.tac.lol,47.4748,-122.273&lt;br/&gt;relay.zone667.com,60.1699,24.9384&lt;br/&gt;nostr-relay.gateway.in.th,15.5163,103.194&lt;br/&gt;vault.iris.to,43.6532,-79.3832&lt;br/&gt;strfry.bonsai.com,37.8716,-122.273&lt;br/&gt;ribo.eu.nostria.app,52.3676,4.90414&lt;br/&gt;relay.wellorder.net,45.5201,-122.99&lt;br/&gt;relay.tapestry.ninja,40.8054,-74.0241&lt;br/&gt;relay.dwadziesciajeden.pl,52.2297,21.0122&lt;br/&gt;relay.satlantis.io,32.8769,-80.0114&lt;br/&gt;nostr.pbfs.io,50.4754,12.3683&lt;br/&gt;freelay.sovbit.host,64.1476,-21.9392&lt;br/&gt;articles.layer3.news,37.3387,-121.885&lt;br/&gt;nostr.na.social,43.6532,-79.3832&lt;br/&gt;relay.fountain.fm,43.6532,-79.3832&lt;br/&gt;dev.relay.stream,43.6532,-79.3832&lt;br/&gt;nostr.n7ekb.net,36.1527,-95.9902&lt;br/&gt;relay5.bitransfer.org,43.6532,-79.3832&lt;br/&gt;relay.og.coop,43.6532,-79.3832&lt;br/&gt;nostr-server-production.up.railway.app,45.5019,-73.5674&lt;br/&gt;bucket.coracle.social,37.7775,-122.397&lt;br/&gt;relay.gulugulu.moe,43.6532,-79.3832&lt;br/&gt;relay.nostr-check.me,43.6532,-79.3832&lt;br/&gt;nostr.faultables.net,43.6532,-79.3832&lt;br/&gt;strfry.openhoofd.nl,51.9229,4.40833&lt;br/&gt;nostr.rblb.it:7777,43.7094,10.6582&lt;br/&gt;relay.nostrcheck.me,43.6532,-79.3832&lt;br/&gt;0x-nostr-relay.fly.dev,48.8575,2.35138&lt;br/&gt;nostr.thalheim.io,60.1699,24.9384&lt;br/&gt;relay-nl.zombi.cloudrodion.com,50.8943,6.06237&lt;br/&gt;relay.shadowbip.com,51.5072,-0.127586&lt;br/&gt;nostr-relay.corb.net,38.8353,-104.822&lt;br/&gt;purplerelay.com,43.6532,-79.3832&lt;br/&gt;nostr-pub.wellorder.net,45.5201,-122.99&lt;br/&gt;herbstmeister.com,34.0549,-118.243&lt;br/&gt;nostrcheck.me,43.6532,-79.3832&lt;br/&gt;pyramid.nostr.technology,52.3947,4.66399&lt;br/&gt;nostr.spicyz.io,43.6532,-79.3832&lt;br/&gt;nrs-02.darkcloudarcade.com,39.9526,-75.1652&lt;br/&gt;nestr.nedao.ch,47.0151,6.98832&lt;br/&gt;nostr.nodesmap.com,59.3327,18.0656&lt;br/&gt;nittom.nostr1.com,38.6327,-90.1961&lt;br/&gt;public.crostr.com,43.6532,-79.3832&lt;br/&gt;relay.cypherflow.ai,48.8575,2.35138&lt;br/&gt;nostr.bitczat.pl,60.1699,24.9384&lt;br/&gt;relayone.geektank.ai,39.1008,-94.5811&lt;br/&gt;testrelay.era21.space,43.6532,-79.3832&lt;br/&gt;relay.npubhaus.com,43.6532,-79.3832&lt;br/&gt;relay.bitmacro.io,48.8566,2.35222&lt;br/&gt;nostr.data.haus,50.4754,12.3683&lt;br/&gt;relay.credenso.cafe,43.3601,-80.3127&lt;br/&gt;relay.ru.ac.th,13.7607,100.627&lt;br/&gt;relay-fra.zombi.cloudrodion.com,48.8566,2.35222&lt;br/&gt;nostr.chaima.info,50.1109,8.68213&lt;br/&gt;nostr.mom,50.4754,12.3683
    </content>
    <updated>2026-04-06T04:16:49Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsfkj9jxx09q7kj2gtzv0u25pdynqxhkvlkknm3ec7t289jpdckx9cpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5f7jp02</id>
    
      <title type="html">#[cfg(feature = &amp;#34;nostr&amp;#34;)] use serde_json::to_string; use ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsfkj9jxx09q7kj2gtzv0u25pdynqxhkvlkknm3ec7t289jpdckx9cpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5f7jp02" />
    <content type="html">
      #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;use serde_json::to_string;&lt;br/&gt;use std::process::Command;&lt;br/&gt;use std::path::PathBuf;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;use nostr_sdk::prelude::{*, EventBuilder, Tag, Kind};&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;use serde_json::json;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;use csv::ReaderBuilder;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;use ::url::Url;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub use frost_secp256k1_tr as frost;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;use frost::keys::{KeyPackage, PublicKeyPackage, SecretShare};&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;use frost::round1::{SigningCommitments, SigningNonces};&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;use frost::round2::SignatureShare;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;use frost::SigningPackage;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;use rand::thread_rng;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub use frost_secp256k1_tr as frost_bip340;&lt;br/&gt;&lt;br/&gt;pub mod frost_mailbox_logic;&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;use std::collections::BTreeMap;&lt;br/&gt;&lt;br/&gt;pub const DUMMY_BUILD_MANIFEST_ID_STR: &amp;amp;str = &amp;#34;f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0&amp;#34;;&lt;br/&gt;pub const DEFAULT_GNOSTR_KEY: &amp;amp;str = &amp;#34;e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&amp;#34;;&lt;br/&gt;pub const DEFAULT_PICTURE_URL: &amp;amp;str = &amp;#34;&lt;a href=&#34;https://avatars.githubusercontent.com/u/135379339?s=400&amp;amp;u=11cb72cccbc2b13252867099546074c50caef1ae&amp;amp;v=4&amp;#34&#34;&gt;https://avatars.githubusercontent.com/u/135379339?s=400&amp;amp;u=11cb72cccbc2b13252867099546074c50caef1ae&amp;amp;v=4&amp;#34&lt;/a&gt;;;&lt;br/&gt;pub const DEFAULT_BANNER_URL: &amp;amp;str = &amp;#34;&lt;a href=&#34;https://raw.githubusercontent.com/gnostr-org/gnostr-icons/refs/heads/master/banner/1024x341.png&amp;#34&#34;&gt;https://raw.githubusercontent.com/gnostr-org/gnostr-icons/refs/heads/master/banner/1024x341.png&amp;#34&lt;/a&gt;;;&lt;br/&gt;&lt;br/&gt;pub const EMPTY_BLOB_SHA1: &amp;amp;str = &amp;#34;e69de29bb2d1d6434b8b29ae775ad8c2e48c5391&amp;#34;;&lt;br/&gt;pub const EMPTY_BLOB_SHA256: &amp;amp;str = &amp;#34;473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813&amp;#34;;&lt;br/&gt;pub const EMPTY_BLOB_PRIVATE_KEY_NSEC: &amp;amp;str = &amp;#34;nsec1guaq7npmaz5ndqdzvl3mr6d8mndprp2rdls5ram5jys2xqmjrqfsdzhrp6&amp;#34;;&lt;br/&gt;&lt;br/&gt;pub const EMPTY_TREE_SHA1: &amp;amp;str = &amp;#34;4b825dc642cb6eb9a060e54bf8d69288fbee4904&amp;#34;;&lt;br/&gt;pub const EMPTY_TREE_SHA256: &amp;amp;str = &amp;#34;6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321&amp;#34;;&lt;br/&gt;pub const EMPTY_TREE_PRIVATE_KEY_NSEC: &amp;amp;str = &amp;#34;nsec1dmceksfzt3fknuwpqn29mrv9a75mq4a48v2tfwde88whfhkv2vsslsc46c&amp;#34;;&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;const ONLINE_RELAYS_GPS_CSV: &amp;amp;[u8] = include_bytes!(&amp;#34;online_relays_gps.csv&amp;#34;);&lt;br/&gt;&lt;br/&gt;/// BIP-64MOD &#43; GCC: Complete NIP-19 Identity Mapping&lt;br/&gt;/// &lt;br/&gt;/// These constants provide the Bech32 encoded Private (NSEC) and &lt;br/&gt;/// Public (NPUB) keys for Git-standard empty states.&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub struct GitEmptyIdentity;&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;impl GitEmptyIdentity {&lt;br/&gt;    // === EMPTY BLOB IDENTITY ===&lt;br/&gt;    // Derived from the identity of a 0-byte file.&lt;br/&gt;    pub const BLOB_NSEC: &amp;amp;&amp;#39;static str = &amp;#34;nsec1guaq7npmaz5ndqdzvl3mr6d8mndprp2rdls5ram5jys2xqmjrqfsdzhrp6&amp;#34;;&lt;br/&gt;    pub const BLOB_NPUB: &amp;amp;&amp;#39;static str = &amp;#34;npub180cvv07tjdrghvkyh6964p7w9vsqpf3p05868v399v86p8y6f69sq5fdp0&amp;#34;;&lt;br/&gt;    pub const BLOB_HEX:  &amp;amp;&amp;#39;static str = &amp;#34;473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813&amp;#34;;&lt;br/&gt;&lt;br/&gt;    // === EMPTY TREE IDENTITY ===&lt;br/&gt;    // Derived from the identity of an empty directory.&lt;br/&gt;    pub const TREE_NSEC: &amp;amp;&amp;#39;static str = &amp;#34;nsec1dmceksfzt3fknuwpqn29mrv9a75mq4a48v2tfwde88whfhkv2vsslsc46c&amp;#34;;&lt;br/&gt;    pub const TREE_NPUB: &amp;amp;&amp;#39;static str = &amp;#34;npub1pxmpep6yk7z6p332u9588k0vscg26rv29pynvscg26rv29pynvsq6erdfh&amp;#34;;&lt;br/&gt;    pub const TREE_HEX:  &amp;amp;&amp;#39;static str = &amp;#34;6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321&amp;#34;;&lt;br/&gt;&lt;br/&gt;    // === NULL / GENESIS IDENTITY ===&lt;br/&gt;    // Often used for the &amp;#39;System&amp;#39; or &amp;#39;Root&amp;#39; user in a new GCC chain.&lt;br/&gt;    // Derived from 32-bytes of zeros.&lt;br/&gt;    pub const NULL_NSEC: &amp;amp;&amp;#39;static str = &amp;#34;nsec1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqp3994m&amp;#34;;&lt;br/&gt;    pub const NULL_NPUB: &amp;amp;&amp;#39;static str = &amp;#34;npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqad8gh8&amp;#34;;&lt;br/&gt;    pub const NULL_HEX:  &amp;amp;&amp;#39;static str = &amp;#34;0000000000000000000000000000000000000000000000000000000000000000&amp;#34;;&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Example usage for signature verification logic&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub mod git_empty_state {&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;	use crate::GitEmptyIdentity;&lt;br/&gt;&lt;br/&gt;    /// Returns the expected public key for a given Git object hash.&lt;br/&gt;    /// Useful for automated verification of &amp;#39;Empty State&amp;#39; transitions.&lt;br/&gt;    pub fn get_expected_npub(git_hash: &amp;amp;str) -&amp;gt; Option&amp;lt;&amp;amp;&amp;#39;static str&amp;gt; {&lt;br/&gt;        match git_hash {&lt;br/&gt;            GitEmptyIdentity::BLOB_HEX =&amp;gt; Some(GitEmptyIdentity::BLOB_NPUB),&lt;br/&gt;            GitEmptyIdentity::TREE_HEX =&amp;gt; Some(GitEmptyIdentity::TREE_NPUB),&lt;br/&gt;            GitEmptyIdentity::NULL_HEX =&amp;gt; Some(GitEmptyIdentity::NULL_NPUB),&lt;br/&gt;            _ =&amp;gt; None,&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub fn get_relay_urls() -&amp;gt; Vec&amp;lt;String&amp;gt; {&lt;br/&gt;    let content = String::from_utf8_lossy(ONLINE_RELAYS_GPS_CSV);&lt;br/&gt;    let mut rdr = ReaderBuilder::new()&lt;br/&gt;        .has_headers(true)&lt;br/&gt;        .from_reader(content.as_bytes());&lt;br/&gt;&lt;br/&gt;    rdr.records()&lt;br/&gt;        .filter_map(|result| {&lt;br/&gt;            match result {&lt;br/&gt;                Ok(record) =&amp;gt; {&lt;br/&gt;                    record.get(0).and_then(|url_str| {&lt;br/&gt;                        let full_url_str = if url_str.contains(&amp;#34;://&amp;#34;) {&lt;br/&gt;                            url_str.to_string()&lt;br/&gt;                        } else {&lt;br/&gt;                            format!(&amp;#34;wss://{}&amp;#34;, url_str)&lt;br/&gt;                        };&lt;br/&gt;                        match Url::parse(&amp;amp;full_url_str) {&lt;br/&gt;                            Ok(url) if url.scheme() == &amp;#34;wss&amp;#34; =&amp;gt; Some(url.to_string()),&lt;br/&gt;                            _ =&amp;gt; {&lt;br/&gt;                                eprintln!(&amp;#34;Warning: Invalid or unsupported relay URL scheme: {}&amp;#34;, full_url_str);&lt;br/&gt;                                None&lt;br/&gt;                            }&lt;br/&gt;                        }&lt;br/&gt;                    })&lt;br/&gt;                },&lt;br/&gt;                Err(e) =&amp;gt; {&lt;br/&gt;                    eprintln!(&amp;#34;Error reading CSV record: {}&amp;#34;, e);&lt;br/&gt;                    None&lt;br/&gt;                }&lt;br/&gt;            }&lt;br/&gt;        })&lt;br/&gt;        .collect()&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;use std::io::Write;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;use std::fs;&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub fn should_remove_relay(error_msg: &amp;amp;str) -&amp;gt; bool {&lt;br/&gt;    error_msg.contains(&amp;#34;relay not connected&amp;#34;) ||&lt;br/&gt;    error_msg.contains(&amp;#34;not in web of trust&amp;#34;) ||&lt;br/&gt;    error_msg.contains(&amp;#34;blocked: not authorized&amp;#34;) ||&lt;br/&gt;    error_msg.contains(&amp;#34;timeout&amp;#34;) ||&lt;br/&gt;    error_msg.contains(&amp;#34;blocked: spam not permitted&amp;#34;) ||&lt;br/&gt;    error_msg.contains(&amp;#34;relay experienced an error trying to publish the latest event&amp;#34;) ||&lt;br/&gt;    error_msg.contains(&amp;#34;duplicate: event already broadcast&amp;#34;)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub fn write_event_json_to_file(&lt;br/&gt;    output_dir: &amp;amp;PathBuf,&lt;br/&gt;    filename: &amp;amp;str,&lt;br/&gt;    event: &amp;amp;Event,&lt;br/&gt;) -&amp;gt; Option&amp;lt;()&amp;gt; {&lt;br/&gt;    let file_path = output_dir.join(filename);&lt;br/&gt;    if let Some(parent) = file_path.parent() {&lt;br/&gt;        if let Err(e) = fs::create_dir_all(parent) {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to create parent directories for {}: {}&amp;#34;, file_path.display(), e);&lt;br/&gt;            return None;&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;    if let Err(e) = fs::File::create(&amp;amp;file_path).and_then(|mut file| write!(file, &amp;#34;{}&amp;#34;, event.as_json())) {&lt;br/&gt;        println!(&amp;#34;cargo:warning=Failed to write event JSON to file {}: {}&amp;#34;, file_path.display(), e);&lt;br/&gt;        None&lt;br/&gt;    } else {&lt;br/&gt;        println!(&amp;#34;Successfully wrote event JSON to {}&amp;#34;, file_path.display());&lt;br/&gt;        Some(())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub async fn publish_nostr_event_if_release(&lt;br/&gt;    client: &amp;amp;mut nostr_sdk::Client,&lt;br/&gt;    hash: String,&lt;br/&gt;    keys: Keys,&lt;br/&gt;    event_builder: EventBuilder,&lt;br/&gt;    _relay_urls: &amp;amp;mut Vec&amp;lt;String&amp;gt;,&lt;br/&gt;    file_path_str: &amp;amp;str,&lt;br/&gt;    output_dir: &amp;amp;PathBuf,&lt;br/&gt;    total_bytes_sent: &amp;amp;mut usize,&lt;br/&gt;) -&amp;gt; Option&amp;lt;EventId&amp;gt; {&lt;br/&gt;    let public_key = keys.public_key().to_string();&lt;br/&gt;&lt;br/&gt;    let event = client.sign_event_builder(event_builder).await.unwrap();&lt;br/&gt;&lt;br/&gt;    match client.send_event(&amp;amp;event).await {        Ok(event_output) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Published Nostr event for {}: {}&amp;#34;, file_path_str, event_output.val);&lt;br/&gt;&lt;br/&gt;            let event_json_size = to_string(&amp;amp;event).map(|s| s.as_bytes().len()).unwrap_or(0);&lt;br/&gt;            // Print successful relays&lt;br/&gt;            for relay_url in event_output.success.iter() {&lt;br/&gt;                println!(&amp;#34;cargo:warning=Successfully published to relay: {} ({} bytes)&amp;#34;, relay_url, event_json_size);&lt;br/&gt;                *total_bytes_sent &#43;= event_json_size;&lt;br/&gt;            }&lt;br/&gt;            // Print failed relays and remove &amp;#34;unfriendly&amp;#34; relays from the list&lt;br/&gt;            for (relay_url, error_msg) in event_output.failed.iter() {&lt;br/&gt;                if should_remove_relay(error_msg) {&lt;br/&gt;                    if let Err(e) = client.remove_relay(relay_url).await {&lt;br/&gt;                        println!(&amp;#34;cargo:warning=Failed to remove relay {}: {}&amp;#34;, relay_url, e);&lt;br/&gt;                    }&lt;br/&gt;                     // println!(&amp;#34;cargo:warning=Removed relay {}&amp;#34;, relay_url);&lt;br/&gt;                }&lt;br/&gt;            }&lt;br/&gt;&lt;br/&gt;            let filename = format!(&amp;#34;{}/{}/{}/{}.json&amp;#34;, file_path_str, hash, public_key.clone(), event_output.val.to_string());&lt;br/&gt;            write_event_json_to_file(output_dir, &amp;amp;filename, &amp;amp;event);&lt;br/&gt;            Some(event_output.val)&lt;br/&gt;        },&lt;br/&gt;        Err(e) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to publish Nostr event for {}: {}&amp;#34;, file_path_str, e);&lt;br/&gt;            None&lt;br/&gt;        },&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub async fn get_repo_announcement_event(&lt;br/&gt;    client: &amp;amp;mut nostr_sdk::Client,&lt;br/&gt;    _keys: &amp;amp;Keys,&lt;br/&gt;    relay_urls: &amp;amp;Vec&amp;lt;String&amp;gt;,&lt;br/&gt;    repo_url: &amp;amp;str,&lt;br/&gt;    repo_name: &amp;amp;str,&lt;br/&gt;    repo_description: &amp;amp;str,&lt;br/&gt;    git_commit_hash: &amp;amp;str,&lt;br/&gt;    git_branch: &amp;amp;str,&lt;br/&gt;    output_dir: &amp;amp;PathBuf,&lt;br/&gt;    public_key_hex: &amp;amp;str,&lt;br/&gt;) -&amp;gt; Option&amp;lt;EventId&amp;gt; {&lt;br/&gt;&lt;br/&gt;    let mut tags = vec![&lt;br/&gt;        Tag::parse([&amp;#34;d&amp;#34;, repo_name].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;        Tag::parse([&amp;#34;name&amp;#34;, repo_name].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;        Tag::parse([&amp;#34;description&amp;#34;, repo_description].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;        Tag::parse([&amp;#34;web&amp;#34;, repo_url].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;        Tag::parse([&amp;#34;clone&amp;#34;, repo_url].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;        Tag::parse([&amp;#34;r&amp;#34;, git_commit_hash, &amp;#34;euc&amp;#34;].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;        Tag::parse([&amp;#34;commit&amp;#34;, git_commit_hash].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;        Tag::parse([&amp;#34;branch&amp;#34;, git_branch].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;        Tag::parse([&amp;#34;maintainers&amp;#34;, &amp;#34;gnostr&amp;#34;].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;        //Tag::parse([&amp;#34;t&amp;#34;, &amp;#34;personal-fork&amp;#34;].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;        Tag::parse([&amp;#34;t&amp;#34;, &amp;#34;gnostr&amp;#34;].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;        Tag::parse([&amp;#34;t&amp;#34;, repo_name].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;    ];&lt;br/&gt;&lt;br/&gt;    // Append each relay url&lt;br/&gt;    for relay in relay_urls {&lt;br/&gt;        tags.push(Tag::parse([&amp;#34;relays&amp;#34;, relay].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap());&lt;br/&gt;    }&lt;br/&gt;    let event_builder = EventBuilder::new(Kind::Custom(30617), repo_description).tags(tags);&lt;br/&gt;    let event = client.sign_event_builder(event_builder).await.unwrap();&lt;br/&gt;&lt;br/&gt;    match client.send_event(&amp;amp;event).await {&lt;br/&gt;        Ok(event_output) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Published Nostr Repository Announcement for {}: {}&amp;#34;, repo_name, event_output.val);&lt;br/&gt;            &lt;br/&gt;            let filename = format!(&amp;#34;30617/{}/{}/{}.json&amp;#34;, repo_name, public_key_hex, event_output.val.to_string());&lt;br/&gt;            write_event_json_to_file(output_dir, &amp;amp;filename, &amp;amp;event);&lt;br/&gt;            Some(event_output.val)&lt;br/&gt;        },&lt;br/&gt;        Err(e) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to publish Nostr Repository Announcement for {}: {}&amp;#34;, repo_name, e);&lt;br/&gt;            None&lt;br/&gt;        },&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub async fn publish_repo_patch_event(&lt;br/&gt;    client: &amp;amp;mut nostr_sdk::Client,&lt;br/&gt;    _keys: &amp;amp;Keys,&lt;br/&gt;    _relay_urls: &amp;amp;Vec&amp;lt;String&amp;gt;,&lt;br/&gt;    repo_url: &amp;amp;str,&lt;br/&gt;    repo_name: &amp;amp;str,&lt;br/&gt;    repo_description: &amp;amp;str,&lt;br/&gt;    git_commit_hash: &amp;amp;str,&lt;br/&gt;    git_branch: &amp;amp;str,&lt;br/&gt;    output_dir: &amp;amp;PathBuf,&lt;br/&gt;    public_key_hex: &amp;amp;str,&lt;br/&gt;) -&amp;gt; Option&amp;lt;EventId&amp;gt; {&lt;br/&gt;&lt;br/&gt;    let tags = vec![&lt;br/&gt;        Tag::parse([&amp;#34;r&amp;#34;, repo_url].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;        Tag::parse([&amp;#34;name&amp;#34;, repo_name].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;        Tag::parse([&amp;#34;description&amp;#34;, repo_description].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;        Tag::parse([&amp;#34;commit&amp;#34;, git_commit_hash].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;        Tag::parse([&amp;#34;branch&amp;#34;, git_branch].iter().map(ToString::to_string).collect::&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;()).unwrap(),&lt;br/&gt;    ];&lt;br/&gt;&lt;br/&gt;    let event_builder = EventBuilder::new(Kind::Custom(1617), repo_description).tags(tags);&lt;br/&gt;    let event = client.sign_event_builder(event_builder).await.unwrap();&lt;br/&gt;&lt;br/&gt;    match client.send_event(&amp;amp;event).await {&lt;br/&gt;        Ok(event_output) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Published Nostr Repository Announcement for {}: {}&amp;#34;, repo_name, event_output.val);&lt;br/&gt;            &lt;br/&gt;            let filename = format!(&amp;#34;30617/{}/{}/{}.json&amp;#34;, repo_name, public_key_hex, event_output.val.to_string());&lt;br/&gt;            write_event_json_to_file(output_dir, &amp;amp;filename, &amp;amp;event);&lt;br/&gt;            Some(event_output.val)&lt;br/&gt;        },&lt;br/&gt;        Err(e) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to publish Nostr Repository Announcement for {}: {}&amp;#34;, repo_name, e);&lt;br/&gt;            None&lt;br/&gt;        },&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Computes the SHA-256 hash of the specified file at compile time.&lt;br/&gt;///&lt;br/&gt;/// This macro takes a string literal representing a file path, reads the file&amp;#39;s bytes&lt;br/&gt;/// at compile time, computes its SHA-256 hash, and returns the hash as a hex-encoded `String`.&lt;br/&gt;///&lt;br/&gt;/// # Examples&lt;br/&gt;///&lt;br/&gt;/// ```rust&lt;br/&gt;/// use get_file_hash_core::get_file_hash;&lt;br/&gt;/// use sha2::{Digest, Sha256};&lt;br/&gt;///&lt;br/&gt;/// let hash = get_file_hash!(&amp;#34;lib.rs&amp;#34;);&lt;br/&gt;/// println!(&amp;#34;Hash: {}&amp;#34;, hash);&lt;br/&gt;/// ```&lt;br/&gt;&lt;br/&gt;#[macro_export]&lt;br/&gt;macro_rules! get_file_hash {&lt;br/&gt;    ($file_path:expr) =&amp;gt; {{&lt;br/&gt;        let bytes = include_bytes!($file_path);&lt;br/&gt;        let mut hasher = Sha256::new();&lt;br/&gt;        hasher.update(bytes);&lt;br/&gt;        let result = hasher.finalize();&lt;br/&gt;&lt;br/&gt;        // Convert the GenericArray to a hex string&lt;br/&gt;        result&lt;br/&gt;            .iter()&lt;br/&gt;            .map(|b| format!(&amp;#34;{:02x}&amp;#34;, b))&lt;br/&gt;            .collect::&amp;lt;String&amp;gt;()&lt;br/&gt;    }};&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Computes the SHA-256 hash of the specified file at compile time and uses it as a Nostr private key.&lt;br/&gt;///&lt;br/&gt;/// This macro takes a string literal representing a file path, computes its SHA-256 hash,&lt;br/&gt;/// and returns a `nostr::Keys` object derived from this hash.&lt;br/&gt;///&lt;br/&gt;/// # Examples&lt;br/&gt;///&lt;br/&gt;/// ```rust&lt;br/&gt;/// use get_file_hash_core::file_hash_as_nostr_private_key;&lt;br/&gt;/// use sha2::{Digest, Sha256};&lt;br/&gt;/// use nostr_sdk::prelude::ToBech32;&lt;br/&gt;///&lt;br/&gt;/// let keys = file_hash_as_nostr_private_key!(&amp;#34;lib.rs&amp;#34;);&lt;br/&gt;/// println!(&amp;#34;Public Key: {}&amp;#34;, keys.public_key().to_bech32().unwrap());&lt;br/&gt;/// ```&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;#[macro_export]&lt;br/&gt;macro_rules! file_hash_as_nostr_private_key {&lt;br/&gt;    ($file_path:expr) =&amp;gt; {{&lt;br/&gt;        let hash_hex = $crate::get_file_hash!($file_path);&lt;br/&gt;        nostr_sdk::Keys::parse(&amp;amp;hash_hex).expect(&amp;#34;Failed to create Nostr Keys from file hash&amp;#34;)&lt;br/&gt;    }};&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Publishes a NIP-34 repository announcement event to Nostr relays.&lt;br/&gt;///&lt;br/&gt;/// This macro takes Nostr keys, relay URLs, project details, a clone URL, and a file path.&lt;br/&gt;/// It computes the SHA-256 hash of the file at compile time to use as the &amp;#34;earliest unique commit&amp;#34; (EUC),&lt;br/&gt;/// and then publishes a Kind 30617 event.&lt;br/&gt;///&lt;br/&gt;/// # Examples&lt;br/&gt;///&lt;br/&gt;/// ```no_run&lt;br/&gt;/// use get_file_hash_core::repository_announcement;&lt;br/&gt;/// use get_file_hash_core::get_file_hash;&lt;br/&gt;/// use nostr_sdk::Keys;&lt;br/&gt;/// use sha2::{Digest, Sha256};&lt;br/&gt;///&lt;br/&gt;/// #[tokio::main]&lt;br/&gt;/// async fn main() {&lt;br/&gt;///     let keys = Keys::generate();&lt;br/&gt;///     let relay_urls = vec![&amp;#34;wss://relay.damus.io&amp;#34;.to_string()];&lt;br/&gt;///     let project_name = &amp;#34;my-awesome-repo&amp;#34;;&lt;br/&gt;///     let description = &amp;#34;A fantastic new project.&amp;#34;;&lt;br/&gt;///     let clone_url = &amp;#34;git@github.com:user/my-awesome-repo.git&amp;#34;;&lt;br/&gt;///&lt;br/&gt;///     repository_announcement!(&lt;br/&gt;///         &amp;amp;keys,&lt;br/&gt;///         &amp;amp;relay_urls,&lt;br/&gt;///         project_name,&lt;br/&gt;///         description,&lt;br/&gt;///         clone_url,&lt;br/&gt;///         &amp;#34;../Cargo.toml&amp;#34;, // Use a known file in your project&lt;br/&gt;///         None&lt;br/&gt;///     );&lt;br/&gt;/// }&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;#[macro_export]&lt;br/&gt;macro_rules! repository_announcement {&lt;br/&gt;    ($keys:expr, $relay_urls:expr, $project_name:expr, $description:expr, $clone_url:expr, $file_for_euc:expr) =&amp;gt; {{&lt;br/&gt;        let euc_hash = $crate::get_file_hash!($file_for_euc);&lt;br/&gt;        // The &amp;#39;d&amp;#39; tag value should be unique for the repository. Using the project_name for simplicity.&lt;br/&gt;        let d_tag_value = $project_name;&lt;br/&gt;        $crate::publish_repository_announcement_event(&lt;br/&gt;            $keys,&lt;br/&gt;            $relay_urls,&lt;br/&gt;            $project_name,&lt;br/&gt;            $description,&lt;br/&gt;            $clone_url,&lt;br/&gt;            &amp;amp;euc_hash,&lt;br/&gt;            d_tag_value,&lt;br/&gt;            None,&lt;br/&gt;        ).await;&lt;br/&gt;    }};&lt;br/&gt;    ($keys:expr, $relay_urls:expr, $project_name:expr, $description:expr, $clone_url:expr, $file_for_euc:expr, $build_manifest_event_id:expr) =&amp;gt; {{&lt;br/&gt;        let euc_hash = $crate::get_file_hash!($file_for_euc);&lt;br/&gt;        let d_tag_value = $project_name;&lt;br/&gt;        $crate::publish_repository_announcement_event(&lt;br/&gt;            $keys,&lt;br/&gt;            $relay_urls,&lt;br/&gt;            $project_name,&lt;br/&gt;            $description,&lt;br/&gt;            $clone_url,&lt;br/&gt;            &amp;amp;euc_hash,&lt;br/&gt;            d_tag_value,&lt;br/&gt;            $build_manifest_event_id, // Correct: Pass directly&lt;br/&gt;        ).await;&lt;br/&gt;    }};&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Publishes a NIP-34 patch event to Nostr relays.&lt;br/&gt;///&lt;br/&gt;/// This macro takes Nostr keys, relay URLs, the repository&amp;#39;s d-tag value,&lt;br/&gt;/// the commit ID the patch applies to, and the path to the patch file.&lt;br/&gt;/// The content of the patch file is included directly in the event.&lt;br/&gt;///&lt;br/&gt;/// # Examples&lt;br/&gt;///&lt;br/&gt;/// ```no_run&lt;br/&gt;/// use get_file_hash_core::publish_patch;&lt;br/&gt;/// use nostr_sdk::Keys;&lt;br/&gt;///&lt;br/&gt;/// #[tokio::main]&lt;br/&gt;/// async fn main() {&lt;br/&gt;///     let keys = Keys::generate();&lt;br/&gt;///     let relay_urls = vec![&amp;#34;wss://relay.damus.io&amp;#34;.to_string()];&lt;br/&gt;///     let d_tag = &amp;#34;my-awesome-repo&amp;#34;;&lt;br/&gt;///     let commit_id = &amp;#34;a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0&amp;#34;; // Example commit ID&lt;br/&gt;///&lt;br/&gt;///     publish_patch!(&lt;br/&gt;///         &amp;amp;keys,&lt;br/&gt;///         &amp;amp;relay_urls,&lt;br/&gt;///         d_tag,&lt;br/&gt;///         commit_id,&lt;br/&gt;///         &amp;#34;lib.rs&amp;#34; // Use an existing file for the patch content&lt;br/&gt;///     );&lt;br/&gt;/// }&lt;br/&gt;/// ```&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;#[macro_export]&lt;br/&gt;macro_rules! publish_patch {&lt;br/&gt;    ($keys:expr, $relay_urls:expr, $d_tag_value:expr, $commit_id:expr, $patch_file_path:expr) =&amp;gt; {{&lt;br/&gt;        let patch_content = include_str!($patch_file_path);&lt;br/&gt;        $crate::publish_patch_event(&lt;br/&gt;            $keys,&lt;br/&gt;            $relay_urls,&lt;br/&gt;            $d_tag_value,&lt;br/&gt;            $commit_id,&lt;br/&gt;            patch_content,&lt;br/&gt;            None, // Pass None for build_manifest_event_id&lt;br/&gt;        ).await;&lt;br/&gt;    }};&lt;br/&gt;    ($keys:expr, $relay_urls:expr, $d_tag_value:expr, $commit_id:expr, $patch_file_path:expr, $build_manifest_event_id:expr) =&amp;gt; {{&lt;br/&gt;        let patch_content = include_str!($patch_file_path);&lt;br/&gt;        $crate::publish_patch_event(&lt;br/&gt;            $keys,&lt;br/&gt;            $relay_urls,&lt;br/&gt;            $d_tag_value,&lt;br/&gt;            $commit_id,&lt;br/&gt;            patch_content,&lt;br/&gt;            $build_manifest_event_id, // Pass directly, macro arg should be Option&amp;lt;&amp;amp;EventId&amp;gt;&lt;br/&gt;        ).await;&lt;br/&gt;    }};&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Publishes a NIP-34 pull request event to Nostr relays.&lt;br/&gt;///&lt;br/&gt;/// This macro takes Nostr keys, relay URLs, the repository&amp;#39;s d-tag value,&lt;br/&gt;/// the commit ID of the pull request, a clone URL where the work can be fetched,&lt;br/&gt;/// and an optional title for the pull request.&lt;br/&gt;///&lt;br/&gt;/// # Examples&lt;br/&gt;///&lt;br/&gt;/// ```no_run&lt;br/&gt;/// use get_file_hash_core::publish_pull_request;&lt;br/&gt;/// use nostr_sdk::Keys;&lt;br/&gt;///&lt;br/&gt;/// #[tokio::main]&lt;br/&gt;/// async fn main() {&lt;br/&gt;///     let keys = Keys::generate();&lt;br/&gt;///     let relay_urls = vec![&amp;#34;wss://relay.damus.io&amp;#34;.to_string()];&lt;br/&gt;///     let d_tag = &amp;#34;my-awesome-repo&amp;#34;;&lt;br/&gt;///     let commit_id = &amp;#34;a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0&amp;#34;;&lt;br/&gt;///     let clone_url = &amp;#34;git@github.com:user/my-feature-branch.git&amp;#34;;&lt;br/&gt;///     let title = Some(&amp;#34;Feat: Add new awesome feature&amp;#34;);&lt;br/&gt;///&lt;br/&gt;///     publish_pull_request!(&lt;br/&gt;///         &amp;amp;keys,&lt;br/&gt;///         &amp;amp;relay_urls,&lt;br/&gt;///         d_tag,&lt;br/&gt;///         commit_id,&lt;br/&gt;///         clone_url,&lt;br/&gt;///         title,&lt;br/&gt;///         None&lt;br/&gt;///     );&lt;br/&gt;/// }&lt;br/&gt;/// ```&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;#[macro_export]&lt;br/&gt;macro_rules! publish_pull_request {&lt;br/&gt;    // 5 args: No title, no build_manifest_event_id&lt;br/&gt;    ($keys:expr, $relay_urls:expr, $d_tag_value:expr, $commit_id:expr, $clone_url:expr) =&amp;gt; {{&lt;br/&gt;        $crate::publish_pull_request_event(&lt;br/&gt;            $keys, $relay_urls, $d_tag_value, $commit_id, $clone_url,&lt;br/&gt;            None, // title: Option&amp;lt;&amp;amp;str&amp;gt;&lt;br/&gt;            None, // build_manifest_event_id: Option&amp;lt;&amp;amp;EventId&amp;gt;&lt;br/&gt;        ).await;&lt;br/&gt;    }};&lt;br/&gt;&lt;br/&gt;    // 6 args: With title (Option&amp;lt;&amp;amp;str&amp;gt;), no build_manifest_event_id&lt;br/&gt;    ($keys:expr, $relay_urls:expr, $d_tag_value:expr, $commit_id:expr, $clone_url:expr, $title:expr) =&amp;gt; {{&lt;br/&gt;        $crate::publish_pull_request_event(&lt;br/&gt;            $keys, $relay_urls, $d_tag_value, $commit_id, $clone_url,&lt;br/&gt;            $title, // title: Option&amp;lt;&amp;amp;str&amp;gt;&lt;br/&gt;            None, // build_manifest_event_id: Option&amp;lt;&amp;amp;EventId&amp;gt;&lt;br/&gt;        ).await;&lt;br/&gt;    }};&lt;br/&gt;&lt;br/&gt;    // 7 args: With title (Option&amp;lt;&amp;amp;str&amp;gt;), with build_manifest_event_id (Option&amp;lt;&amp;amp;EventId&amp;gt;)&lt;br/&gt;    // This needs to be before the 6-arg arm that passes a single Option for build_manifest_event_id if it&amp;#39;s not None.&lt;br/&gt;    ($keys:expr, $relay_urls:expr, $d_tag_value:expr, $commit_id:expr, $clone_url:expr, $title:expr, $build_manifest_event_id:expr) =&amp;gt; {{&lt;br/&gt;        $crate::publish_pull_request_event(&lt;br/&gt;            $keys, $relay_urls, $d_tag_value, $commit_id, $clone_url,&lt;br/&gt;            $title, // title: Option&amp;lt;&amp;amp;str&amp;gt;&lt;br/&gt;            $build_manifest_event_id, // build_manifest_event_id: Option&amp;lt;&amp;amp;EventId&amp;gt;&lt;br/&gt;        ).await;&lt;br/&gt;    }};&lt;br/&gt;&lt;br/&gt;    // 6 args: No title, with build_manifest_event_id (Option&amp;lt;&amp;amp;EventId&amp;gt;)&lt;br/&gt;    // This must be after the 7-arg arm to avoid ambiguity.&lt;br/&gt;    // The example needs to explicitly pass None for title.&lt;br/&gt;    ($keys:expr, $relay_urls:expr, $d_tag_value:expr, $commit_id:expr, $clone_url:expr, _none_title:tt, $build_manifest_event_id:expr) =&amp;gt; {{ // _none_title as tt to match None&lt;br/&gt;        $crate::publish_pull_request_event(&lt;br/&gt;            $keys, $relay_urls, $d_tag_value, $commit_id, $clone_url,&lt;br/&gt;            None, // title: Option&amp;lt;&amp;amp;str&amp;gt;&lt;br/&gt;            $build_manifest_event_id, // build_manifest_event_id: Option&amp;lt;&amp;amp;EventId&amp;gt;&lt;br/&gt;        ).await;&lt;br/&gt;    }};&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Publishes a NIP-34 PR update event to Nostr relays.&lt;br/&gt;///&lt;br/&gt;/// This macro takes Nostr keys, relay URLs, the repository&amp;#39;s d-tag value,&lt;br/&gt;/// the event ID of the original pull request, the new commit ID,&lt;br/&gt;/// and the new clone URL.&lt;br/&gt;///&lt;br/&gt;/// # Examples&lt;br/&gt;///&lt;br/&gt;/// ```no_run&lt;br/&gt;/// use get_file_hash_core::publish_pr_update;&lt;br/&gt;/// use nostr_sdk::Keys;&lt;br/&gt;/// use nostr_sdk::EventId;&lt;br/&gt;/// use std::str::FromStr;&lt;br/&gt;///&lt;br/&gt;/// #[tokio::main]&lt;br/&gt;/// async fn main() {&lt;br/&gt;///     let keys = Keys::generate();&lt;br/&gt;///     let relay_urls = vec![&amp;#34;wss://relay.damus.io&amp;#34;.to_string()];&lt;br/&gt;///     let d_tag = &amp;#34;my-awesome-repo&amp;#34;;&lt;br/&gt;///     let pr_event_id = EventId::from_str(&amp;#34;f6e4d6a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9&amp;#34;).unwrap(); // Example PR Event ID&lt;br/&gt;///     let updated_commit_id = &amp;#34;z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5k4j3i2h1g0&amp;#34;;&lt;br/&gt;///     let updated_clone_url = &amp;#34;git@github.com:user/my-feature-branch-v2.git&amp;#34;;&lt;br/&gt;///&lt;br/&gt;///     publish_pr_update!(&lt;br/&gt;///         &amp;amp;keys,&lt;br/&gt;///         &amp;amp;relay_urls,&lt;br/&gt;///         d_tag,&lt;br/&gt;///         &amp;amp;pr_event_id,&lt;br/&gt;///         updated_commit_id,&lt;br/&gt;///         updated_clone_url&lt;br/&gt;///     );&lt;br/&gt;/// }&lt;br/&gt;/// ```&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;#[macro_export]&lt;br/&gt;macro_rules! publish_pr_update {&lt;br/&gt;    ($keys:expr, $relay_urls:expr, $d_tag_value:expr, $pr_event_id:expr, $updated_commit_id:expr, $updated_clone_url:expr) =&amp;gt; {{&lt;br/&gt;        $crate::publish_pr_update_event(&lt;br/&gt;            $keys,&lt;br/&gt;            $relay_urls,&lt;br/&gt;            $d_tag_value,&lt;br/&gt;            $pr_event_id,&lt;br/&gt;            $updated_commit_id,&lt;br/&gt;            $updated_clone_url,&lt;br/&gt;            None, // Pass None for build_manifest_event_id&lt;br/&gt;        ).await;&lt;br/&gt;    }};&lt;br/&gt;    ($keys:expr, $relay_urls:expr, $d_tag_value:expr, $pr_event_id:expr, $updated_commit_id:expr, $updated_clone_url:expr, $build_manifest_event_id:expr) =&amp;gt; {{&lt;br/&gt;        $crate::publish_pr_update_event(&lt;br/&gt;            $keys,&lt;br/&gt;            $relay_urls,&lt;br/&gt;            $d_tag_value,&lt;br/&gt;            $pr_event_id,&lt;br/&gt;            $updated_commit_id,&lt;br/&gt;            $updated_clone_url,&lt;br/&gt;            $build_manifest_event_id,&lt;br/&gt;&lt;br/&gt;        ).await;&lt;br/&gt;    }};&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Publishes a NIP-34 repository state event to Nostr relays.&lt;br/&gt;///&lt;br/&gt;/// This macro takes Nostr keys, relay URLs, the repository&amp;#39;s d-tag value,&lt;br/&gt;/// the branch name, and the commit ID for that branch.&lt;br/&gt;///&lt;br/&gt;/// # Examples&lt;br/&gt;///&lt;br/&gt;/// ```no_run&lt;br/&gt;/// use get_file_hash_core::publish_repository_state;&lt;br/&gt;/// use nostr_sdk::Keys;&lt;br/&gt;///&lt;br/&gt;/// #[tokio::main]&lt;br/&gt;/// async fn main() {&lt;br/&gt;///     let keys = Keys::generate();&lt;br/&gt;///     let relay_urls = vec![&amp;#34;wss://relay.damus.io&amp;#34;.to_string()];&lt;br/&gt;///     let d_tag = &amp;#34;my-awesome-repo&amp;#34;;&lt;br/&gt;///     let branch_name = &amp;#34;main&amp;#34;;&lt;br/&gt;///     let commit_id = &amp;#34;a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0&amp;#34;;&lt;br/&gt;///&lt;br/&gt;///     publish_repository_state!(&lt;br/&gt;///         &amp;amp;keys,&lt;br/&gt;///         &amp;amp;relay_urls,&lt;br/&gt;///         d_tag,&lt;br/&gt;///         branch_name,&lt;br/&gt;///         commit_id&lt;br/&gt;///     );&lt;br/&gt;/// }&lt;br/&gt;/// ```&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;#[macro_export]&lt;br/&gt;macro_rules! publish_repository_state {&lt;br/&gt;    ($keys:expr, $relay_urls:expr, $d_tag_value:expr, $branch_name:expr, $commit_id:expr) =&amp;gt; {{&lt;br/&gt;        $crate::publish_repository_state_event(&lt;br/&gt;            $keys,&lt;br/&gt;            $relay_urls,&lt;br/&gt;            $d_tag_value,&lt;br/&gt;            $branch_name,&lt;br/&gt;            $commit_id,&lt;br/&gt;        ).await;&lt;br/&gt;    }};&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Publishes a NIP-34 issue event to Nostr relays.&lt;br/&gt;///&lt;br/&gt;/// This macro takes Nostr keys, relay URLs, the repository&amp;#39;s d-tag value,&lt;br/&gt;/// a unique issue ID, the issue&amp;#39;s title, and its content (markdown).&lt;br/&gt;///&lt;br/&gt;/// # Examples&lt;br/&gt;///&lt;br/&gt;/// ```no_run&lt;br/&gt;/// use get_file_hash_core::publish_issue;&lt;br/&gt;/// use nostr_sdk::Keys;&lt;br/&gt;///&lt;br/&gt;/// #[tokio::main]&lt;br/&gt;/// async fn main() {&lt;br/&gt;///     let keys = Keys::generate();&lt;br/&gt;///     let relay_urls = vec![&amp;#34;wss://relay.damus.io&amp;#34;.to_string()];&lt;br/&gt;///     let d_tag = &amp;#34;my-awesome-repo&amp;#34;;&lt;br/&gt;///     let issue_id = &amp;#34;123&amp;#34;;&lt;br/&gt;///     let title = &amp;#34;Bug: Fix authentication flow&amp;#34;;&lt;br/&gt;///     let content = &amp;#34;The authentication flow is currently broken when users try to log in with invalid credentials. It crashes instead of showing an error message.&amp;#34;;&lt;br/&gt;///&lt;br/&gt;///     publish_issue!(&lt;br/&gt;///         &amp;amp;keys,&lt;br/&gt;///         &amp;amp;relay_urls,&lt;br/&gt;///         d_tag,&lt;br/&gt;///         issue_id,&lt;br/&gt;///         title,&lt;br/&gt;///         content&lt;br/&gt;///     );&lt;br/&gt;/// }&lt;br/&gt;/// ```&lt;br/&gt;/// ```&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;#[macro_export]&lt;br/&gt;macro_rules! publish_issue {&lt;br/&gt;    ($keys:expr, $relay_urls:expr, $d_tag_value:expr, $issue_id:expr, $title:expr, $content:expr) =&amp;gt; {{&lt;br/&gt;        $crate::publish_issue_event(&lt;br/&gt;            $keys,&lt;br/&gt;            $relay_urls,&lt;br/&gt;            $d_tag_value,&lt;br/&gt;            $issue_id,&lt;br/&gt;            $title,&lt;br/&gt;            $content,&lt;br/&gt;            None, // Pass None for build_manifest_event_id&lt;br/&gt;        ).await;&lt;br/&gt;    }};&lt;br/&gt;    ($keys:expr, $relay_urls:expr, $d_tag_value:expr, $issue_id:expr, $title:expr, $content:expr, $build_manifest_event_id:expr) =&amp;gt; {{&lt;br/&gt;        $crate::publish_issue_event(&lt;br/&gt;            $keys,&lt;br/&gt;            $relay_urls,&lt;br/&gt;            $d_tag_value,&lt;br/&gt;            $issue_id,&lt;br/&gt;            $title,&lt;br/&gt;            $content,&lt;br/&gt;            $build_manifest_event_id, // Pass Option&amp;lt;&amp;amp;EventId&amp;gt; directly&lt;br/&gt;        ).await;&lt;br/&gt;    }};&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;pub fn get_git_tracked_files(dir: &amp;amp;PathBuf) -&amp;gt; Vec&amp;lt;String&amp;gt; {&lt;br/&gt;    match Command::new(&amp;#34;git&amp;#34;)&lt;br/&gt;        .arg(&amp;#34;ls-files&amp;#34;)&lt;br/&gt;        .current_dir(dir)&lt;br/&gt;        .stdout(std::process::Stdio::piped())&lt;br/&gt;        .stderr(std::process::Stdio::null())&lt;br/&gt;        .output()&lt;br/&gt;    {&lt;br/&gt;        Ok(output) if output.status.success() &amp;amp;&amp;amp; !output.stdout.is_empty() =&amp;gt; {&lt;br/&gt;            String::from_utf8_lossy(&amp;amp;output.stdout)&lt;br/&gt;                .lines()&lt;br/&gt;                .filter_map(|line| Some(String::from(line)))&lt;br/&gt;                .collect()&lt;br/&gt;        }&lt;br/&gt;        Ok(output) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=git ls-files failed or returned empty. Status: {:?}, Stderr: {}&amp;#34;, &lt;br/&gt;                     output.status, String::from_utf8_lossy(&amp;amp;output.stderr));&lt;br/&gt;            Vec::new()&lt;br/&gt;        }&lt;br/&gt;        Err(e) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to execute git ls-files: {}&amp;#34;, e);&lt;br/&gt;            Vec::new()&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub async fn publish_metadata_event(&lt;br/&gt;    keys: &amp;amp;Keys,&lt;br/&gt;    relay_urls: &amp;amp;[String],&lt;br/&gt;    picture_url: &amp;amp;str,&lt;br/&gt;    banner_url: &amp;amp;str,&lt;br/&gt;    file_path_str: &amp;amp;str,&lt;br/&gt;) {&lt;br/&gt;    let client = nostr_sdk::Client::new(keys.clone());&lt;br/&gt;&lt;br/&gt;    for relay_url in relay_urls {&lt;br/&gt;        if let Err(e) = client.add_relay(relay_url).await {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to add relay for metadata {}: {}&amp;#34;, relay_url, e);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;    client.connect().await;&lt;br/&gt;&lt;br/&gt;    let metadata_json = json!({&lt;br/&gt;        &amp;#34;picture&amp;#34;: picture_url,&lt;br/&gt;        &amp;#34;banner&amp;#34;: banner_url,&lt;br/&gt;        &amp;#34;name&amp;#34;: file_path_str,&lt;br/&gt;        &amp;#34;about&amp;#34;: format!(&amp;#34;Metadata for file event: {}&amp;#34;, file_path_str),&lt;br/&gt;    });&lt;br/&gt;&lt;br/&gt;    let metadata = serde_json::from_str::&amp;lt;nostr_sdk::Metadata&amp;gt;(&amp;amp;metadata_json.to_string())&lt;br/&gt;        .expect(&amp;#34;Failed to parse metadata JSON&amp;#34;);&lt;br/&gt;&lt;br/&gt;    match client.send_event_builder(EventBuilder::metadata(&amp;amp;metadata)).await {&lt;br/&gt;        Ok(_event_id) =&amp;gt; {&lt;br/&gt;            //println!(&amp;#34;cargo:warning=Published Nostr metadata event for {}: {:?}&amp;#34;, file_path_str, event_id);&lt;br/&gt;        }&lt;br/&gt;        Err(e) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to publish Nostr metadata event for {}: {}&amp;#34;, file_path_str, e);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub async fn publish_repository_announcement_event(&lt;br/&gt;    keys: &amp;amp;Keys,&lt;br/&gt;    relay_urls: &amp;amp;[String],&lt;br/&gt;    project_name: &amp;amp;str,&lt;br/&gt;    description: &amp;amp;str,&lt;br/&gt;    clone_url: &amp;amp;str,&lt;br/&gt;    euc: &amp;amp;str, // Earliest Unique Commit hash&lt;br/&gt;    d_tag_value: &amp;amp;str, // d-tag value&lt;br/&gt;    build_manifest_event_id: Option&amp;lt;&amp;amp;EventId&amp;gt;,&lt;br/&gt;) {&lt;br/&gt;    let client = nostr_sdk::Client::new(keys.clone());&lt;br/&gt;&lt;br/&gt;    for relay_url in relay_urls {&lt;br/&gt;        if let Err(e) = client.add_relay(relay_url).await {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to add relay for repository announcement {}: {}&amp;#34;, relay_url, e);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;    client.connect().await;&lt;br/&gt;&lt;br/&gt;    let mut tags = vec![&lt;br/&gt;        Tag::parse([&amp;#34;name&amp;#34;, project_name]).expect(&amp;#34;Failed to create name tag&amp;#34;),&lt;br/&gt;        Tag::parse([&amp;#34;description&amp;#34;, description]).expect(&amp;#34;Failed to create description tag&amp;#34;),&lt;br/&gt;        Tag::parse([&amp;#34;clone&amp;#34;, clone_url]).expect(&amp;#34;Failed to create clone tag&amp;#34;),&lt;br/&gt;        Tag::custom(&amp;#34;euc&amp;#34;.into(), vec![euc.to_string()]),&lt;br/&gt;        Tag::custom(&amp;#34;d&amp;#34;.into(), vec![d_tag_value.to_string()]), // NIP-33 d-tag&lt;br/&gt;    ];&lt;br/&gt;&lt;br/&gt;    if let Some(event_id) = build_manifest_event_id {&lt;br/&gt;        tags.push(Tag::event(*event_id));&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    let event_builder = EventBuilder::new(&lt;br/&gt;        Kind::Custom(30617), // NIP-34 Repository Announcement kind&lt;br/&gt;        &amp;#34;&amp;#34;, // Content is empty for repository announcement&lt;br/&gt;    ).tags(tags);&lt;br/&gt;&lt;br/&gt;    match client.send_event_builder(event_builder).await {&lt;br/&gt;        Ok(event_id) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Published NIP-34 Repository Announcement for {}. Event ID (raw): {:?}, Event ID (bech32): {}&amp;#34;, project_name, event_id, event_id.to_bech32().unwrap());&lt;br/&gt;        }&lt;br/&gt;        Err(e) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to publish NIP-34 Repository Announcement for {}: {}&amp;#34;, project_name, e);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub async fn publish_patch_event(&lt;br/&gt;    keys: &amp;amp;Keys,&lt;br/&gt;    relay_urls: &amp;amp;[String],&lt;br/&gt;    d_tag_value: &amp;amp;str,&lt;br/&gt;    commit_id: &amp;amp;str,&lt;br/&gt;    patch_content: &amp;amp;str,&lt;br/&gt;    build_manifest_event_id: Option&amp;lt;&amp;amp;EventId&amp;gt;,&lt;br/&gt;) {&lt;br/&gt;    let client = nostr_sdk::Client::new(keys.clone());&lt;br/&gt;&lt;br/&gt;    for relay_url in relay_urls {&lt;br/&gt;        if let Err(e) = client.add_relay(relay_url).await {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to add relay for patch {}: {}&amp;#34;, relay_url, e);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;    client.connect().await;&lt;br/&gt;&lt;br/&gt;    let mut tags = vec![&lt;br/&gt;        Tag::custom(&amp;#34;d&amp;#34;.into(), vec![d_tag_value.to_string()]), // Repository d-tag&lt;br/&gt;        Tag::parse([&amp;#34;commit&amp;#34;, commit_id]).expect(&amp;#34;Failed to create commit tag&amp;#34;),&lt;br/&gt;    ];&lt;br/&gt;&lt;br/&gt;    if let Some(event_id) = build_manifest_event_id {&lt;br/&gt;        tags.push(Tag::event(*event_id));&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    let event_builder = EventBuilder::new(&lt;br/&gt;        Kind::Custom(1617), // NIP-34 Patch kind&lt;br/&gt;        patch_content,&lt;br/&gt;    ).tags(tags);&lt;br/&gt;&lt;br/&gt;    match client.send_event_builder(event_builder).await {&lt;br/&gt;        Ok(event_id) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=\nPublished NIP-34 Patch event for commit {}.\nEvent ID (raw): {:?},\nEvent ID (bech32): {}&amp;#34;, commit_id, event_id, event_id.to_bech32().unwrap());&lt;br/&gt;        }&lt;br/&gt;        Err(e) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to publish NIP-34 Patch event for commit {}: {}&amp;#34;, commit_id, e);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub async fn publish_pull_request_event(&lt;br/&gt;    keys: &amp;amp;Keys,&lt;br/&gt;    relay_urls: &amp;amp;[String],&lt;br/&gt;    d_tag_value: &amp;amp;str,&lt;br/&gt;    commit_id: &amp;amp;str,&lt;br/&gt;    clone_url: &amp;amp;str,&lt;br/&gt;    title: Option&amp;lt;&amp;amp;str&amp;gt;,&lt;br/&gt;    build_manifest_event_id: Option&amp;lt;&amp;amp;EventId&amp;gt;,&lt;br/&gt;) {&lt;br/&gt;    let client = nostr_sdk::Client::new(keys.clone());&lt;br/&gt;&lt;br/&gt;    for relay_url in relay_urls {&lt;br/&gt;        if let Err(e) = client.add_relay(relay_url).await {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to add relay for pull request {}: {}&amp;#34;, relay_url, e);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;    client.connect().await;&lt;br/&gt;&lt;br/&gt;    let mut tags = vec![&lt;br/&gt;        Tag::custom(&amp;#34;d&amp;#34;.into(), vec![d_tag_value.to_string()]), // Repository d-tag&lt;br/&gt;        Tag::parse([&amp;#34;commit&amp;#34;, commit_id]).expect(&amp;#34;Failed to create commit tag&amp;#34;),&lt;br/&gt;        Tag::parse([&amp;#34;clone&amp;#34;, clone_url]).expect(&amp;#34;Failed to create clone tag&amp;#34;),&lt;br/&gt;    ];&lt;br/&gt;&lt;br/&gt;    if let Some(t) = title {&lt;br/&gt;        tags.push(Tag::parse([&amp;#34;title&amp;#34;, t]).expect(&amp;#34;Failed to create title tag&amp;#34;));&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    if let Some(event_id) = build_manifest_event_id {&lt;br/&gt;        tags.push(Tag::event(*event_id));&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    let event_builder = EventBuilder::new(&lt;br/&gt;        Kind::Custom(1618), // NIP-34 Pull Request kind&lt;br/&gt;        &amp;#34;gnostr patch&amp;#34;, // Content can be empty or a description for the PR&lt;br/&gt;    ).tags(tags);&lt;br/&gt;&lt;br/&gt;    match client.send_event_builder(event_builder).await {&lt;br/&gt;        Ok(event_id) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Published NIP-34 Pull Request event for commit {}. Event ID (raw): {:?}, Event ID (bech32): {}&amp;#34;, commit_id, event_id, event_id.to_bech32().unwrap());&lt;br/&gt;        }&lt;br/&gt;        Err(e) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to publish NIP-34 Pull Request event for commit {}: {}&amp;#34;, commit_id, e);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub async fn publish_pr_update_event(&lt;br/&gt;    keys: &amp;amp;Keys,&lt;br/&gt;    relay_urls: &amp;amp;[String],&lt;br/&gt;    d_tag_value: &amp;amp;str,&lt;br/&gt;    pr_event_id: &amp;amp;EventId,&lt;br/&gt;    updated_commit_id: &amp;amp;str,&lt;br/&gt;    updated_clone_url: &amp;amp;str,&lt;br/&gt;    build_manifest_event_id: Option&amp;lt;&amp;amp;EventId&amp;gt;,&lt;br/&gt;) {&lt;br/&gt;    let client = nostr_sdk::Client::new(keys.clone());&lt;br/&gt;&lt;br/&gt;    for relay_url in relay_urls {&lt;br/&gt;        if let Err(e) = client.add_relay(relay_url).await {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to add relay for PR update {}: {}&amp;#34;, relay_url, e);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;    client.connect().await;&lt;br/&gt;&lt;br/&gt;    let mut tags = vec![&lt;br/&gt;        Tag::custom(&amp;#34;d&amp;#34;.into(), vec![d_tag_value.to_string()]), // Repository d-tag&lt;br/&gt;        Tag::parse([&amp;#34;p&amp;#34;, pr_event_id.to_string().as_str()]).expect(&amp;#34;Failed to create PR event ID tag&amp;#34;),&lt;br/&gt;        Tag::parse([&amp;#34;commit&amp;#34;, updated_commit_id]).expect(&amp;#34;Failed to create updated commit ID tag&amp;#34;),&lt;br/&gt;        Tag::parse([&amp;#34;clone&amp;#34;, updated_clone_url]).expect(&amp;#34;Failed to create updated clone URL tag&amp;#34;),&lt;br/&gt;    ];&lt;br/&gt;&lt;br/&gt;    if let Some(event_id) = build_manifest_event_id {&lt;br/&gt;        tags.push(Tag::event(*event_id));&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    let event_builder = EventBuilder::new(&lt;br/&gt;        Kind::Custom(1619), // NIP-34 PR Update kind&lt;br/&gt;        &amp;#34;&amp;#34;, // Content is empty for PR update&lt;br/&gt;    ).tags(tags);&lt;br/&gt;&lt;br/&gt;    match client.send_event_builder(event_builder).await {&lt;br/&gt;        Ok(event_id) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Published NIP-34 PR Update event for PR {} (raw: {:?}). Event ID (raw): {:?}, Event ID (bech32): {}&amp;#34;, pr_event_id.to_bech32().unwrap(), pr_event_id, event_id, event_id.to_bech32().unwrap());&lt;br/&gt;        }&lt;br/&gt;        Err(e) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to publish NIP-34 PR Update event for PR {}: {}&amp;#34;, pr_event_id.to_string(), e);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub async fn publish_repository_state_event(&lt;br/&gt;    keys: &amp;amp;Keys,&lt;br/&gt;    relay_urls: &amp;amp;[String],&lt;br/&gt;    d_tag_value: &amp;amp;str,&lt;br/&gt;    branch_name: &amp;amp;str,&lt;br/&gt;    commit_id: &amp;amp;str,&lt;br/&gt;) {&lt;br/&gt;    let client = nostr_sdk::Client::new(keys.clone());&lt;br/&gt;&lt;br/&gt;    for relay_url in relay_urls {&lt;br/&gt;        if let Err(e) = client.add_relay(relay_url).await {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to add relay for repository state {}: {}&amp;#34;, relay_url, e);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;    client.connect().await;&lt;br/&gt;&lt;br/&gt;    let event_builder = EventBuilder::new(&lt;br/&gt;        Kind::Custom(30618), // NIP-34 Repository State kind&lt;br/&gt;        &amp;#34;&amp;#34;, // Content is empty for repository state&lt;br/&gt;    ).tags(vec![&lt;br/&gt;        Tag::custom(&amp;#34;d&amp;#34;.into(), vec![d_tag_value.to_string()]), // Repository d-tag&lt;br/&gt;        Tag::parse([&amp;#34;name&amp;#34;, branch_name]).expect(&amp;#34;Failed to create branch name tag&amp;#34;),&lt;br/&gt;        Tag::parse([&amp;#34;commit&amp;#34;, commit_id]).expect(&amp;#34;Failed to create commit ID tag&amp;#34;),&lt;br/&gt;    ]);&lt;br/&gt;&lt;br/&gt;    match client.send_event_builder(event_builder).await {&lt;br/&gt;        Ok(event_id) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Published NIP-34 Repository State event for branch {} (commit {}). Event ID (raw): {:?}, Event ID (bech32): {}&amp;#34;, branch_name, commit_id, event_id, event_id.to_bech32().unwrap());&lt;br/&gt;        }&lt;br/&gt;        Err(e) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to publish NIP-34 Repository State event for branch {} (commit {}): {}&amp;#34;, branch_name, commit_id, e);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub async fn publish_issue_event(&lt;br/&gt;    keys: &amp;amp;Keys,&lt;br/&gt;    relay_urls: &amp;amp;[String],&lt;br/&gt;    d_tag_value: &amp;amp;str,&lt;br/&gt;    issue_id: &amp;amp;str, // Unique identifier for the issue&lt;br/&gt;    title: &amp;amp;str,&lt;br/&gt;    content: &amp;amp;str,&lt;br/&gt;    build_manifest_event_id: Option&amp;lt;&amp;amp;EventId&amp;gt;,&lt;br/&gt;) {&lt;br/&gt;    let client = nostr_sdk::Client::new(keys.clone());&lt;br/&gt;&lt;br/&gt;    for relay_url in relay_urls {&lt;br/&gt;        if let Err(e) = client.add_relay(relay_url).await {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to add relay for issue {}: {}&amp;#34;, relay_url, e);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;    client.connect().await;&lt;br/&gt;&lt;br/&gt;    let mut tags = vec![&lt;br/&gt;        Tag::custom(&amp;#34;d&amp;#34;.into(), vec![d_tag_value.to_string()]), // Repository d-tag&lt;br/&gt;        Tag::parse([&amp;#34;i&amp;#34;, issue_id]).expect(&amp;#34;Failed to create issue ID tag&amp;#34;),&lt;br/&gt;        Tag::parse([&amp;#34;title&amp;#34;, title]).expect(&amp;#34;Failed to create title tag&amp;#34;),&lt;br/&gt;    ];&lt;br/&gt;&lt;br/&gt;    if let Some(event_id) = build_manifest_event_id {&lt;br/&gt;        tags.push(Tag::event(*event_id));&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    let event_builder = EventBuilder::new(&lt;br/&gt;        Kind::Custom(1621), // NIP-34 Issue kind&lt;br/&gt;        content,&lt;br/&gt;    ).tags(tags);&lt;br/&gt;&lt;br/&gt;    match client.send_event_builder(event_builder).await {&lt;br/&gt;        Ok(event_id) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Published NIP-34 Issue event for issue {} ({}). Event ID (raw): {:?}, Event ID (bech32): {}&amp;#34;, issue_id, title, event_id, event_id.to_bech32().unwrap());&lt;br/&gt;        }&lt;br/&gt;        Err(e) =&amp;gt; {&lt;br/&gt;            println!(&amp;#34;cargo:warning=Failed to publish NIP-34 Issue event for issue {} ({}): {}&amp;#34;, issue_id, title, e);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;pub fn generate_frost_keys(&lt;br/&gt;    max_signers: u16,&lt;br/&gt;    min_signers: u16,&lt;br/&gt;) -&amp;gt; Result&amp;lt;(BTreeMap&amp;lt;frost::Identifier, SecretShare&amp;gt;, PublicKeyPackage), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {                let mut rng = thread_rng();&lt;br/&gt;    let (shares, pubkey_package) = frost::keys::generate_with_dealer(&lt;br/&gt;        max_signers,&lt;br/&gt;        min_signers,&lt;br/&gt;        frost::keys::IdentifierList::Default,&lt;br/&gt;        &amp;amp;mut rng,&lt;br/&gt;    )?;&lt;br/&gt;    Ok((shares, pubkey_package))&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    pub fn create_frost_commitment(&lt;br/&gt;    secret_share: &amp;amp;SecretShare,&lt;br/&gt;    ) -&amp;gt; (SigningNonces, SigningCommitments) {&lt;br/&gt;    let mut rng = thread_rng();&lt;br/&gt;    frost::round1::commit(secret_share.signing_share(), &amp;amp;mut rng)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    pub fn create_signing_package(&lt;br/&gt;    commitments: BTreeMap&amp;lt;frost::Identifier, SigningCommitments&amp;gt;,&lt;br/&gt;    message: &amp;amp;[u8],&lt;br/&gt;    ) -&amp;gt; SigningPackage {&lt;br/&gt;    frost::SigningPackage::new(commitments, message)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    pub fn generate_signature_share(&lt;br/&gt;    signing_package: &amp;amp;SigningPackage,&lt;br/&gt;    nonces: &amp;amp;SigningNonces,&lt;br/&gt;    secret_share: &amp;amp;SecretShare,&lt;br/&gt;    ) -&amp;gt; Result&amp;lt;SignatureShare, Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {&lt;br/&gt;    let key_package: KeyPackage = secret_share.clone().try_into()?;&lt;br/&gt;    Ok(frost::round2::sign(signing_package, nonces, &amp;amp;key_package)?)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    pub fn aggregate_signature_shares(&lt;br/&gt;    signing_package: &amp;amp;SigningPackage,&lt;br/&gt;    signature_shares: &amp;amp;BTreeMap&amp;lt;frost::Identifier, SignatureShare&amp;gt;,&lt;br/&gt;    pubkey_package: &amp;amp;PublicKeyPackage,&lt;br/&gt;    ) -&amp;gt; Result&amp;lt;frost_secp256k1_tr::Signature, Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {&lt;br/&gt;    Ok(frost::aggregate(signing_package, signature_shares, pubkey_package)?)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    pub fn verify_frost_signature(&lt;br/&gt;    group_public_key: &amp;amp;frost_secp256k1_tr::VerifyingKey,&lt;br/&gt;    message: &amp;amp;[u8],&lt;br/&gt;    signature: &amp;amp;frost_secp256k1_tr::Signature,&lt;br/&gt;    ) -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {&lt;br/&gt;    Ok(group_public_key.verify(message, signature)?)&lt;br/&gt;    }&lt;br/&gt;#[cfg(test)]&lt;br/&gt;mod tests {&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    use serial_test::serial;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    use std::collections::BTreeMap;&lt;br/&gt;    use std::fs::File;&lt;br/&gt;    use std::io::Write;&lt;br/&gt;    use sha2::{Digest, Sha256};&lt;br/&gt;    use tempfile;&lt;br/&gt;    use super::get_git_tracked_files;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    use super::frost;&lt;br/&gt;        use std::process::Command;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    use nostr_sdk::EventId;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    use std::str::FromStr;&lt;br/&gt;&lt;br/&gt;    // Test for get_file_hash! macro&lt;br/&gt;    #[test]&lt;br/&gt;    fn test_get_file_hash() {&lt;br/&gt;        let dir = tempfile::tempdir().unwrap();&lt;br/&gt;        let file_path = dir.path().join(&amp;#34;test_file.txt&amp;#34;);&lt;br/&gt;        let content = &amp;#34;Hello, world!&amp;#34;;&lt;br/&gt;        File::create(&amp;amp;file_path).unwrap().write_all(content.as_bytes()).unwrap();&lt;br/&gt;&lt;br/&gt;        // The macro expects a string literal, so we need to construct the path at compile time.&lt;br/&gt;        // This is a limitation for testing, normally you&amp;#39;d use it with a known file.&lt;br/&gt;        // For testing, we&amp;#39;ll manually verify a file known to be in the project.&lt;br/&gt;        // Let&amp;#39;s test `lib.rs` itself for a more realistic scenario.&lt;br/&gt;        let macro_hash = get_file_hash!(&amp;#34;lib.rs&amp;#34;);&lt;br/&gt;&lt;br/&gt;        // We will assert on a known file within the crate.&lt;br/&gt;        let bytes = include_bytes!(&amp;#34;lib.rs&amp;#34;);&lt;br/&gt;        let mut hasher_manual = Sha256::new();&lt;br/&gt;        hasher_manual.update(bytes);&lt;br/&gt;        let expected_hash_lib_rs = hasher_manual.finalize()&lt;br/&gt;            .iter()&lt;br/&gt;            .map(|b| format!(&amp;#34;{:02x}&amp;#34;, b))&lt;br/&gt;            .collect::&amp;lt;String&amp;gt;();&lt;br/&gt;&lt;br/&gt;        assert_eq!(macro_hash, expected_hash_lib_rs);&lt;br/&gt;&lt;br/&gt;        // Test with another known file, e.g., Cargo.toml of the core crate&lt;br/&gt;        let cargo_toml_hash = get_file_hash!(&amp;#34;../Cargo.toml&amp;#34;);&lt;br/&gt;        let cargo_toml_bytes = include_bytes!(&amp;#34;../Cargo.toml&amp;#34;);&lt;br/&gt;        let mut cargo_toml_hasher = Sha256::new();&lt;br/&gt;        cargo_toml_hasher.update(cargo_toml_bytes);&lt;br/&gt;        let expected_cargo_toml_hash = cargo_toml_hasher.finalize()&lt;br/&gt;            .iter()&lt;br/&gt;            .map(|b| format!(&amp;#34;{:02x}&amp;#34;, b))&lt;br/&gt;            .collect::&amp;lt;String&amp;gt;();&lt;br/&gt;        assert_eq!(cargo_toml_hash, expected_cargo_toml_hash);&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    #[test]&lt;br/&gt;    fn test_get_git_tracked_files() {&lt;br/&gt;        let dir = tempfile::tempdir().unwrap();&lt;br/&gt;        let repo_path = dir.path();&lt;br/&gt;&lt;br/&gt;        // Initialize a git repository&lt;br/&gt;        let _ = Command::new(&amp;#34;git&amp;#34;)&lt;br/&gt;            .arg(&amp;#34;init&amp;#34;)&lt;br/&gt;            .current_dir(repo_path)&lt;br/&gt;            .stdout(std::process::Stdio::null())&lt;br/&gt;            .stderr(std::process::Stdio::null())&lt;br/&gt;            .output()&lt;br/&gt;            .expect(&amp;#34;Failed to initialize git repo&amp;#34;);&lt;br/&gt;&lt;br/&gt;        // Create some files&lt;br/&gt;        let file1_path = repo_path.join(&amp;#34;file1.txt&amp;#34;);&lt;br/&gt;        File::create(&amp;amp;file1_path).unwrap().write_all(b&amp;#34;content1&amp;#34;).unwrap();&lt;br/&gt;        let file2_path = repo_path.join(&amp;#34;file2.txt&amp;#34;);&lt;br/&gt;        File::create(&amp;amp;file2_path).unwrap().write_all(b&amp;#34;content2&amp;#34;).unwrap();&lt;br/&gt;&lt;br/&gt;        // Add and commit files&lt;br/&gt;        let _ = Command::new(&amp;#34;git&amp;#34;)&lt;br/&gt;            .arg(&amp;#34;add&amp;#34;)&lt;br/&gt;            .arg(&amp;#34;.&amp;#34;)&lt;br/&gt;            .current_dir(repo_path)&lt;br/&gt;            .stdout(std::process::Stdio::null())&lt;br/&gt;            .stderr(std::process::Stdio::null())&lt;br/&gt;            .output()&lt;br/&gt;            .expect(&amp;#34;Failed to git add files&amp;#34;);&lt;br/&gt;        let _ = Command::new(&amp;#34;git&amp;#34;)&lt;br/&gt;            .arg(&amp;#34;commit&amp;#34;)&lt;br/&gt;            .arg(&amp;#34;-m&amp;#34;)&lt;br/&gt;            .arg(&amp;#34;Initial commit&amp;#34;)&lt;br/&gt;            .current_dir(repo_path)&lt;br/&gt;            .stdout(std::process::Stdio::null())&lt;br/&gt;            .stderr(std::process::Stdio::null())&lt;br/&gt;            .output()&lt;br/&gt;            .expect(&amp;#34;Failed to git commit&amp;#34;);&lt;br/&gt;&lt;br/&gt;        let tracked_files = get_git_tracked_files(&amp;amp;repo_path.to_path_buf());&lt;br/&gt;        assert_eq!(tracked_files.len(), 2);&lt;br/&gt;        assert!(tracked_files.contains(&amp;amp;&amp;#34;file1.txt&amp;#34;.to_string()));&lt;br/&gt;        assert!(tracked_files.contains(&amp;amp;&amp;#34;file2.txt&amp;#34;.to_string()));&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    // #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    // #[test]&lt;br/&gt;    // fn test_file_hash_as_nostr_private_key() {&lt;br/&gt;    //     use super::file_hash_as_nostr_private_key;&lt;br/&gt;    //     // use std::fs::{File, remove_file};&lt;br/&gt;    //     // use std::io::Write;&lt;br/&gt;    //     // use tempfile::tempdir; // Not needed as we&amp;#39;re using a literal path&lt;br/&gt;    //     use nostr_sdk::prelude::ToBech32;&lt;br/&gt;&lt;br/&gt;    //     let file_path = PathBuf::from(&amp;#34;test_nostr_file_for_macro.txt&amp;#34;);&lt;br/&gt;    //     let content = &amp;#34;Nostr test content!&amp;#34;;&lt;br/&gt;    //     File::create(&amp;amp;file_path).unwrap().write_all(content.as_bytes()).unwrap();&lt;br/&gt;&lt;br/&gt;    //     let keys = file_hash_as_nostr_private_key!(&amp;#34;test_nostr_file_for_macro.txt&amp;#34;);&lt;br/&gt;&lt;br/&gt;    //     assert!(!keys.public_key().to_bech32().unwrap().is_empty());&lt;br/&gt;&lt;br/&gt;    //     remove_file(&amp;amp;file_path).unwrap();&lt;br/&gt;    // }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    #[tokio::test]&lt;br/&gt;    async fn test_publish_metadata_event_tr() {&lt;br/&gt;        use super::publish_metadata_event;&lt;br/&gt;        use nostr_sdk::Keys;&lt;br/&gt;&lt;br/&gt;        let keys = Keys::parse(super::DEFAULT_GNOSTR_KEY).expect(&amp;#34;Failed to create Nostr Keys from DEFAULT_GNOSTR_KEY&amp;#34;);&lt;br/&gt;        let picture_url = super::DEFAULT_PICTURE_URL;&lt;br/&gt;        let banner_url = super::DEFAULT_BANNER_URL;&lt;br/&gt;        let file_path_str = &amp;#34;test_file.txt&amp;#34;;&lt;br/&gt;&lt;br/&gt;        // This test primarily checks that the function doesn&amp;#39;t panic&lt;br/&gt;        // and goes through its execution path.&lt;br/&gt;        // Actual publishing success depends on external network conditions.&lt;br/&gt;        let relay_urls = super::get_relay_urls();&lt;br/&gt;        publish_metadata_event(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            picture_url,&lt;br/&gt;            banner_url,&lt;br/&gt;            file_path_str,&lt;br/&gt;        ).await;&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    #[tokio::test]&lt;br/&gt;    #[serial]&lt;br/&gt;    async fn test_repository_announcement_event_tr() {&lt;br/&gt;        use super::get_relay_urls;&lt;br/&gt;        use nostr_sdk::{Keys, EventId};&lt;br/&gt;        use std::str::FromStr;&lt;br/&gt;&lt;br/&gt;        let keys = Keys::parse(super::DEFAULT_GNOSTR_KEY).expect(&amp;#34;Failed to create Nostr Keys from DEFAULT_GNOSTR_KEY&amp;#34;);&lt;br/&gt;        let relay_urls = get_relay_urls();&lt;br/&gt;        let project_name = &amp;#34;test-nip34-repo&amp;#34;;&lt;br/&gt;        let description = &amp;#34;A test repository for NIP-34 announcements.&amp;#34;;&lt;br/&gt;        let clone_url = &amp;#34;git@example.com:test/test-nip34-repo.git&amp;#34;;&lt;br/&gt;        let _dummy_build_manifest_id = EventId::from_str(super::DUMMY_BUILD_MANIFEST_ID_STR).unwrap();&lt;br/&gt;        let _file_for_euc = &amp;#34;Cargo.toml&amp;#34;; // Use a known file in the project, as required by include_bytes!&lt;br/&gt;&lt;br/&gt;        // This test primarily checks that the macro and function compile and execute without panicking.&lt;br/&gt;        // Actual publishing success depends on external network conditions.&lt;br/&gt;        super::publish_metadata_event(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_repo_announcement_picture.jpg&amp;#34&#34;&gt;https://example.com/test_repo_announcement_picture.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_repo_announcement_banner.jpg&amp;#34&#34;&gt;https://example.com/test_repo_announcement_banner.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;test_repository_announcement_event_metadata&amp;#34;,&lt;br/&gt;        ).await;&lt;br/&gt;&lt;br/&gt;        let dummy_build_manifest_id = EventId::from_str(super::DUMMY_BUILD_MANIFEST_ID_STR).unwrap();&lt;br/&gt;&lt;br/&gt;        repository_announcement!(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            project_name,&lt;br/&gt;            description,&lt;br/&gt;            clone_url,&lt;br/&gt;            &amp;#34;../Cargo.toml&amp;#34;, // Pass the string literal directly, correcting path for include_bytes!&lt;br/&gt;            Some(&amp;amp;dummy_build_manifest_id)&lt;br/&gt;            );&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    #[tokio::test]&lt;br/&gt;    async fn test_publish_patch_event_tr() {&lt;br/&gt;        use super::{get_relay_urls, DEFAULT_PICTURE_URL, DEFAULT_BANNER_URL};&lt;br/&gt;        use nostr_sdk::Keys;&lt;br/&gt;&lt;br/&gt;        let keys = Keys::parse(super::DEFAULT_GNOSTR_KEY).expect(&amp;#34;Failed to create Nostr Keys from DEFAULT_GNOSTR_KEY&amp;#34;);&lt;br/&gt;        let relay_urls = get_relay_urls();&lt;br/&gt;        let d_tag = &amp;#34;test-repo-for-patch&amp;#34;;&lt;br/&gt;        let commit_id = &amp;#34;fedcba9876543210fedcba9876543210fedcba&amp;#34;;&lt;br/&gt;&lt;br/&gt;        // This test primarily checks that the macro and function compile and execute without panicking.&lt;br/&gt;        // Actual publishing success depends on external network conditions.&lt;br/&gt;        super::publish_metadata_event(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            DEFAULT_PICTURE_URL,&lt;br/&gt;            DEFAULT_BANNER_URL,&lt;br/&gt;            &amp;#34;test_publish_patch_event_metadata&amp;#34;,&lt;br/&gt;        ).await;&lt;br/&gt;&lt;br/&gt;        let dummy_build_manifest_id = EventId::from_str(super::DUMMY_BUILD_MANIFEST_ID_STR).unwrap();&lt;br/&gt;        publish_patch!(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            d_tag,&lt;br/&gt;            commit_id,&lt;br/&gt;            &amp;#34;lib.rs&amp;#34;, // Use an existing file for the patch content&lt;br/&gt;            Some(&amp;amp;dummy_build_manifest_id)&lt;br/&gt;        );    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    #[tokio::test]&lt;br/&gt;    async fn test_publish_pull_request_event_tr() {&lt;br/&gt;        use super::get_relay_urls;&lt;br/&gt;        use nostr_sdk::Keys;&lt;br/&gt;&lt;br/&gt;        let keys = Keys::parse(super::DEFAULT_GNOSTR_KEY).expect(&amp;#34;Failed to create Nostr Keys from DEFAULT_GNOSTR_KEY&amp;#34;);&lt;br/&gt;        let relay_urls = get_relay_urls();&lt;br/&gt;        let d_tag = &amp;#34;test-repo-for-pr&amp;#34;;&lt;br/&gt;        let commit_id = &amp;#34;0123456789abcdef0123456789abcdef01234567&amp;#34;;&lt;br/&gt;        let clone_url = &amp;#34;git@example.com:test/pr-branch.git&amp;#34;;&lt;br/&gt;        let title = Some(&amp;#34;Feat: Implement NIP-34 PR&amp;#34;);&lt;br/&gt;        let dummy_build_manifest_id = EventId::from_str(super::DUMMY_BUILD_MANIFEST_ID_STR).unwrap();&lt;br/&gt;&lt;br/&gt;        super::publish_metadata_event(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_pr_picture.jpg&amp;#34&#34;&gt;https://example.com/test_pr_picture.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_pr_banner.jpg&amp;#34&#34;&gt;https://example.com/test_pr_banner.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;test_publish_pull_request_event_metadata&amp;#34;,&lt;br/&gt;        ).await;&lt;br/&gt;&lt;br/&gt;        // Test with a title&lt;br/&gt;        publish_pull_request!(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            d_tag,&lt;br/&gt;            commit_id,&lt;br/&gt;            clone_url,&lt;br/&gt;            Some(title.unwrap()),&lt;br/&gt;            Some(&amp;amp;dummy_build_manifest_id)&lt;br/&gt;            );&lt;br/&gt;        // Test without a title&lt;br/&gt;        publish_pull_request!(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            d_tag,&lt;br/&gt;            commit_id,&lt;br/&gt;            clone_url&lt;br/&gt;        );&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    #[tokio::test]&lt;br/&gt;    async fn test_publish_pr_update_event_tr() {&lt;br/&gt;        use super::get_relay_urls;&lt;br/&gt;        use nostr_sdk::{Keys, EventId};&lt;br/&gt;        use std::str::FromStr;&lt;br/&gt;&lt;br/&gt;        let keys = Keys::parse(super::DEFAULT_GNOSTR_KEY).expect(&amp;#34;Failed to create Nostr Keys from DEFAULT_GNOSTR_KEY&amp;#34;);&lt;br/&gt;        let relay_urls = get_relay_urls();&lt;br/&gt;        let d_tag = &amp;#34;test-repo-for-pr-update&amp;#34;;&lt;br/&gt;        let pr_event_id = EventId::from_str(&amp;#34;f6e4d6a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9&amp;#34;).unwrap(); // Placeholder EventId&lt;br/&gt;        let updated_commit_id = &amp;#34;z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5k4j3i2h1g0&amp;#34;;&lt;br/&gt;        let updated_clone_url = &amp;#34;git@example.com:test/pr-branch-updated.git&amp;#34;;&lt;br/&gt;        let dummy_build_manifest_id = EventId::from_str(super::DUMMY_BUILD_MANIFEST_ID_STR).unwrap();&lt;br/&gt;&lt;br/&gt;        // This test primarily checks that the macro and function compile and execute without panicking.&lt;br/&gt;        // Actual publishing success depends on external network conditions.&lt;br/&gt;        super::publish_metadata_event(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_pr_update_picture.jpg&amp;#34&#34;&gt;https://example.com/test_pr_update_picture.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_pr_update_banner.jpg&amp;#34&#34;&gt;https://example.com/test_pr_update_banner.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;test_publish_pr_update_event_metadata&amp;#34;,&lt;br/&gt;        ).await;&lt;br/&gt;&lt;br/&gt;        publish_pr_update!(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            d_tag,&lt;br/&gt;            &amp;amp;pr_event_id, // Pass a reference to pr_event_id&lt;br/&gt;            updated_commit_id,&lt;br/&gt;            updated_clone_url,&lt;br/&gt;            Some(&amp;amp;dummy_build_manifest_id)&lt;br/&gt;        );    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    #[tokio::test]&lt;br/&gt;    async fn test_publish_repository_state_event() {&lt;br/&gt;        use super::get_relay_urls;&lt;br/&gt;        use nostr_sdk::Keys;&lt;br/&gt;&lt;br/&gt;        let keys = Keys::parse(super::DEFAULT_GNOSTR_KEY).expect(&amp;#34;Failed to create Nostr Keys from DEFAULT_GNOSTR_KEY&amp;#34;);&lt;br/&gt;        let relay_urls = get_relay_urls();&lt;br/&gt;        let d_tag = &amp;#34;test-repo-for-state&amp;#34;;&lt;br/&gt;        let branch_name = &amp;#34;main&amp;#34;;&lt;br/&gt;        let commit_id = &amp;#34;abcde12345abcde12345abcde12345abcde12345&amp;#34;;&lt;br/&gt;        use nostr_sdk::EventId;&lt;br/&gt;        use std::str::FromStr;&lt;br/&gt;        let _dummy_build_manifest_id = EventId::from_str(super::DUMMY_BUILD_MANIFEST_ID_STR).unwrap();&lt;br/&gt;&lt;br/&gt;        // This test primarily checks that the macro and function compile and execute without panicking.&lt;br/&gt;        // Actual publishing success depends on external network conditions.&lt;br/&gt;        super::publish_metadata_event(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_repo_state_picture.jpg&amp;#34&#34;&gt;https://example.com/test_repo_state_picture.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_repo_state_banner.jpg&amp;#34&#34;&gt;https://example.com/test_repo_state_banner.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;test_publish_repository_state_event_metadata&amp;#34;,&lt;br/&gt;        ).await;&lt;br/&gt;&lt;br/&gt;        publish_repository_state!(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            d_tag,&lt;br/&gt;            branch_name,&lt;br/&gt;            commit_id&lt;br/&gt;        );    }&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;    // Test for get_file_hash! macro&lt;br/&gt;    #[test]&lt;br/&gt;    fn test_get_file_hash_tr() {&lt;br/&gt;        let dir = tempfile::tempdir().unwrap();&lt;br/&gt;        let file_path = dir.path().join(&amp;#34;test_file.txt&amp;#34;);&lt;br/&gt;        let content = &amp;#34;Hello, world!&amp;#34;;&lt;br/&gt;        File::create(&amp;amp;file_path).unwrap().write_all(content.as_bytes()).unwrap();&lt;br/&gt;&lt;br/&gt;        // The macro expects a string literal, so we need to construct the path at compile time.&lt;br/&gt;        // This is a limitation for testing, normally you&amp;#39;d use it with a known file.&lt;br/&gt;        // For testing, we&amp;#39;ll manually verify a file known to be in the project.&lt;br/&gt;        // Let&amp;#39;s test `lib.rs` itself for a more realistic scenario.&lt;br/&gt;        let macro_hash = get_file_hash!(&amp;#34;lib.rs&amp;#34;);&lt;br/&gt;&lt;br/&gt;        // We will assert on a known file within the crate.&lt;br/&gt;        let bytes = include_bytes!(&amp;#34;lib.rs&amp;#34;);&lt;br/&gt;        let mut hasher_manual = Sha256::new();&lt;br/&gt;        hasher_manual.update(bytes);&lt;br/&gt;        let expected_hash_lib_rs = hasher_manual.finalize()&lt;br/&gt;            .iter()&lt;br/&gt;            .map(|b| format!(&amp;#34;{:02x}&amp;#34;, b))&lt;br/&gt;            .collect::&amp;lt;String&amp;gt;();&lt;br/&gt;&lt;br/&gt;        assert_eq!(macro_hash, expected_hash_lib_rs);&lt;br/&gt;&lt;br/&gt;        // Test with another known file, e.g., Cargo.toml of the core crate&lt;br/&gt;        let cargo_toml_hash = get_file_hash!(&amp;#34;../Cargo.toml&amp;#34;);&lt;br/&gt;        let cargo_toml_bytes = include_bytes!(&amp;#34;../Cargo.toml&amp;#34;);&lt;br/&gt;        let mut cargo_toml_hasher = Sha256::new();&lt;br/&gt;        cargo_toml_hasher.update(cargo_toml_bytes);&lt;br/&gt;        let expected_cargo_toml_hash = cargo_toml_hasher.finalize()&lt;br/&gt;            .iter()&lt;br/&gt;            .map(|b| format!(&amp;#34;{:02x}&amp;#34;, b))&lt;br/&gt;            .collect::&amp;lt;String&amp;gt;();&lt;br/&gt;        assert_eq!(cargo_toml_hash, expected_cargo_toml_hash);&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    #[test]&lt;br/&gt;    fn test_get_git_tracked_files_tr() {&lt;br/&gt;        let dir = tempfile::tempdir().unwrap();&lt;br/&gt;        let repo_path = dir.path();&lt;br/&gt;&lt;br/&gt;        // Initialize a git repository&lt;br/&gt;        let _ = Command::new(&amp;#34;git&amp;#34;)&lt;br/&gt;            .arg(&amp;#34;init&amp;#34;)&lt;br/&gt;            .current_dir(repo_path)&lt;br/&gt;            .stdout(std::process::Stdio::null())&lt;br/&gt;            .stderr(std::process::Stdio::null())&lt;br/&gt;            .output()&lt;br/&gt;            .expect(&amp;#34;Failed to initialize git repo&amp;#34;);&lt;br/&gt;&lt;br/&gt;        // Create some files&lt;br/&gt;        let file1_path = repo_path.join(&amp;#34;file1.txt&amp;#34;);&lt;br/&gt;        File::create(&amp;amp;file1_path).unwrap().write_all(b&amp;#34;content1&amp;#34;).unwrap();&lt;br/&gt;        let file2_path = repo_path.join(&amp;#34;file2.txt&amp;#34;);&lt;br/&gt;        File::create(&amp;amp;file2_path).unwrap().write_all(b&amp;#34;content2&amp;#34;).unwrap();&lt;br/&gt;&lt;br/&gt;        // Add and commit files&lt;br/&gt;        let _ = Command::new(&amp;#34;git&amp;#34;)&lt;br/&gt;            .arg(&amp;#34;add&amp;#34;)&lt;br/&gt;            .arg(&amp;#34;.&amp;#34;)&lt;br/&gt;            .current_dir(repo_path)&lt;br/&gt;            .stdout(std::process::Stdio::null())&lt;br/&gt;            .stderr(std::process::Stdio::null())&lt;br/&gt;            .output()&lt;br/&gt;            .expect(&amp;#34;Failed to git add files&amp;#34;);&lt;br/&gt;        let _ = Command::new(&amp;#34;git&amp;#34;)&lt;br/&gt;            .arg(&amp;#34;commit&amp;#34;)&lt;br/&gt;            .arg(&amp;#34;-m&amp;#34;)&lt;br/&gt;            .arg(&amp;#34;Initial commit&amp;#34;)&lt;br/&gt;            .current_dir(repo_path)&lt;br/&gt;            .stdout(std::process::Stdio::null())&lt;br/&gt;            .stderr(std::process::Stdio::null())&lt;br/&gt;            .output()&lt;br/&gt;            .expect(&amp;#34;Failed to git commit&amp;#34;);&lt;br/&gt;&lt;br/&gt;        let tracked_files = get_git_tracked_files(&amp;amp;repo_path.to_path_buf());&lt;br/&gt;        assert_eq!(tracked_files.len(), 2);&lt;br/&gt;        assert!(tracked_files.contains(&amp;amp;&amp;#34;file1.txt&amp;#34;.to_string()));&lt;br/&gt;        assert!(tracked_files.contains(&amp;amp;&amp;#34;file2.txt&amp;#34;.to_string()));&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    // #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    // #[test]&lt;br/&gt;    // fn test_file_hash_as_nostr_private_key() {&lt;br/&gt;    //     use super::file_hash_as_nostr_private_key;&lt;br/&gt;    //     // use std::fs::{File, remove_file};&lt;br/&gt;    //     // use std::io::Write;&lt;br/&gt;    //     // use tempfile::tempdir; // Not needed as we&amp;#39;re using a literal path&lt;br/&gt;    //     use nostr_sdk::prelude::ToBech32;&lt;br/&gt;&lt;br/&gt;    //     let file_path = PathBuf::from(&amp;#34;test_nostr_file_for_macro.txt&amp;#34;);&lt;br/&gt;    //     let content = &amp;#34;Nostr test content!&amp;#34;;&lt;br/&gt;    //     File::create(&amp;amp;file_path).unwrap().write_all(content.as_bytes()).unwrap();&lt;br/&gt;&lt;br/&gt;    //     let keys = file_hash_as_nostr_private_key!(&amp;#34;test_nostr_file_for_macro.txt&amp;#34;);&lt;br/&gt;&lt;br/&gt;    //     assert!(!keys.public_key().to_bech32().unwrap().is_empty());&lt;br/&gt;&lt;br/&gt;    //     remove_file(&amp;amp;file_path).unwrap();&lt;br/&gt;    // }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    #[tokio::test]&lt;br/&gt;    async fn test_publish_metadata_event() {&lt;br/&gt;        use super::publish_metadata_event;&lt;br/&gt;        use nostr_sdk::Keys;&lt;br/&gt;&lt;br/&gt;        let keys = Keys::parse(super::DEFAULT_GNOSTR_KEY).expect(&amp;#34;Failed to create Nostr Keys from DEFAULT_GNOSTR_KEY&amp;#34;);&lt;br/&gt;        let picture_url = super::DEFAULT_PICTURE_URL;&lt;br/&gt;        let banner_url = super::DEFAULT_BANNER_URL;&lt;br/&gt;        let file_path_str = &amp;#34;test_file.txt&amp;#34;;&lt;br/&gt;&lt;br/&gt;        // This test primarily checks that the function doesn&amp;#39;t panic&lt;br/&gt;        // and goes through its execution path.&lt;br/&gt;        // Actual publishing success depends on external network conditions.&lt;br/&gt;        let relay_urls = super::get_relay_urls();&lt;br/&gt;        publish_metadata_event(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            picture_url,&lt;br/&gt;            banner_url,&lt;br/&gt;            file_path_str,&lt;br/&gt;        ).await;&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    #[tokio::test]&lt;br/&gt;    #[serial]&lt;br/&gt;    async fn test_repository_announcement_event() {&lt;br/&gt;        use super::get_relay_urls;&lt;br/&gt;        use nostr_sdk::{Keys, EventId};&lt;br/&gt;        use std::str::FromStr;&lt;br/&gt;&lt;br/&gt;        let keys = Keys::parse(super::DEFAULT_GNOSTR_KEY).expect(&amp;#34;Failed to create Nostr Keys from DEFAULT_GNOSTR_KEY&amp;#34;);&lt;br/&gt;        let relay_urls = get_relay_urls();&lt;br/&gt;        let project_name = &amp;#34;test-nip34-repo&amp;#34;;&lt;br/&gt;        let description = &amp;#34;A test repository for NIP-34 announcements.&amp;#34;;&lt;br/&gt;        let clone_url = &amp;#34;git@example.com:test/test-nip34-repo.git&amp;#34;;&lt;br/&gt;        let _dummy_build_manifest_id = EventId::from_str(super::DUMMY_BUILD_MANIFEST_ID_STR).unwrap();&lt;br/&gt;        let _file_for_euc = &amp;#34;Cargo.toml&amp;#34;; // Use a known file in your project, as required by include_bytes!&lt;br/&gt;&lt;br/&gt;        // This test primarily checks that the macro and function compile and execute without panicking.&lt;br/&gt;        // Actual publishing success depends on external network conditions.&lt;br/&gt;        super::publish_metadata_event(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_repo_announcement_picture.jpg&amp;#34&#34;&gt;https://example.com/test_repo_announcement_picture.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_repo_announcement_banner.jpg&amp;#34&#34;&gt;https://example.com/test_repo_announcement_banner.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;test_repository_announcement_event_metadata&amp;#34;,&lt;br/&gt;        ).await;&lt;br/&gt;&lt;br/&gt;        let dummy_build_manifest_id = EventId::from_str(super::DUMMY_BUILD_MANIFEST_ID_STR).unwrap();&lt;br/&gt;&lt;br/&gt;        repository_announcement!(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            project_name,&lt;br/&gt;            description,&lt;br/&gt;            clone_url,&lt;br/&gt;            &amp;#34;../Cargo.toml&amp;#34;, // Pass the string literal directly, correcting path for include_bytes!&lt;br/&gt;            Some(&amp;amp;dummy_build_manifest_id)&lt;br/&gt;            );&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    #[tokio::test]&lt;br/&gt;    async fn test_publish_patch_event() {&lt;br/&gt;        use super::get_relay_urls;&lt;br/&gt;        use nostr_sdk::Keys;&lt;br/&gt;&lt;br/&gt;        let keys = Keys::parse(super::DEFAULT_GNOSTR_KEY).expect(&amp;#34;Failed to create Nostr Keys from DEFAULT_GNOSTR_KEY&amp;#34;);&lt;br/&gt;        let relay_urls = get_relay_urls();&lt;br/&gt;        let d_tag = &amp;#34;test-repo-for-patch&amp;#34;;&lt;br/&gt;        let commit_id = &amp;#34;fedcba9876543210fedcba9876543210fedcba&amp;#34;;&lt;br/&gt;&lt;br/&gt;        // This test primarily checks that the macro and function compile and execute without panicking.&lt;br/&gt;        // Actual publishing success depends on external network conditions.&lt;br/&gt;        super::publish_metadata_event(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_patch_picture.jpg&amp;#34&#34;&gt;https://example.com/test_patch_picture.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_patch_banner.jpg&amp;#34&#34;&gt;https://example.com/test_patch_banner.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;test_publish_patch_event_metadata&amp;#34;,&lt;br/&gt;        ).await;&lt;br/&gt;&lt;br/&gt;        let dummy_build_manifest_id = EventId::from_str(super::DUMMY_BUILD_MANIFEST_ID_STR).unwrap();&lt;br/&gt;        publish_patch!(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            d_tag,&lt;br/&gt;            commit_id,&lt;br/&gt;            &amp;#34;lib.rs&amp;#34;, // Use an existing file for the patch content&lt;br/&gt;            Some(&amp;amp;dummy_build_manifest_id)&lt;br/&gt;        );    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    #[tokio::test]&lt;br/&gt;    async fn test_publish_pull_request_event() {&lt;br/&gt;        use super::get_relay_urls;&lt;br/&gt;        use nostr_sdk::Keys;&lt;br/&gt;&lt;br/&gt;        let keys = Keys::parse(super::DEFAULT_GNOSTR_KEY).expect(&amp;#34;Failed to create Nostr Keys from DEFAULT_GNOSTR_KEY&amp;#34;);&lt;br/&gt;        let relay_urls = get_relay_urls();&lt;br/&gt;        let d_tag = &amp;#34;test-repo-for-pr&amp;#34;;&lt;br/&gt;        let commit_id = &amp;#34;0123456789abcdef0123456789abcdef01234567&amp;#34;;&lt;br/&gt;        let clone_url = &amp;#34;git@example.com:test/pr-branch.git&amp;#34;;&lt;br/&gt;        let title = Some(&amp;#34;Feat: Implement NIP-34 PR&amp;#34;);&lt;br/&gt;        let dummy_build_manifest_id = EventId::from_str(super::DUMMY_BUILD_MANIFEST_ID_STR).unwrap();&lt;br/&gt;&lt;br/&gt;        super::publish_metadata_event(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_pr_picture.jpg&amp;#34&#34;&gt;https://example.com/test_pr_picture.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_pr_banner.jpg&amp;#34&#34;&gt;https://example.com/test_pr_banner.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;test_publish_pull_request_event_metadata&amp;#34;,&lt;br/&gt;        ).await;&lt;br/&gt;&lt;br/&gt;        // Test with a title&lt;br/&gt;        publish_pull_request!(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            d_tag,&lt;br/&gt;            commit_id,&lt;br/&gt;            clone_url,&lt;br/&gt;            Some(title.unwrap()),&lt;br/&gt;            Some(&amp;amp;dummy_build_manifest_id)&lt;br/&gt;            );&lt;br/&gt;        // Test without a title&lt;br/&gt;        publish_pull_request!(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            d_tag,&lt;br/&gt;            commit_id,&lt;br/&gt;            clone_url&lt;br/&gt;        );&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    #[tokio::test]&lt;br/&gt;    async fn test_publish_pr_update_event() {&lt;br/&gt;        use super::get_relay_urls;&lt;br/&gt;        use nostr_sdk::{Keys, EventId};&lt;br/&gt;        use std::str::FromStr;&lt;br/&gt;&lt;br/&gt;        let keys = Keys::parse(super::DEFAULT_GNOSTR_KEY).expect(&amp;#34;Failed to create Nostr Keys from DEFAULT_GNOSTR_KEY&amp;#34;);&lt;br/&gt;        let relay_urls = get_relay_urls();&lt;br/&gt;        let d_tag = &amp;#34;test-repo-for-pr-update&amp;#34;;&lt;br/&gt;        let pr_event_id = EventId::from_str(&amp;#34;f6e4d6a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9&amp;#34;).unwrap(); // Placeholder EventId&lt;br/&gt;        let updated_commit_id = &amp;#34;z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5k4j3i2h1g0&amp;#34;;&lt;br/&gt;        let updated_clone_url = &amp;#34;git@example.com:test/pr-branch-updated.git&amp;#34;;&lt;br/&gt;        let dummy_build_manifest_id = EventId::from_str(super::DUMMY_BUILD_MANIFEST_ID_STR).unwrap();&lt;br/&gt;&lt;br/&gt;        // This test primarily checks that the macro and function compile and execute without panicking.&lt;br/&gt;        // Actual publishing success depends on external network conditions.&lt;br/&gt;        super::publish_metadata_event(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_pr_update_picture.jpg&amp;#34&#34;&gt;https://example.com/test_pr_update_picture.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_pr_update_banner.jpg&amp;#34&#34;&gt;https://example.com/test_pr_update_banner.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;test_publish_pr_update_event_metadata&amp;#34;,&lt;br/&gt;        ).await;&lt;br/&gt;&lt;br/&gt;        publish_pr_update!(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            d_tag,&lt;br/&gt;            &amp;amp;pr_event_id, // Pass a reference to pr_event_id&lt;br/&gt;            updated_commit_id,&lt;br/&gt;            updated_clone_url,&lt;br/&gt;            Some(&amp;amp;dummy_build_manifest_id)&lt;br/&gt;        );    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    #[tokio::test]&lt;br/&gt;    #[serial]&lt;br/&gt;    async fn test_publish_repository_state_event_tr() {&lt;br/&gt;        use super::get_relay_urls;&lt;br/&gt;        use nostr_sdk::Keys;&lt;br/&gt;        use nostr_sdk::secp256k1::SecretKey as NostrSecretKey;&lt;br/&gt;        &lt;br/&gt;        // 1. Generate FROST keys (1-of-1 for this test to derive a single Nostr key)&lt;br/&gt;        let (shares, _pubkey_package) = super::generate_frost_keys(2, 2).unwrap();&lt;br/&gt;        let signer_id = frost::Identifier::try_from(1 as u16).unwrap();&lt;br/&gt;        let secret_share = shares.get(&amp;amp;signer_id).unwrap();&lt;br/&gt;&lt;br/&gt;        // Convert FROST secret share&amp;#39;s scalar to a Nostr SecretKey&lt;br/&gt;        let frost_secp_secret_key = secret_share.signing_share().to_scalar();&lt;br/&gt;        let nostr_secret_key = NostrSecretKey::from_slice(&amp;amp;frost_secp_secret_key.to_bytes()).unwrap();&lt;br/&gt;        let keys = Keys::new(nostr_secret_key.into());&lt;br/&gt;        let relay_urls = get_relay_urls();&lt;br/&gt;        let d_tag = &amp;#34;test-repo-for-state&amp;#34;;&lt;br/&gt;        let branch_name = &amp;#34;main&amp;#34;;&lt;br/&gt;        let commit_id = &amp;#34;abcde12345abcde12345abcde12345abcde12345&amp;#34;;&lt;br/&gt;        use nostr_sdk::EventId;&lt;br/&gt;        use std::str::FromStr;&lt;br/&gt;        let _dummy_build_manifest_id = EventId::from_str(super::DUMMY_BUILD_MANIFEST_ID_STR).unwrap();&lt;br/&gt;&lt;br/&gt;        // This test primarily checks that the macro and function compile and execute without panicking.&lt;br/&gt;        // Actual publishing success depends on external network conditions.&lt;br/&gt;        super::publish_metadata_event(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_repo_state_picture.jpg&amp;#34&#34;&gt;https://example.com/test_repo_state_picture.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_repo_state_banner.jpg&amp;#34&#34;&gt;https://example.com/test_repo_state_banner.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;test_publish_repository_state_event_metadata&amp;#34;,&lt;br/&gt;        ).await;&lt;br/&gt;&lt;br/&gt;        publish_repository_state!(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            d_tag,&lt;br/&gt;            branch_name,&lt;br/&gt;            commit_id&lt;br/&gt;        );    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    #[tokio::test]&lt;br/&gt;    async fn test_publish_issue_event_tr() {&lt;br/&gt;        use super::get_relay_urls;&lt;br/&gt;        use nostr_sdk::Keys;&lt;br/&gt;        use nostr_sdk::EventId;&lt;br/&gt;        use std::str::FromStr;&lt;br/&gt;&lt;br/&gt;        let keys = Keys::parse(super::DEFAULT_GNOSTR_KEY).expect(&amp;#34;Failed to create Nostr Keys from DEFAULT_GNOSTR_KEY&amp;#34;);&lt;br/&gt;        let relay_urls = get_relay_urls();&lt;br/&gt;        let d_tag = &amp;#34;test-repo-for-issue&amp;#34;;&lt;br/&gt;        let issue_id = &amp;#34;456&amp;#34;;&lt;br/&gt;        let title = &amp;#34;Feature: Implement NIP-34 Issues&amp;#34;;&lt;br/&gt;        let content = &amp;#34;This is a test issue to verify the NIP-34 issue macro implementation.&amp;#34;;&lt;br/&gt;        let dummy_build_manifest_id = EventId::from_str(super::DUMMY_BUILD_MANIFEST_ID_STR).unwrap();&lt;br/&gt;&lt;br/&gt;        // This test primarily checks that the macro and function compile and execute without panicking.&lt;br/&gt;        // Actual publishing success depends on external network conditions.&lt;br/&gt;        super::publish_metadata_event(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_issue_picture.jpg&amp;#34&#34;&gt;https://example.com/test_issue_picture.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;&lt;a href=&#34;https://example.com/test_issue_banner.jpg&amp;#34&#34;&gt;https://example.com/test_issue_banner.jpg&amp;#34&lt;/a&gt;;,&lt;br/&gt;            &amp;#34;test_publish_issue_event_metadata&amp;#34;,&lt;br/&gt;        ).await;&lt;br/&gt;&lt;br/&gt;        publish_issue!(&lt;br/&gt;            &amp;amp;keys,&lt;br/&gt;            &amp;amp;relay_urls,&lt;br/&gt;            d_tag,&lt;br/&gt;            issue_id,&lt;br/&gt;            title,&lt;br/&gt;            content,&lt;br/&gt;            Some(&amp;amp;dummy_build_manifest_id)&lt;br/&gt;        );&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    #[cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;    #[test]&lt;br/&gt;    fn test_frost_signature_flow_tr() {&lt;br/&gt;        let max_signers = 3;&lt;br/&gt;        let min_signers = 2;&lt;br/&gt;        let message = b&amp;#34;This is a test message for FROST signing&amp;#34;;&lt;br/&gt;&lt;br/&gt;        // 1. Key Generation&lt;br/&gt;        let (shares, pubkey_package) = super::generate_frost_keys(max_signers, min_signers).unwrap();&lt;br/&gt;&lt;br/&gt;        let mut commitments = BTreeMap::new();&lt;br/&gt;        let mut nonces_map = BTreeMap::new();&lt;br/&gt;        let mut signature_shares_map = BTreeMap::new();&lt;br/&gt;&lt;br/&gt;        // 2. Commitment Phase (simulated for two signers)&lt;br/&gt;        let signer1_id = frost::Identifier::try_from(1 as u16).unwrap();&lt;br/&gt;        let (nonces1, comms1) = super::create_frost_commitment(&amp;amp;shares[&amp;amp;signer1_id]);&lt;br/&gt;        commitments.insert(signer1_id, comms1);&lt;br/&gt;        nonces_map.insert(signer1_id, nonces1);&lt;br/&gt;&lt;br/&gt;        let signer2_id = frost::Identifier::try_from(2 as u16).unwrap();&lt;br/&gt;        let (nonces2, comms2) = super::create_frost_commitment(&amp;amp;shares[&amp;amp;signer2_id]);&lt;br/&gt;        commitments.insert(signer2_id, comms2);&lt;br/&gt;        nonces_map.insert(signer2_id, nonces2);&lt;br/&gt;&lt;br/&gt;        // 3. Signing Package Creation&lt;br/&gt;        let signing_package = super::create_signing_package(commitments, message);&lt;br/&gt;&lt;br/&gt;        // 4. Signature Share Generation&lt;br/&gt;        let share1 = super::generate_signature_share(&amp;amp;signing_package, &amp;amp;nonces_map[&amp;amp;signer1_id], &amp;amp;shares[&amp;amp;signer1_id]).unwrap();&lt;br/&gt;        signature_shares_map.insert(signer1_id, share1);&lt;br/&gt;&lt;br/&gt;        let share2 = super::generate_signature_share(&amp;amp;signing_package, &amp;amp;nonces_map[&amp;amp;signer2_id], &amp;amp;shares[&amp;amp;signer2_id]).unwrap();&lt;br/&gt;        signature_shares_map.insert(signer2_id, share2);&lt;br/&gt;&lt;br/&gt;        // 5. Aggregation&lt;br/&gt;        let group_signature = super::aggregate_signature_shares(&amp;amp;signing_package, &amp;amp;signature_shares_map, &amp;amp;pubkey_package).unwrap();&lt;br/&gt;&lt;br/&gt;        // 6. Verification&lt;br/&gt;        let group_public_key = pubkey_package.verifying_key();&lt;br/&gt;        super::verify_frost_signature(&amp;amp;group_public_key, message, &amp;amp;group_signature).unwrap();&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-06T04:16:29Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsphfeu2ha6av5p0nf8w73qg4sfu52zwq6hv8mxhgxt4878lplx6egpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q56645y5</id>
    
      <title type="html">#![cfg(feature = &amp;#34;nostr&amp;#34;)] use frost_secp256k1_tr as ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsphfeu2ha6av5p0nf8w73qg4sfu52zwq6hv8mxhgxt4878lplx6egpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q56645y5" />
    <content type="html">
      #![cfg(feature = &amp;#34;nostr&amp;#34;)]&lt;br/&gt;&lt;br/&gt;use frost_secp256k1_tr as frost;&lt;br/&gt;use frost::keys::PublicKeyPackage;&lt;br/&gt;use frost::round2::SignatureShare;&lt;br/&gt;use frost::SigningPackage;&lt;br/&gt;use hex;&lt;br/&gt;use rand::thread_rng;&lt;br/&gt;use std::collections::BTreeMap;&lt;br/&gt;use sha2::Sha256;&lt;br/&gt;use serde_json;&lt;br/&gt;use sha2::Digest;&lt;br/&gt;&lt;br/&gt;pub fn process_relay_share(&lt;br/&gt;    relay_payload_hex: &amp;amp;str,&lt;br/&gt;    signer_id_u16: u16,&lt;br/&gt;    _signing_package: &amp;amp;SigningPackage,&lt;br/&gt;    _pubkey_package: &amp;amp;PublicKeyPackage,&lt;br/&gt;) -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {&lt;br/&gt;    // In a real scenario, this function would deserialize the share, perform&lt;br/&gt;    // individual verification, and store it for aggregation.&lt;br/&gt;    // For this example, we&amp;#39;ll just acknowledge receipt.&lt;br/&gt;    let _share_bytes = hex::decode(relay_payload_hex)?;&lt;br/&gt;    let _share = SignatureShare::deserialize(&amp;amp;_share_bytes)?;&lt;br/&gt;    let _identifier = frost::Identifier::try_from(signer_id_u16)?;&lt;br/&gt;&lt;br/&gt;    println!(&amp;#34;✅ Share from Signer {} processed (simplified).&amp;#34;, signer_id_u16);&lt;br/&gt;    Ok(())&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;pub fn simulate_frost_mailbox_coordinator() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {&lt;br/&gt;    let mut rng = thread_rng();&lt;br/&gt;    let (max_signers, min_signers) = (2, 2);&lt;br/&gt;&lt;br/&gt;    let (shares, pubkey_package) = frost::keys::generate_with_dealer(&lt;br/&gt;        max_signers,&lt;br/&gt;        min_signers,&lt;br/&gt;        frost::keys::IdentifierList::Default,&lt;br/&gt;        &amp;amp;mut rng,&lt;br/&gt;    )?;&lt;br/&gt;&lt;br/&gt;    let signer1_id = frost::Identifier::try_from(1 as u16)?;&lt;br/&gt;    let key_package1: frost::keys::KeyPackage = shares[&amp;amp;signer1_id].clone().try_into()?;&lt;br/&gt;    let signer2_id = frost::Identifier::try_from(2 as u16)?;&lt;br/&gt;    let key_package2: frost::keys::KeyPackage = shares[&amp;amp;signer2_id].clone().try_into()?;&lt;br/&gt;&lt;br/&gt;    let message = b&amp;#34;BIP-64MOD: Anchor Data Proposal v1&amp;#34;;&lt;br/&gt;&lt;br/&gt;    let (nonces1, comms1) = frost::round1::commit(key_package1.signing_share(), &amp;amp;mut rng);&lt;br/&gt;    let (nonces2, comms2) = frost::round1::commit(key_package2.signing_share(), &amp;amp;mut rng);&lt;br/&gt;&lt;br/&gt;    let mut session_commitments = BTreeMap::new();&lt;br/&gt;    session_commitments.insert(signer1_id, comms1);&lt;br/&gt;    session_commitments.insert(signer2_id, comms2);&lt;br/&gt;&lt;br/&gt;    let signing_package = frost::SigningPackage::new(session_commitments.clone(), message);&lt;br/&gt;&lt;br/&gt;    let share1 = frost::round2::sign(&amp;amp;signing_package, &amp;amp;nonces1, &amp;amp;key_package1)?;&lt;br/&gt;    let share1_hex = hex::encode(share1.serialize());&lt;br/&gt;&lt;br/&gt;    let share2 = frost::round2::sign(&amp;amp;signing_package, &amp;amp;nonces2, &amp;amp;key_package2)?;&lt;br/&gt;    let share2_hex = hex::encode(share2.serialize());&lt;br/&gt;&lt;br/&gt;    println!(&amp;#34;Coordinator listening for Nostr events (simulated)...&amp;#34;);&lt;br/&gt;&lt;br/&gt;    process_relay_share(&amp;amp;share1_hex, 1_u16, &amp;amp;signing_package, &amp;amp;pubkey_package)?;&lt;br/&gt;    process_relay_share(&amp;amp;share2_hex, 2_u16, &amp;amp;signing_package, &amp;amp;pubkey_package)?;&lt;br/&gt;    println!(&amp;#34;All required shares processed. Coordinator would now aggregate.&amp;#34;);&lt;br/&gt;&lt;br/&gt;    Ok(())&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Simulates a Signer producing a FROST signature share and preparing a Nostr event&lt;br/&gt;/// to be sent to a coordinator via a &amp;#34;mailbox&amp;#34; relay.&lt;br/&gt;///&lt;br/&gt;/// In a real ROAST setup, signers would generate their share and post it&lt;br/&gt;/// encrypted (e.g., using NIP-44) to a coordinator&amp;#39;s &amp;#34;mailbox&amp;#34; on a Nostr relay.&lt;br/&gt;/// This function demonstrates the creation of the signature share and the&lt;br/&gt;/// construction of a *simplified* Nostr event JSON.&lt;br/&gt;///&lt;br/&gt;/// # Arguments&lt;br/&gt;///&lt;br/&gt;/// * `_identifier` - The FROST identifier of the signer. (Currently unused in this specific function body).&lt;br/&gt;/// * `signing_package` - The FROST signing package received from the coordinator.&lt;br/&gt;/// * `nonces` - The signer&amp;#39;s nonces generated in Round 1.&lt;br/&gt;/// * `key_package` - The signer&amp;#39;s FROST key package.&lt;br/&gt;/// * `coordinator_pubkey` - The hex-encoded public key of the ROAST coordinator,&lt;br/&gt;///                          used to tag the Nostr event.&lt;br/&gt;///&lt;br/&gt;/// # Returns&lt;br/&gt;///&lt;br/&gt;/// A `Result` containing the JSON string of the Nostr event if successful,&lt;br/&gt;/// or a `Box&amp;lt;dyn std::error::Error&amp;gt;` if an error occurs.&lt;br/&gt;pub fn create_signer_event(&lt;br/&gt;    _identifier: frost::Identifier,&lt;br/&gt;    signing_package: &amp;amp;frost::SigningPackage,&lt;br/&gt;    nonces: &amp;amp;frost::round1::SigningNonces,&lt;br/&gt;    key_package: &amp;amp;frost::keys::KeyPackage,&lt;br/&gt;    coordinator_pubkey: &amp;amp;str, // The Hex pubkey of the ROAST coordinator&lt;br/&gt;) -&amp;gt; Result&amp;lt;String, Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {&lt;br/&gt;&lt;br/&gt;    // 1. Generate the partial signature share (Round 2 of FROST)&lt;br/&gt;    // This share is the core cryptographic output from the signer.&lt;br/&gt;    let share = frost::round2::sign(signing_package, nonces, key_package)?;&lt;br/&gt;    let share_bytes = share.serialize();&lt;br/&gt;    let share_hex = hex::encode(share_bytes);&lt;br/&gt;&lt;br/&gt;    // 2. Create a Session ID to tag the event&lt;br/&gt;    // This ID is derived from the signing package hash, allowing the coordinator&lt;br/&gt;    // to correlate shares belonging to the same signing session.&lt;br/&gt;    let mut hasher = Sha256::new();&lt;br/&gt;    hasher.update(signing_package.serialize()?);&lt;br/&gt;    let session_id = hex::encode(hasher.finalize());&lt;br/&gt;&lt;br/&gt;    // 3. Construct the Nostr Event JSON (Simplified)&lt;br/&gt;    // This JSON represents the event that a signer would post to a relay.&lt;br/&gt;    // In a production ROAST system, the &amp;#39;content&amp;#39; field (the signature share)&lt;br/&gt;    // would be encrypted for the coordinator using NIP-44.&lt;br/&gt;    let event = serde_json::json!({&lt;br/&gt;        &amp;#34;kind&amp;#34;: 4, // Example: Using Kind 4 (Private Message), though custom Kinds could be used for Sovereign Stack.&lt;br/&gt;        &amp;#34;pubkey&amp;#34;: hex::encode(key_package.verifying_key().serialize()?.as_slice()), // Signer&amp;#39;s public key&lt;br/&gt;        &amp;#34;created_at&amp;#34;: 1712050000, // Example timestamp&lt;br/&gt;        &amp;#34;tags&amp;#34;: [&lt;br/&gt;            [&amp;#34;p&amp;#34;, coordinator_pubkey],       // &amp;#39;p&amp;#39; tag: Directs the event to the coordinator.&lt;br/&gt;            [&amp;#34;i&amp;#34;, session_id],               // &amp;#39;i&amp;#39; tag: Provides a session identifier for filtering/requests.&lt;br/&gt;            [&amp;#34;t&amp;#34;, &amp;#34;frost-signature-share&amp;#34;]   // &amp;#39;t&amp;#39; tag: A searchable label for the event type.&lt;br/&gt;        ],&lt;br/&gt;        &amp;#34;content&amp;#34;: share_hex, // The actual signature share (would be encrypted in production).&lt;br/&gt;        &amp;#34;id&amp;#34;: &amp;#34;...&amp;#34;, // Event ID (filled by relay upon publishing)&lt;br/&gt;        &amp;#34;sig&amp;#34;: &amp;#34;...&amp;#34; // Event signature (filled by relay upon publishing)&lt;br/&gt;    });&lt;br/&gt;&lt;br/&gt;    Ok(event.to_string())&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;pub fn simulate_frost_mailbox_post_signer() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {&lt;br/&gt;    use rand::thread_rng;&lt;br/&gt;    use std::collections::BTreeMap;&lt;br/&gt;    use frost_secp256k1_tr as frost;&lt;br/&gt;&lt;br/&gt;    // This example simulates a single signer&amp;#39;s role in a ROAST mailbox post workflow.&lt;br/&gt;    // The general workflow is:&lt;br/&gt;    // 1. Coordinator sends a request for signatures (e.g., on a BIP-64MOD proposal).&lt;br/&gt;    // 2. Signers receive the proposal, perform local verification.&lt;br/&gt;    // 3. Each signer generates their signature share and posts it (encrypted) to a&lt;br/&gt;    //    Nostr relay, targeting the coordinator&amp;#39;s mailbox.&lt;br/&gt;    // 4. The coordinator collects enough shares to aggregate the final signature.&lt;br/&gt;&lt;br/&gt;    let mut rng = thread_rng();&lt;br/&gt;    // For this example, we simulate a 2-of-2 threshold for simplicity.&lt;br/&gt;    let (max_signers, min_signers) = (2, 2);&lt;br/&gt;&lt;br/&gt;    ////////////////////////////////////////////////////////////////////////////&lt;br/&gt;    // 1. Key Generation (Simulated Trusted Dealer)&lt;br/&gt;    ////////////////////////////////////////////////////////////////////////////&lt;br/&gt;    // In a real distributed setup, this would be DKG. Here, a &amp;#34;trusted dealer&amp;#34;&lt;br/&gt;    // generates the shares and public key package.&lt;br/&gt;    let (shares, _pubkey_package) = frost::keys::generate_with_dealer(&lt;br/&gt;        max_signers,&lt;br/&gt;        min_signers,&lt;br/&gt;        frost::keys::IdentifierList::Default,&lt;br/&gt;        &amp;amp;mut rng,&lt;br/&gt;    )?;&lt;br/&gt;&lt;br/&gt;    // For a 2-of-2 scheme, we have two signers. Let&amp;#39;s pick signer 1.&lt;br/&gt;    let signer1_id = frost::Identifier::try_from(1 as u16)?;&lt;br/&gt;    let key_package1: frost::keys::KeyPackage = shares[&amp;amp;signer1_id].clone().try_into()?;&lt;br/&gt;&lt;br/&gt;    let signer2_id = frost::Identifier::try_from(2 as u16)?;&lt;br/&gt;    let key_package2: frost::keys::KeyPackage = shares[&amp;amp;signer2_id].clone().try_into()?;&lt;br/&gt;&lt;br/&gt;    // The message that is to be signed (e.g., a hash of a Git commit or a Nostr event ID).&lt;br/&gt;    let message = b&amp;#34;This is a test message for ROAST mailbox post.&amp;#34;;&lt;br/&gt;&lt;br/&gt;    ////////////////////////////////////////////////////////////////////////////&lt;br/&gt;    // 2. Round 1: Commitment Phase (Signer&amp;#39;s role)&lt;br/&gt;    ////////////////////////////////////////////////////////////////////////////&lt;br/&gt;    // Each signer generates nonces and commitments.&lt;br/&gt;    let (nonces1, comms1) = frost::round1::commit(key_package1.signing_share(), &amp;amp;mut rng);&lt;br/&gt;    let (nonces2, comms2) = frost::round1::commit(key_package2.signing_share(), &amp;amp;mut rng);&lt;br/&gt;    &lt;br/&gt;    // The coordinator collects these commitments. Here, we simulate by putting them in a BTreeMap.&lt;br/&gt;    let mut session_commitments = BTreeMap::new();&lt;br/&gt;    session_commitments.insert(signer1_id, comms1);&lt;br/&gt;    session_commitments.insert(signer2_id, comms2);&lt;br/&gt;&lt;br/&gt;    ////////////////////////////////////////////////////////////////////////////&lt;br/&gt;    // 3. Signing Package Creation (Coordinator&amp;#39;s role, simulated for context)&lt;br/&gt;    ////////////////////////////////////////////////////////////////////////////&lt;br/&gt;    // The coordinator combines the collected commitments and the message to be signed&lt;br/&gt;    // into a signing package, which is then sent back to the signers.&lt;br/&gt;    let signing_package = frost::SigningPackage::new(session_commitments, message);&lt;br/&gt;&lt;br/&gt;    // Dummy coordinator public key. In a real scenario, this would be the&lt;br/&gt;    // actual public key of the ROAST coordinator, used for event tagging&lt;br/&gt;    // and encryption (NIP-44).&lt;br/&gt;    let coordinator_pubkey_hex = &amp;#34;0000000000000000000000000000000000000000000000000000000000000001&amp;#34;;&lt;br/&gt;&lt;br/&gt;    ////////////////////////////////////////////////////////////////////////////&lt;br/&gt;    // 4. Create the Signer Event (Signer&amp;#39;s role)&lt;br/&gt;    ////////////////////////////////////////////////////////////////////////////&lt;br/&gt;    // We demonstrate for signer 1. Signer 2 would perform a similar action.&lt;br/&gt;    let event_json_signer1 = create_signer_event(&lt;br/&gt;        signer1_id,&lt;br/&gt;        &amp;amp;signing_package,&lt;br/&gt;        &amp;amp;nonces1,&lt;br/&gt;        &amp;amp;key_package1,&lt;br/&gt;        coordinator_pubkey_hex,&lt;br/&gt;    )?;&lt;br/&gt;&lt;br/&gt;    println!(&amp;#34;Generated Nostr Event for Signer 1 Mailbox Post:&lt;br/&gt;{}&amp;#34;, event_json_signer1);&lt;br/&gt;&lt;br/&gt;    // Similarly, Signer 2 would generate their event:&lt;br/&gt;    let event_json_signer2 = create_signer_event(&lt;br/&gt;        signer2_id,&lt;br/&gt;        &amp;amp;signing_package,&lt;br/&gt;        &amp;amp;nonces2,&lt;br/&gt;        &amp;amp;key_package2,&lt;br/&gt;        coordinator_pubkey_hex,&lt;br/&gt;    )?;&lt;br/&gt;    println!(&amp;#34;Generated Nostr Event for Signer 2 Mailbox Post:&lt;br/&gt;{}&amp;#34;, event_json_signer2);&lt;br/&gt;&lt;br/&gt;    Ok(())&lt;br/&gt;}
    </content>
    <updated>2026-04-06T04:16:17Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsw65vq2ktwecgyvkvq560nr0cs00pgg8pvn9haky83r2fetpm5x3qpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5ha2rq3</id>
    
      <title type="html">#[cfg(all(not(debug_assertions), feature = &amp;#34;nostr&amp;#34;))] ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsw65vq2ktwecgyvkvq560nr0cs00pgg8pvn9haky83r2fetpm5x3qpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5ha2rq3" />
    <content type="html">
      #[cfg(all(not(debug_assertions), feature = &amp;#34;nostr&amp;#34;))]&lt;br/&gt;#[tokio::main]&lt;br/&gt;async fn main() {&lt;br/&gt;    use std::fs;&lt;br/&gt;    use std::path::PathBuf;&lt;br/&gt;&lt;br/&gt;    let manifest_dir = PathBuf::from(std::env::var(&amp;#34;CARGO_MANIFEST_DIR&amp;#34;).unwrap());&lt;br/&gt;    let crate_src_path = manifest_dir.join(&amp;#34;src&amp;#34;).join(&amp;#34;online_relays_gps.csv&amp;#34;);&lt;br/&gt;&lt;br/&gt;    // Only download if the file doesn&amp;#39;t exist or is empty&lt;br/&gt;    if !crate_src_path.exists() || fs::metadata(&amp;amp;crate_src_path).map(|m| m.len() == 0).unwrap_or(true) {&lt;br/&gt;        println!(&amp;#34;cargo:warning=Downloading online_relays_gps.csv...&amp;#34;);&lt;br/&gt;        let url = &amp;#34;&lt;a href=&#34;https://raw.githubusercontent.com/permissionlesstech/bitchat/main/relays/online_relays_gps.csv&amp;#34&#34;&gt;https://raw.githubusercontent.com/permissionlesstech/bitchat/main/relays/online_relays_gps.csv&amp;#34&lt;/a&gt;;;&lt;br/&gt;        match reqwest::get(url).await {&lt;br/&gt;            Ok(response) =&amp;gt; {&lt;br/&gt;                if response.status().is_success() {&lt;br/&gt;                    match response.text().await {&lt;br/&gt;                        Ok(content) =&amp;gt; {&lt;br/&gt;                            fs::write(&amp;amp;crate_src_path, content).expect(&amp;#34;Unable to write online_relays_gps.csv&amp;#34;);&lt;br/&gt;                            println!(&amp;#34;cargo:warning=Successfully downloaded online_relays_gps.csv to {:?}&amp;#34;, crate_src_path);&lt;br/&gt;                        },&lt;br/&gt;                        Err(e) =&amp;gt; {&lt;br/&gt;                            println!(&amp;#34;cargo:warning=Failed to get text from response: {}&amp;#34;, e);&lt;br/&gt;                        }&lt;br/&gt;                    }&lt;br/&gt;                } else {&lt;br/&gt;                    println!(&amp;#34;cargo:warning=Failed to download online_relays_gps.csv: HTTP status {}&amp;#34;, response.status());&lt;br/&gt;                }&lt;br/&gt;            },&lt;br/&gt;            Err(e) =&amp;gt; {&lt;br/&gt;                println!(&amp;#34;cargo:warning=Failed to fetch online_relays_gps.csv: {}&amp;#34;, e);&lt;br/&gt;            }&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[cfg(not(all(not(debug_assertions), feature = &amp;#34;nostr&amp;#34;)))]&lt;br/&gt;fn main() {&lt;br/&gt;    // Placeholder for when the nostr feature is not enabled or in debug mode&lt;br/&gt;    println!(&amp;#34;cargo:warning=Skipping online_relays_gps.csv download (nostr feature not enabled or debug mode)&amp;#34;);&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-06T04:16:05Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsql9336v9cl6z9l9qr6ggpt0lruuwzuk45zs3k6zhgnzhht8kjjsqpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5hyn664</id>
    
      <title type="html">[package] name = &amp;#34;get_file_hash_core&amp;#34; version = { ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsql9336v9cl6z9l9qr6ggpt0lruuwzuk45zs3k6zhgnzhht8kjjsqpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5hyn664" />
    <content type="html">
      [package]&lt;br/&gt;name = &amp;#34;get_file_hash_core&amp;#34;&lt;br/&gt;version = { workspace = true }&lt;br/&gt;edition = { workspace = true }&lt;br/&gt;description = { workspace = true }&lt;br/&gt;license = { workspace = true }&lt;br/&gt;documentation = { workspace = true }&lt;br/&gt;homepage = { workspace = true }&lt;br/&gt;repository = { workspace = true }&lt;br/&gt;authors = { workspace = true }&lt;br/&gt;&lt;br/&gt;[features]&lt;br/&gt;nostr = [&amp;#34;dep:nostr&amp;#34;, &amp;#34;dep:nostr-sdk&amp;#34;, &amp;#34;dep:serde_json&amp;#34;, &amp;#34;dep:sha2&amp;#34;, &amp;#34;dep:hex&amp;#34;, &amp;#34;dep:reqwest&amp;#34;, &amp;#34;dep:tokio&amp;#34;, &amp;#34;dep:csv&amp;#34;, &amp;#34;dep:url&amp;#34;, &amp;#34;dep:frost-secp256k1-tr&amp;#34;, &amp;#34;dep:rand&amp;#34;]&lt;br/&gt;frost = [&amp;#34;dep:nostr&amp;#34;, &amp;#34;dep:nostr-sdk&amp;#34;, &amp;#34;dep:serde_json&amp;#34;, &amp;#34;dep:sha2&amp;#34;, &amp;#34;dep:hex&amp;#34;, &amp;#34;dep:reqwest&amp;#34;, &amp;#34;dep:tokio&amp;#34;, &amp;#34;dep:csv&amp;#34;, &amp;#34;dep:url&amp;#34;, &amp;#34;dep:frost-secp256k1-tr&amp;#34;, &amp;#34;dep:rand&amp;#34;]&lt;br/&gt;&lt;br/&gt;[dependencies]&lt;br/&gt;sha2 = { workspace = true, optional = true }&lt;br/&gt;nostr = { workspace = true, optional = true }&lt;br/&gt;serde_json = { workspace = true, optional = true }&lt;br/&gt;nostr-sdk = { workspace = true, optional = true }&lt;br/&gt;hex = { workspace = true, optional = true }&lt;br/&gt;csv = { workspace = true, optional = true }&lt;br/&gt;url = { workspace = true, optional = true }&lt;br/&gt;frost-secp256k1-tr = { workspace = true, optional = true }&lt;br/&gt;rand = { workspace = true, optional = true }&lt;br/&gt;&lt;br/&gt;[dev-dependencies]&lt;br/&gt;sha2 = { workspace = true }&lt;br/&gt;tempfile = { workspace = true }&lt;br/&gt;nostr = { workspace = true }&lt;br/&gt;nostr-sdk = { workspace = true }&lt;br/&gt;serde_json = { workspace = true }&lt;br/&gt;hex = { workspace = true }&lt;br/&gt;tokio = { workspace = true, features = [&amp;#34;macros&amp;#34;, &amp;#34;rt-multi-thread&amp;#34;] }&lt;br/&gt;csv = { workspace = true }&lt;br/&gt;url = { workspace = true }&lt;br/&gt;frost-secp256k1-tr = { workspace = true }&lt;br/&gt;serial_test = { workspace = true, features = [&amp;#34;test_logging&amp;#34;] }&lt;br/&gt;log = { workspace = true }&lt;br/&gt;&lt;br/&gt;[build-dependencies]&lt;br/&gt;reqwest = { workspace = true, features = [&amp;#34;json&amp;#34;], optional = true }&lt;br/&gt;tokio = { workspace = true, features = [&amp;#34;macros&amp;#34;, &amp;#34;rt-multi-thread&amp;#34;], optional = true }&lt;br/&gt;
    </content>
    <updated>2026-04-06T04:15:53Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsrslymz5axdxcp4t9u4wy496afc3xen639vpmue8k669lz053p8acpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5rfwp84</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsrslymz5axdxcp4t9u4wy496afc3xen639vpmue8k669lz053p8acpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5rfwp84" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;/// Command line interface module&lt;br/&gt;use n34::cli;&lt;br/&gt;// /// N34 errors&lt;br/&gt;// use n34::error;&lt;br/&gt;// /// Nostr utils module&lt;br/&gt;// use n34::nostr_utils;&lt;br/&gt;&lt;br/&gt;use std::{&lt;br/&gt;    process::ExitCode,&lt;br/&gt;    sync::atomic::{AtomicBool, Ordering},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use clap::Parser;&lt;br/&gt;use clap_verbosity_flag::Verbosity;&lt;br/&gt;use tracing::Level;&lt;br/&gt;use tracing_subscriber::{Layer, filter, layer::SubscriberExt};&lt;br/&gt;&lt;br/&gt;use self::cli::Cli;&lt;br/&gt;&lt;br/&gt;/// Whether the editor is currently open. Prevents logging while the editor is&lt;br/&gt;/// open.&lt;br/&gt;static EDITOR_OPEN: AtomicBool = AtomicBool::new(false);&lt;br/&gt;&lt;br/&gt;/// Configures the logging level based on the provided verbosity.&lt;br/&gt;///&lt;br/&gt;/// When verbosity is set to TRACE, includes file and line numbers in logs.&lt;br/&gt;fn set_log_level(verbosity: Verbosity) {&lt;br/&gt;    let is_trace = verbosity&lt;br/&gt;        .tracing_level()&lt;br/&gt;        .is_some_and(|l| l == tracing::Level::TRACE);&lt;br/&gt;&lt;br/&gt;    let logs_filter = filter::dynamic_filter_fn(move |m, _| {&lt;br/&gt;        // Disable all logs while editor is open&lt;br/&gt;        verbosity.tracing_level().unwrap_or(Level::ERROR) &amp;gt;= *m.level()&lt;br/&gt;            &amp;amp;&amp;amp; !EDITOR_OPEN.load(Ordering::Relaxed)&lt;br/&gt;    });&lt;br/&gt;&lt;br/&gt;    let logs_layer = tracing_subscriber::fmt::layer()&lt;br/&gt;        .with_file(is_trace)&lt;br/&gt;        .with_line_number(is_trace)&lt;br/&gt;        .without_time();&lt;br/&gt;    let subscriber = tracing_subscriber::registry().with(logs_layer.with_filter(logs_filter));&lt;br/&gt;    tracing::subscriber::set_global_default(subscriber).ok();&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[tokio::main]&lt;br/&gt;async fn main() -&amp;gt; ExitCode {&lt;br/&gt;    let cli = match cli::post_cli(Cli::parse()) {&lt;br/&gt;        Ok(cli) =&amp;gt; cli,&lt;br/&gt;        Err(err) =&amp;gt; {&lt;br/&gt;            eprintln!(&amp;#34;{err}&amp;#34;);&lt;br/&gt;            return ExitCode::FAILURE;&lt;br/&gt;        }&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    set_log_level(cli.verbosity);&lt;br/&gt;&lt;br/&gt;    if let Err(err) = cli.run().await {&lt;br/&gt;        tracing::error!(&amp;#34;{err}&amp;#34;);&lt;br/&gt;        return err.exit_code();&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    ExitCode::SUCCESS&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-06T04:15:21Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsrtffsdhsymts0zklf945cwd4mxujwwhphfg20jypxelhfqll9h0gpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5u0nfhm</id>
    
      <title type="html">//! A simple command-line tool that calculates and displays the ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsrtffsdhsymts0zklf945cwd4mxujwwhphfg20jypxelhfqll9h0gpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5u0nfhm" />
    <content type="html">
      //! A simple command-line tool that calculates and displays the SHA-256 hash of&lt;br/&gt;//! its own source file.&lt;br/&gt;//!&lt;br/&gt;//! This utility demonstrates how to use the `get_file_hash!` macro to obtain&lt;br/&gt;//! the hash of a specified file at compile time and incorporate it into runtime&lt;br/&gt;//! logic.&lt;br/&gt;use get_file_hash::{BUILD_HASH, CARGO_TOML_HASH, LIB_HASH};&lt;br/&gt;use get_file_hash_core::get_file_hash;&lt;br/&gt;use sha2::{Digest, Sha256};&lt;br/&gt;&lt;br/&gt;const README_TEMPLATE_PART0: &amp;amp;str = r##&amp;#34;# `get_file_hash` macro&lt;br/&gt;&lt;br/&gt;This project provides a Rust procedural macro, `get_file_hash!`, designed to compute the SHA-256 hash of a specified file at compile time. This hash is then embedded directly into your compiled executable. This feature is invaluable for:&lt;br/&gt;&lt;br/&gt;*   **Integrity Verification:** Ensuring the deployed code hasn&amp;#39;t been tampered with.&lt;br/&gt;*   **Versioning:** Embedding a unique identifier linked to the exact source code version.&lt;br/&gt;*   **Cache Busting:** Generating unique names for assets based on their content.&lt;br/&gt;&lt;br/&gt;## Project Structure&lt;br/&gt;&lt;br/&gt;*   `get_file_hash_core`: A foundational crate containing the `get_file_hash!` macro definition.&lt;br/&gt;*   `get_file_hash`: The main library crate that re-exports the macro.&lt;br/&gt;*   `src/bin/get_file_hash.rs`: An example executable demonstrating the macro&amp;#39;s usage by hashing its own source file and updating this `README.md`.&lt;br/&gt;*   `build.rs`: A build script that also utilizes the `get_file_hash!` macro to hash `Cargo.toml` during the build process.&lt;br/&gt;&lt;br/&gt;## Usage of `get_file_hash!` Macro&lt;br/&gt;&lt;br/&gt;To use the `get_file_hash!` macro, ensure you have `get_file_hash` (or `get_file_hash_core` for direct usage) as a dependency in your `Cargo.toml`.&lt;br/&gt;&lt;br/&gt;### Example&lt;br/&gt;&lt;br/&gt;```rust&lt;br/&gt;use get_file_hash::get_file_hash;&lt;br/&gt;use get_file_hash::CARGO_TOML_HASH;&lt;br/&gt;use sha2::{Digest, Sha256};&lt;br/&gt;&lt;br/&gt;fn main() {&lt;br/&gt;    // The macro resolves the path relative to CARGO_MANIFEST_DIR&lt;br/&gt;    let readme_hash = get_file_hash!(&amp;#34;src/bin/readme.rs&amp;#34;);&lt;br/&gt;    let lib_hash = get_file_hash!(&amp;#34;src/lib.rs&amp;#34;);&lt;br/&gt;    println!(&amp;#34;The SHA-256 hash of src/lib.rs is: {}&amp;#34;, lib_hash);&lt;br/&gt;    println!(&amp;#34;The SHA-256 hash of src/bin/readme.rs is: {}&amp;#34;, readme_hash);&lt;br/&gt;    println!(&amp;#34;The SHA-256 hash of Cargo.toml is: {}&amp;#34;, CARGO_TOML_HASH);&lt;br/&gt;}&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;&amp;#34;##;&lt;br/&gt;&lt;br/&gt;const README_TEMPLATE_PART1: &amp;amp;str = r&amp;#34;## Release&lt;br/&gt;## [`README.md`](./README.md)&lt;br/&gt;&lt;br/&gt;```bash&lt;br/&gt;cargo run --bin readme &amp;gt; README.md&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;## [`src/bin/readme.rs`](src/bin/readme.rs)&lt;br/&gt;&lt;br/&gt;*   **Target File:** `src/bin/readme.rs`&lt;br/&gt;&amp;#34;;&lt;br/&gt;&lt;br/&gt;const README_TEMPLATE_PART2: &amp;amp;str = r&amp;#34;##&lt;br/&gt;&lt;br/&gt;## [`build.rs`](build.rs)&lt;br/&gt;&lt;br/&gt;*   **Target File:** `build.rs`&lt;br/&gt;&amp;#34;;&lt;br/&gt;&lt;br/&gt;const README_TEMPLATE_PART3: &amp;amp;str = r&amp;#34;##&lt;br/&gt;&lt;br/&gt;## [`Cargo.toml`](Cargo.toml)&lt;br/&gt;&lt;br/&gt;*   **Target File:** `Cargo.toml`&lt;br/&gt;&amp;#34;;&lt;br/&gt;&lt;br/&gt;const README_TEMPLATE_PART4: &amp;amp;str = r&amp;#34;##&lt;br/&gt;&lt;br/&gt;## [`src/lib.rs`](src/lib.rs)&lt;br/&gt;&lt;br/&gt;*   **Target File:** `src/lib.rs`&lt;br/&gt;&amp;#34;;&lt;br/&gt;&lt;br/&gt;const README_TEMPLATE_PART_NIP34: &amp;amp;str = r&amp;#34;## NIP-34 Integration: Git Repository Events on Nostr&lt;br/&gt;&lt;br/&gt;This library provides a set of powerful macros and functions for integrating Git repository events with the Nostr protocol, adhering to the [NIP-34: Git Repositories on Nostr](&lt;a href=&#34;https://github.com/nostr-protocol/nips/blob/master/34.md&#34;&gt;https://github.com/nostr-protocol/nips/blob/master/34.md&lt;/a&gt;) specification.&lt;br/&gt;&lt;br/&gt;These tools allow you to publish various Git-related events to Nostr relays, enabling decentralized tracking and collaboration for your code repositories.&lt;br/&gt;&lt;br/&gt;### Available NIP-34 Macros&lt;br/&gt;&lt;br/&gt;Each macro provides a convenient way to publish specific NIP-34 event kinds:&lt;br/&gt;&lt;br/&gt;*   [`repository_announcement!`](#repository_announcement)&lt;br/&gt;    *   Publishes a `Repository Announcement` event (Kind 30617) to announce a new or updated Git repository.&lt;br/&gt;*   [`publish_patch!`](#publish_patch)&lt;br/&gt;    *   Publishes a `Patch` event (Kind 1617) containing a Git patch (diff) for a specific commit.&lt;br/&gt;*   [`publish_pull_request!`](#publish_pull_request)&lt;br/&gt;    *   Publishes a `Pull Request` event (Kind 1618) to propose changes and facilitate code review.&lt;br/&gt;*   [`publish_pr_update!`](#publish_pr_update)&lt;br/&gt;    *   Publishes a `Pull Request Update` event (Kind 1619) to update an existing pull request.&lt;br/&gt;*   [`publish_repository_state!`](#publish_repository_state)&lt;br/&gt;    *   Publishes a `Repository State` event (Kind 1620) to announce the current state of a branch (e.g., its latest commit).&lt;br/&gt;*   [`publish_issue!`](#publish_issue)&lt;br/&gt;    *   Publishes an `Issue` event (Kind 1621) to report bugs, request features, or track tasks.&lt;br/&gt;&lt;br/&gt;### Running NIP-34 Examples&lt;br/&gt;&lt;br/&gt;To see these macros in action, navigate to the `examples/` directory and run each example individually with the `nostr` feature enabled:&lt;br/&gt;&lt;br/&gt;```bash&lt;br/&gt;cargo run --example repository_announcement --features nostr&lt;br/&gt;cargo run --example publish_patch --features nostr&lt;br/&gt;cargo run --example publish_pull_request --features nostr&lt;br/&gt;cargo run --example publish_pr_update --features nostr&lt;br/&gt;cargo run --example publish_repository_state --features nostr&lt;br/&gt;cargo run --example publish_issue --features nostr&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;&amp;#34;;&lt;br/&gt;&lt;br/&gt;/// The main entry point of the application.&lt;br/&gt;///&lt;br/&gt;/// This function calculates the SHA-256 hash of the `get_file_hash.rs` source&lt;br/&gt;/// file using a custom procedural macro and then prints the hash to the&lt;br/&gt;/// console. It also includes a basic integrity verification check.&lt;br/&gt;fn main() {&lt;br/&gt;    // Calculate the SHA-256 hash of the current file (`readme.rs`) at&lt;br/&gt;    // compile time. The `get_file_hash!` macro reads the file content and&lt;br/&gt;    // computes its hash.&lt;br/&gt;    let self_hash = get_file_hash!(&amp;#34;readme.rs&amp;#34;);&lt;br/&gt;&lt;br/&gt;    let status_message = if self_hash.starts_with(&amp;#34;e3b0&amp;#34;) {&lt;br/&gt;        &amp;#34;Warning: This hash represents an empty file.&amp;#34;&lt;br/&gt;    } else {&lt;br/&gt;        &amp;#34;Integrity Verified.&amp;#34;&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    let build_message = if BUILD_HASH.starts_with(&amp;#34;e3b0&amp;#34;) {&lt;br/&gt;        &amp;#34;Warning: This hash represents an empty file.&amp;#34;&lt;br/&gt;    } else {&lt;br/&gt;        &amp;#34;Integrity Verified.&amp;#34;&lt;br/&gt;    };&lt;br/&gt;    let cargo_message = if CARGO_TOML_HASH.starts_with(&amp;#34;e3b0&amp;#34;) {&lt;br/&gt;        &amp;#34;Warning: This hash represents an empty file.&amp;#34;&lt;br/&gt;    } else {&lt;br/&gt;        &amp;#34;Integrity Verified.&amp;#34;&lt;br/&gt;    };&lt;br/&gt;    let lib_message = if LIB_HASH.starts_with(&amp;#34;e3b0&amp;#34;) {&lt;br/&gt;        &amp;#34;Warning: This hash represents an empty file.&amp;#34;&lt;br/&gt;    } else {&lt;br/&gt;        &amp;#34;Integrity Verified.&amp;#34;&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    print!(&amp;#34;{}{}{}&amp;#34;, README_TEMPLATE_PART0, README_TEMPLATE_PART1, README_TEMPLATE_PART_NIP34);&lt;br/&gt;    println!(&amp;#34;*   **SHA-256 Hash:** {}&amp;#34;, self_hash);&lt;br/&gt;    println!(&amp;#34;*   **Status:** {}.\n&amp;#34;, status_message);&lt;br/&gt;    //&lt;br/&gt;    print!(&amp;#34;{}&amp;#34;, README_TEMPLATE_PART2);&lt;br/&gt;    println!(&amp;#34;*   **SHA-256 Hash:** {}&amp;#34;, BUILD_HASH);&lt;br/&gt;    println!(&amp;#34;*   **Status:** {}.\n&amp;#34;, build_message);&lt;br/&gt;    //&lt;br/&gt;    print!(&amp;#34;{}&amp;#34;, README_TEMPLATE_PART3);&lt;br/&gt;    println!(&amp;#34;*   **SHA-256 Hash:** {}&amp;#34;, CARGO_TOML_HASH);&lt;br/&gt;    println!(&amp;#34;*   **Status:** {}.\n&amp;#34;, cargo_message);&lt;br/&gt;    //&lt;br/&gt;    print!(&amp;#34;{}&amp;#34;, README_TEMPLATE_PART4);&lt;br/&gt;    println!(&amp;#34;*   **SHA-256 Hash:** {}&amp;#34;, LIB_HASH);&lt;br/&gt;    println!(&amp;#34;*   **Status:** {}.\n&amp;#34;, lib_message);&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-06T04:15:41Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsdpqcj233mvnx6u77d9y4t90xzdmpndsna0eegns8wvz74grj2nlspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q53gaygh</id>
    
      <title type="html">// n34-relay - A nostr GRASP relay implementation // Copyright ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsdpqcj233mvnx6u77d9y4t90xzdmpndsna0eegns8wvz74grj2nlspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q53gaygh" />
    <content type="html">
      // n34-relay - A nostr GRASP relay implementation&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU Affero General Public License as published&lt;br/&gt;// by the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU Affero General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU Affero General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/agpl-3.0&amp;gt&#34;&gt;https://gnu.org/licenses/agpl-3.0&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::{net::SocketAddr, process::ExitCode, sync::Arc};&lt;br/&gt;&lt;br/&gt;use axum::Extension;&lt;br/&gt;use hyper::{Method, header};&lt;br/&gt;use tokio::signal;&lt;br/&gt;use tower_http::{&lt;br/&gt;    cors,&lt;br/&gt;    decompression::RequestDecompressionLayer,&lt;br/&gt;    trace::{DefaultMakeSpan, TraceLayer},&lt;br/&gt;};&lt;br/&gt;use tracing::level_filters::LevelFilter;&lt;br/&gt;use tracing_subscriber::{EnvFilter, Layer, layer::SubscriberExt};&lt;br/&gt;&lt;br/&gt;/// Relay endpoints&lt;br/&gt;use n34_relay::endpoints;&lt;br/&gt;/// Relay errors.&lt;br/&gt;use n34_relay::errors;&lt;br/&gt;// /// Extension traits&lt;br/&gt;// use n34_relay::ext_traits;&lt;br/&gt;/// GRASP git server&lt;br/&gt;use n34_relay::git_server;&lt;br/&gt;// /// Relay pathes.&lt;br/&gt;// use n34_relay::pathes;&lt;br/&gt;// /// Raw axum websocket&lt;br/&gt;// use n34_relay::raw_websocket;&lt;br/&gt;/// Our relay.&lt;br/&gt;use n34_relay::relay;&lt;br/&gt;/// Relay configuration.&lt;br/&gt;use n34_relay::relay_config;&lt;br/&gt;/// Router state&lt;br/&gt;use n34_relay::router_state;&lt;br/&gt;/// Some useful utils.&lt;br/&gt;use n34_relay::utils;&lt;br/&gt;&lt;br/&gt;use self::{errors::RelayResult, relay_config::RelayConfig, router_state::RouterState};&lt;br/&gt;&lt;br/&gt;/// Sets up default logging with two outputs, stderr and a log file.&lt;br/&gt;///&lt;br/&gt;/// Log level for stderr is controlled by `RUST_LOG` environment variable,&lt;br/&gt;/// defaults to `ERROR`. The log file always uses `TRACE` level.&lt;br/&gt;fn setup_logs() -&amp;gt; errors::RelayResult&amp;lt;()&amp;gt; {&lt;br/&gt;    tracing::subscriber::set_global_default(&lt;br/&gt;        tracing_subscriber::registry()&lt;br/&gt;            .with(&lt;br/&gt;                tracing_subscriber::fmt::layer()&lt;br/&gt;                    .with_ansi(true)&lt;br/&gt;                    .with_writer(std::io::stderr)&lt;br/&gt;                    .without_time()&lt;br/&gt;                    .with_filter(EnvFilter::from_default_env()),&lt;br/&gt;            )&lt;br/&gt;            .with(&lt;br/&gt;                tracing_subscriber::fmt::layer()&lt;br/&gt;                    .with_ansi(false)&lt;br/&gt;                    .with_writer(utils::logs_file()?)&lt;br/&gt;                    .with_file(false)&lt;br/&gt;                    .with_line_number(false)&lt;br/&gt;                    .with_filter(LevelFilter::TRACE),&lt;br/&gt;            ),&lt;br/&gt;    )&lt;br/&gt;    .ok();&lt;br/&gt;&lt;br/&gt;    Ok(())&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;async fn shutdown_signal() {&lt;br/&gt;    let ctrl_c = async {&lt;br/&gt;        signal::ctrl_c()&lt;br/&gt;            .await&lt;br/&gt;            .expect(&amp;#34;Failed to install CTRL&#43;C handler&amp;#34;);&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    #[cfg(unix)]&lt;br/&gt;    let terminate = async {&lt;br/&gt;        use tokio::signal::unix::SignalKind;&lt;br/&gt;&lt;br/&gt;        signal::unix::signal(SignalKind::terminate())&lt;br/&gt;            .expect(&amp;#34;Failed to create SIGTERM handler&amp;#34;)&lt;br/&gt;            .recv()&lt;br/&gt;            .await;&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    #[cfg(not(unix))]&lt;br/&gt;    let terminate = std::future::pending::&amp;lt;()&amp;gt;();&lt;br/&gt;&lt;br/&gt;    tokio::select! {&lt;br/&gt;        _ = ctrl_c =&amp;gt; {},&lt;br/&gt;        _ = terminate =&amp;gt; {},&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;async fn try_main() -&amp;gt; RelayResult&amp;lt;()&amp;gt; {&lt;br/&gt;    setup_logs()?;&lt;br/&gt;    let config = Arc::new(RelayConfig::reload()?);&lt;br/&gt;    let relay_db = config.get_relay_db().await?;&lt;br/&gt;    let n34_relay = Arc::new(relay::build_relay(Arc::clone(&amp;amp;config), Arc::clone(&amp;amp;relay_db)).await);&lt;br/&gt;    let addr = SocketAddr::new(config.net.ip, config.net.port);&lt;br/&gt;&lt;br/&gt;    tracing::debug!(&amp;#34;Running relay with configuration: {config:#?}&amp;#34;);&lt;br/&gt;    tracing::info!(&amp;#34;Relay is running at `{}`&amp;#34;, n34_relay.url().await);&lt;br/&gt;&lt;br/&gt;    let mut app = axum::Router::new()&lt;br/&gt;        // main handler. GET and POST&lt;br/&gt;        .route(&amp;#34;/&amp;#34;, axum::routing::get(endpoints::main_handler).post(endpoints::main_handler));&lt;br/&gt;&lt;br/&gt;    if config.grasp.enable {&lt;br/&gt;        tracing::info!(&amp;#34;Git server is running&amp;#34;);&lt;br/&gt;        app = app.merge(git_server::router(&amp;amp;config));&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    app = app&lt;br/&gt;        // enable cross-origin access&lt;br/&gt;        .route(&lt;br/&gt;            &amp;#34;/&amp;#34;,&lt;br/&gt;            axum::routing::options(|| async { hyper::StatusCode::NO_CONTENT }),&lt;br/&gt;        )&lt;br/&gt;        .layer(&lt;br/&gt;            cors::CorsLayer::new()&lt;br/&gt;                .allow_origin(cors::Any)&lt;br/&gt;                .allow_methods([Method::GET, Method::POST])&lt;br/&gt;                .allow_headers([header::CONTENT_TYPE]),&lt;br/&gt;        )&lt;br/&gt;        .layer(&lt;br/&gt;            TraceLayer::new_for_http()&lt;br/&gt;                .make_span_with(DefaultMakeSpan::default().include_headers(true)),&lt;br/&gt;        )&lt;br/&gt;        .layer(RequestDecompressionLayer::new())&lt;br/&gt;        .layer(Extension(Arc::new(RouterState::new(&lt;br/&gt;            config, n34_relay, relay_db,&lt;br/&gt;        ))));&lt;br/&gt;&lt;br/&gt;    axum::serve(&lt;br/&gt;        tokio::net::TcpListener::bind(addr).await?,&lt;br/&gt;        app.into_make_service_with_connect_info::&amp;lt;SocketAddr&amp;gt;(),&lt;br/&gt;    )&lt;br/&gt;    .with_graceful_shutdown(shutdown_signal())&lt;br/&gt;    .await?;&lt;br/&gt;    Ok(())&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[tokio::main]&lt;br/&gt;async fn main() -&amp;gt; ExitCode {&lt;br/&gt;    if let Err(err) = try_main().await {&lt;br/&gt;        eprintln!(&amp;#34;{err}&amp;#34;);&lt;br/&gt;        return ExitCode::FAILURE;&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    tracing::info!(&amp;#34;Exited gracefully without any errors&amp;#34;);&lt;br/&gt;    ExitCode::SUCCESS&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-06T04:15:08Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsws0ma0e2rsyg09kl47my628k95rky9x8n064r7xwyrhfth99tx5gpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5xxsap8</id>
    
      <title type="html">//! A simple command-line tool that calculates and displays the ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsws0ma0e2rsyg09kl47my628k95rky9x8n064r7xwyrhfth99tx5gpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5xxsap8" />
    <content type="html">
      //! A simple command-line tool that calculates and displays the SHA-256 hash of&lt;br/&gt;//! its own source file.&lt;br/&gt;//!&lt;br/&gt;//! This utility demonstrates how to use the `get_file_hash!` macro to obtain&lt;br/&gt;//! the hash of a specified file at compile time and incorporate it into runtime&lt;br/&gt;//! logic.&lt;br/&gt;&lt;br/&gt;use get_file_hash_core::get_file_hash;&lt;br/&gt;use sha2::{Digest, Sha256};&lt;br/&gt;&lt;br/&gt;const README_TEMPLATE_PART1: &amp;amp;str = r##&amp;#34;# `get_file_hash` macro&lt;br/&gt;&lt;br/&gt;This project provides a Rust procedural macro, `get_file_hash!`, designed to compute the SHA-256 hash of a specified file at compile time. This hash is then embedded directly into your compiled executable. This feature is invaluable for:&lt;br/&gt;&lt;br/&gt;*   **Integrity Verification:** Ensuring the deployed code hasn&amp;#39;t been tampered with.&lt;br/&gt;*   **Versioning:** Embedding a unique identifier linked to the exact source code version.&lt;br/&gt;*   **Cache Busting:** Generating unique names for assets based on their content.&lt;br/&gt;&lt;br/&gt;## Project Structure&lt;br/&gt;&lt;br/&gt;*   `get_file_hash_core`: A foundational crate containing the `get_file_hash!` macro definition.&lt;br/&gt;*   `get_file_hash`: The main library crate that re-exports the macro.&lt;br/&gt;*   `src/bin/get_file_hash.rs`: An example executable demonstrating the macro&amp;#39;s usage by hashing its own source file and updating this `README.md`.&lt;br/&gt;*   `build.rs`: A build script that also utilizes the `get_file_hash!` macro to hash `Cargo.toml` during the build process.&lt;br/&gt;&lt;br/&gt;## Usage of `get_file_hash!` Macro&lt;br/&gt;&lt;br/&gt;To use the `get_file_hash!` macro, ensure you have `get_file_hash` (or `get_file_hash_core` for direct usage) as a dependency in your `Cargo.toml`.&lt;br/&gt;&lt;br/&gt;### Example&lt;br/&gt;&lt;br/&gt;```rust&lt;br/&gt;use get_file_hash::get_file_hash;&lt;br/&gt;use sha2::{Digest, Sha256};&lt;br/&gt;&lt;br/&gt;fn main() {&lt;br/&gt;    // The macro resolves the path relative to CARGO_MANIFEST_DIR&lt;br/&gt;    let file_hash = get_file_hash!(&amp;#34;src/lib.rs&amp;#34;);&lt;br/&gt;    println!(&amp;#34;The SHA-256 hash of src/lib.rs is: {}&amp;#34;, file_hash);&lt;br/&gt;}&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;&amp;#34;##;&lt;br/&gt;&lt;br/&gt;const README_TEMPLATE_PART2: &amp;amp;str = r&amp;#34;## Setup and Building&lt;br/&gt;&lt;br/&gt;1.  **Clone the repository:**&lt;br/&gt;    ```bash&lt;br/&gt;    git clone &amp;lt;repository-url&amp;gt;&lt;br/&gt;    cd &amp;lt;repository-name&amp;gt;&lt;br/&gt;    ```&lt;br/&gt;2.  **Build the project:**&lt;br/&gt;    ```bash&lt;br/&gt;    cargo build&lt;br/&gt;    ```&lt;br/&gt;    During the build, `build.rs` will execute and print the hash of `Cargo.toml`.&lt;br/&gt;3.  **Run the example executable:**&lt;br/&gt;    ```bash&lt;br/&gt;    cargo run --bin get_file_hash&lt;br/&gt;    ```&lt;br/&gt;    This will print the hash of `src/bin/get_file_hash.rs` to your console.&lt;br/&gt;&lt;br/&gt;## Updating this `README.md`&lt;br/&gt;&lt;br/&gt;The hash information in this `README.md` is automatically generated by running the example executable.&lt;br/&gt;To update it, execute:&lt;br/&gt;&lt;br/&gt;```bash&lt;br/&gt;cargo run --bin get_file_hash &amp;gt; README.md&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;## Current File Hash Information (of `src/bin/get_file_hash.rs`)&lt;br/&gt;&lt;br/&gt;*   **Target File:** `src/bin/get_file_hash.rs`&lt;br/&gt;&amp;#34;;&lt;br/&gt;&lt;br/&gt;/// The main entry point of the application.&lt;br/&gt;///&lt;br/&gt;/// This function calculates the SHA-256 hash of the `get_file_hash.rs` source&lt;br/&gt;/// file using a custom procedural macro and then prints the hash to the&lt;br/&gt;/// console. It also includes a basic integrity verification check.&lt;br/&gt;fn main() {&lt;br/&gt;    // Calculate the SHA-256 hash of the current file (`get_file_hash.rs`) at&lt;br/&gt;    // compile time. The `get_file_hash!` macro reads the file content and&lt;br/&gt;    // computes its hash.&lt;br/&gt;    let self_hash = get_file_hash!(&amp;#34;get_file_hash.rs&amp;#34;);&lt;br/&gt;&lt;br/&gt;    let status_message = if self_hash.starts_with(&amp;#34;e3b0&amp;#34;) {&lt;br/&gt;        &amp;#34;Warning: This hash represents an empty file.&amp;#34;&lt;br/&gt;    } else {&lt;br/&gt;        &amp;#34;Integrity Verified.&amp;#34;&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    print!(&amp;#34;{}{}&amp;#34;, README_TEMPLATE_PART1, README_TEMPLATE_PART2);&lt;br/&gt;    println!(&amp;#34;*   **SHA-256 Hash:** `{}`&amp;#34;, self_hash);&lt;br/&gt;    println!(&amp;#34;*   **Status:** {}.\n&amp;#34;, status_message);&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-06T04:14:56Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsy5qhz5t84pq8j9aywle6pmh9sp02d5uhsjjjve568cluc0tkxxdspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q590qgy5</id>
    
      <title type="html">&amp;lt;?xml version=&amp;#39;1.0&amp;#39; ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsy5qhz5t84pq8j9aywle6pmh9sp02d5uhsjjjve568cluc0tkxxdspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q590qgy5" />
    <content type="html">
      &amp;lt;?xml version=&amp;#39;1.0&amp;#39; encoding=&amp;#39;windows-1252&amp;#39;?&amp;gt;&lt;br/&gt;&amp;lt;!--&lt;br/&gt;  Copyright (C) 2017 Christopher R. Field.&lt;br/&gt;&lt;br/&gt;  Licensed under the Apache License, Version 2.0 (the &amp;#34;License&amp;#34;);&lt;br/&gt;  you may not use this file except in compliance with the License.&lt;br/&gt;  You may obtain a copy of the License at&lt;br/&gt;&lt;br/&gt;  &lt;a href=&#34;http://www.apache.org/licenses/LICENSE-2.0&#34;&gt;http://www.apache.org/licenses/LICENSE-2.0&lt;/a&gt;&lt;br/&gt;&lt;br/&gt;  Unless required by applicable law or agreed to in writing, software&lt;br/&gt;  distributed under the License is distributed on an &amp;#34;AS IS&amp;#34; BASIS,&lt;br/&gt;  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br/&gt;  See the License for the specific language governing permissions and&lt;br/&gt;  limitations under the License.&lt;br/&gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;!--&lt;br/&gt;  The &amp;#34;cargo wix&amp;#34; subcommand provides a variety of predefined variables available&lt;br/&gt;  for customization of this template. The values for each variable are set at&lt;br/&gt;  installer creation time. The following variables are available:&lt;br/&gt;&lt;br/&gt;  TargetTriple      = The rustc target triple name.&lt;br/&gt;  TargetEnv         = The rustc target environment. This is typically either&lt;br/&gt;                      &amp;#34;msvc&amp;#34; or &amp;#34;gnu&amp;#34; depending on the toolchain downloaded and&lt;br/&gt;                      installed.&lt;br/&gt;  TargetVendor      = The rustc target vendor. This is typically &amp;#34;pc&amp;#34;, but Rust&lt;br/&gt;                      does support other vendors, like &amp;#34;uwp&amp;#34;.&lt;br/&gt;  CargoTargetBinDir = The complete path to the directory containing the&lt;br/&gt;                      binaries (exes) to include. The default would be&lt;br/&gt;                      &amp;#34;target\release\&amp;#34;. If an explicit rustc target triple is&lt;br/&gt;                      used, i.e. cross-compiling, then the default path would&lt;br/&gt;                      be &amp;#34;target\&amp;lt;CARGO_TARGET&amp;gt;\&amp;lt;CARGO_PROFILE&amp;gt;&amp;#34;,&lt;br/&gt;                      where &amp;#34;&amp;lt;CARGO_TARGET&amp;gt;&amp;#34; is replaced with the &amp;#34;CargoTarget&amp;#34;&lt;br/&gt;                      variable value and &amp;#34;&amp;lt;CARGO_PROFILE&amp;gt;&amp;#34; is replaced with the&lt;br/&gt;                      value from the &amp;#34;CargoProfile&amp;#34; variable. This can also&lt;br/&gt;                      be overridden manually with the &amp;#34;target-bin-dir&amp;#34; flag.&lt;br/&gt;  CargoTargetDir    = The path to the directory for the build artifacts, i.e.&lt;br/&gt;                      &amp;#34;target&amp;#34;.&lt;br/&gt;  CargoProfile      = The cargo profile used to build the binaries&lt;br/&gt;                      (usually &amp;#34;debug&amp;#34; or &amp;#34;release&amp;#34;).&lt;br/&gt;  Version           = The version for the installer. The default is the&lt;br/&gt;                      &amp;#34;Major.Minor.Fix&amp;#34; semantic versioning number of the Rust&lt;br/&gt;                      package.&lt;br/&gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;!--&lt;br/&gt;  Please do not remove these pre-processor If-Else blocks. These are used with&lt;br/&gt;  the `cargo wix` subcommand to automatically determine the installation&lt;br/&gt;  destination for 32-bit versus 64-bit installers. Removal of these lines will&lt;br/&gt;  cause installation errors.&lt;br/&gt;--&amp;gt;&lt;br/&gt;&amp;lt;?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64 ?&amp;gt;&lt;br/&gt;    &amp;lt;?define PlatformProgramFilesFolder = &amp;#34;ProgramFiles64Folder&amp;#34; ?&amp;gt;&lt;br/&gt;&amp;lt;?else ?&amp;gt;&lt;br/&gt;    &amp;lt;?define PlatformProgramFilesFolder = &amp;#34;ProgramFilesFolder&amp;#34; ?&amp;gt;&lt;br/&gt;&amp;lt;?endif ?&amp;gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;Wix xmlns=&amp;#39;&lt;a href=&#34;http://schemas.microsoft.com/wix/2006/wi&amp;#39;&amp;gt&#34;&gt;http://schemas.microsoft.com/wix/2006/wi&amp;#39;&amp;gt&lt;/a&gt;;&lt;br/&gt;&lt;br/&gt;    &amp;lt;Product&lt;br/&gt;        Id=&amp;#39;*&amp;#39;&lt;br/&gt;        Name=&amp;#39;n34&amp;#39;&lt;br/&gt;        UpgradeCode=&amp;#39;5E8926D0-4A18-48E9-A070-AFB0F698F04C&amp;#39;&lt;br/&gt;        Manufacturer=&amp;#39;Awiteb&amp;#39;&lt;br/&gt;        Language=&amp;#39;1033&amp;#39;&lt;br/&gt;        Codepage=&amp;#39;1252&amp;#39;&lt;br/&gt;        Version=&amp;#39;$(var.Version)&amp;#39;&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Package Id=&amp;#39;*&amp;#39;&lt;br/&gt;            Keywords=&amp;#39;Installer&amp;#39;&lt;br/&gt;            Description=&amp;#39;A CLI to interact with NIP-34 and other stuff related to code in Nostr&amp;#39;&lt;br/&gt;            Manufacturer=&amp;#39;Awiteb&amp;#39;&lt;br/&gt;            InstallerVersion=&amp;#39;450&amp;#39;&lt;br/&gt;            Languages=&amp;#39;1033&amp;#39;&lt;br/&gt;            Compressed=&amp;#39;yes&amp;#39;&lt;br/&gt;            InstallScope=&amp;#39;perMachine&amp;#39;&lt;br/&gt;            SummaryCodepage=&amp;#39;1252&amp;#39;&lt;br/&gt;            /&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;MajorUpgrade&lt;br/&gt;            Schedule=&amp;#39;afterInstallInitialize&amp;#39;&lt;br/&gt;            DowngradeErrorMessage=&amp;#39;A newer version of [ProductName] is already installed. Setup will now exit.&amp;#39;/&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Media Id=&amp;#39;1&amp;#39; Cabinet=&amp;#39;media1.cab&amp;#39; EmbedCab=&amp;#39;yes&amp;#39; DiskPrompt=&amp;#39;CD-ROM #1&amp;#39;/&amp;gt;&lt;br/&gt;        &amp;lt;Property Id=&amp;#39;DiskPrompt&amp;#39; Value=&amp;#39;n34 Installation&amp;#39;/&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Directory Id=&amp;#39;TARGETDIR&amp;#39; Name=&amp;#39;SourceDir&amp;#39;&amp;gt;&lt;br/&gt;            &amp;lt;Directory Id=&amp;#39;$(var.PlatformProgramFilesFolder)&amp;#39; Name=&amp;#39;PFiles&amp;#39;&amp;gt;&lt;br/&gt;                &amp;lt;Directory Id=&amp;#39;APPLICATIONFOLDER&amp;#39; Name=&amp;#39;n34&amp;#39;&amp;gt;&lt;br/&gt;                    &lt;br/&gt;                    &amp;lt;!--&lt;br/&gt;                      Enabling the license sidecar file in the installer is a four step process:&lt;br/&gt;&lt;br/&gt;                      1. Uncomment the `Component` tag and its contents.&lt;br/&gt;                      2. Change the value for the `Source` attribute in the `File` tag to a path&lt;br/&gt;                         to the file that should be included as the license sidecar file. The path&lt;br/&gt;                         can, and probably should be, relative to this file.&lt;br/&gt;                      3. Change the value for the `Name` attribute in the `File` tag to the&lt;br/&gt;                         desired name for the file when it is installed alongside the `bin` folder&lt;br/&gt;                         in the installation directory. This can be omitted if the desired name is&lt;br/&gt;                         the same as the file name.&lt;br/&gt;                      4. Uncomment the `ComponentRef` tag with the Id attribute value of &amp;#34;License&amp;#34;&lt;br/&gt;                         further down in this file.&lt;br/&gt;                    --&amp;gt;&lt;br/&gt;                    &amp;lt;!--&lt;br/&gt;                    &amp;lt;Component Id=&amp;#39;License&amp;#39; Guid=&amp;#39;*&amp;#39;&amp;gt;&lt;br/&gt;                        &amp;lt;File Id=&amp;#39;LicenseFile&amp;#39; Name=&amp;#39;ChangeMe&amp;#39; DiskId=&amp;#39;1&amp;#39; Source=&amp;#39;C:\Path\To\File&amp;#39; KeyPath=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                    &amp;lt;/Component&amp;gt;&lt;br/&gt;                    --&amp;gt;&lt;br/&gt;&lt;br/&gt;                    &amp;lt;Directory Id=&amp;#39;Bin&amp;#39; Name=&amp;#39;bin&amp;#39;&amp;gt;&lt;br/&gt;                        &amp;lt;Component Id=&amp;#39;Path&amp;#39; Guid=&amp;#39;7A2E5F6A-FC23-498B-AEC1-5FA63678FBFD&amp;#39; KeyPath=&amp;#39;yes&amp;#39;&amp;gt;&lt;br/&gt;                            &amp;lt;Environment&lt;br/&gt;                                Id=&amp;#39;PATH&amp;#39;&lt;br/&gt;                                Name=&amp;#39;PATH&amp;#39;&lt;br/&gt;                                Value=&amp;#39;[Bin]&amp;#39;&lt;br/&gt;                                Permanent=&amp;#39;no&amp;#39;&lt;br/&gt;                                Part=&amp;#39;last&amp;#39;&lt;br/&gt;                                Action=&amp;#39;set&amp;#39;&lt;br/&gt;                                System=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                        &amp;lt;/Component&amp;gt;&lt;br/&gt;                        &amp;lt;Component Id=&amp;#39;binary0&amp;#39; Guid=&amp;#39;*&amp;#39;&amp;gt;&lt;br/&gt;                            &amp;lt;File&lt;br/&gt;                                Id=&amp;#39;exe0&amp;#39;&lt;br/&gt;                                Name=&amp;#39;n34-cli.exe&amp;#39;&lt;br/&gt;                                DiskId=&amp;#39;1&amp;#39;&lt;br/&gt;                                Source=&amp;#39;$(var.CargoTargetBinDir)\n34-cli.exe&amp;#39;&lt;br/&gt;                                KeyPath=&amp;#39;yes&amp;#39;/&amp;gt;&lt;br/&gt;                        &amp;lt;/Component&amp;gt;&lt;br/&gt;                    &amp;lt;/Directory&amp;gt;&lt;br/&gt;                &amp;lt;/Directory&amp;gt;&lt;br/&gt;            &amp;lt;/Directory&amp;gt;&lt;br/&gt;        &amp;lt;/Directory&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Feature&lt;br/&gt;            Id=&amp;#39;Binaries&amp;#39;&lt;br/&gt;            Title=&amp;#39;Application&amp;#39;&lt;br/&gt;            Description=&amp;#39;Installs all binaries and the license.&amp;#39;&lt;br/&gt;            Level=&amp;#39;1&amp;#39;&lt;br/&gt;            ConfigurableDirectory=&amp;#39;APPLICATIONFOLDER&amp;#39;&lt;br/&gt;            AllowAdvertise=&amp;#39;no&amp;#39;&lt;br/&gt;            Display=&amp;#39;expand&amp;#39;&lt;br/&gt;            Absent=&amp;#39;disallow&amp;#39;&amp;gt;&lt;br/&gt;            &lt;br/&gt;            &amp;lt;!--&lt;br/&gt;              Uncomment the following `ComponentRef` tag to add the license&lt;br/&gt;              sidecar file to the installer.&lt;br/&gt;            --&amp;gt;&lt;br/&gt;            &amp;lt;!--&amp;lt;ComponentRef Id=&amp;#39;License&amp;#39;/&amp;gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;            &amp;lt;ComponentRef Id=&amp;#39;binary0&amp;#39;/&amp;gt;&lt;br/&gt;&lt;br/&gt;            &amp;lt;Feature&lt;br/&gt;                Id=&amp;#39;Environment&amp;#39;&lt;br/&gt;                Title=&amp;#39;PATH Environment Variable&amp;#39;&lt;br/&gt;                Description=&amp;#39;Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.&amp;#39;&lt;br/&gt;                Level=&amp;#39;1&amp;#39;&lt;br/&gt;                Absent=&amp;#39;allow&amp;#39;&amp;gt;&lt;br/&gt;                &amp;lt;ComponentRef Id=&amp;#39;Path&amp;#39;/&amp;gt;&lt;br/&gt;            &amp;lt;/Feature&amp;gt;&lt;br/&gt;        &amp;lt;/Feature&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;SetProperty Id=&amp;#39;ARPINSTALLLOCATION&amp;#39; Value=&amp;#39;[APPLICATIONFOLDER]&amp;#39; After=&amp;#39;CostFinalize&amp;#39;/&amp;gt;&lt;br/&gt;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;!--&lt;br/&gt;          Uncomment the following `Icon` and `Property` tags to change the product icon.&lt;br/&gt;&lt;br/&gt;          The product icon is the graphic that appears in the Add/Remove&lt;br/&gt;          Programs control panel for the application.&lt;br/&gt;        --&amp;gt;&lt;br/&gt;        &amp;lt;!--&amp;lt;Icon Id=&amp;#39;ProductICO&amp;#39; SourceFile=&amp;#39;wix\Product.ico&amp;#39;/&amp;gt;--&amp;gt;&lt;br/&gt;        &amp;lt;!--&amp;lt;Property Id=&amp;#39;ARPPRODUCTICON&amp;#39; Value=&amp;#39;ProductICO&amp;#39; /&amp;gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;Property Id=&amp;#39;ARPHELPLINK&amp;#39; Value=&amp;#39;&lt;a href=&#34;https://n34.dev/commands.html&amp;#39;/&amp;gt&#34;&gt;https://n34.dev/commands.html&amp;#39;/&amp;gt&lt;/a&gt;;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;UI&amp;gt;&lt;br/&gt;            &amp;lt;UIRef Id=&amp;#39;WixUI_FeatureTree&amp;#39;/&amp;gt;&lt;br/&gt;            &lt;br/&gt;            &amp;lt;!--&lt;br/&gt;              Enabling the EULA dialog in the installer is a three step process:&lt;br/&gt;&lt;br/&gt;                1. Comment out or remove the two `Publish` tags that follow the&lt;br/&gt;                   `WixVariable` tag.&lt;br/&gt;                2. Uncomment the `&amp;lt;WixVariable Id=&amp;#39;WixUILicenseRtf&amp;#39; Value=&amp;#39;Path\to\Eula.rft&amp;#39;&amp;gt;` tag further down&lt;br/&gt;                3. Replace the `Value` attribute of the `WixVariable` tag with&lt;br/&gt;                   the path to a RTF file that will be used as the EULA and&lt;br/&gt;                   displayed in the license agreement dialog.&lt;br/&gt;            --&amp;gt;&lt;br/&gt;            &amp;lt;Publish Dialog=&amp;#39;WelcomeDlg&amp;#39; Control=&amp;#39;Next&amp;#39; Event=&amp;#39;NewDialog&amp;#39; Value=&amp;#39;CustomizeDlg&amp;#39; Order=&amp;#39;99&amp;#39;&amp;gt;1&amp;lt;/Publish&amp;gt;&lt;br/&gt;            &amp;lt;Publish Dialog=&amp;#39;CustomizeDlg&amp;#39; Control=&amp;#39;Back&amp;#39; Event=&amp;#39;NewDialog&amp;#39; Value=&amp;#39;WelcomeDlg&amp;#39; Order=&amp;#39;99&amp;#39;&amp;gt;1&amp;lt;/Publish&amp;gt;&lt;br/&gt;&lt;br/&gt;        &amp;lt;/UI&amp;gt;&lt;br/&gt;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;!--&lt;br/&gt;          Enabling the EULA dialog in the installer requires uncommenting&lt;br/&gt;          the following `WixUILicenseRTF` tag and changing the `Value`&lt;br/&gt;          attribute.&lt;br/&gt;        --&amp;gt;&lt;br/&gt;        &amp;lt;!-- &amp;lt;WixVariable Id=&amp;#39;WixUILicenseRtf&amp;#39; Value=&amp;#39;Relative\Path\to\Eula.rtf&amp;#39;/&amp;gt; --&amp;gt;&lt;br/&gt;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;!--&lt;br/&gt;          Uncomment the next `WixVariable` tag to customize the installer&amp;#39;s&lt;br/&gt;          Graphical User Interface (GUI) and add a custom banner image across&lt;br/&gt;          the top of each screen. See the WiX Toolset documentation for details&lt;br/&gt;          about customization.&lt;br/&gt;&lt;br/&gt;          The banner BMP dimensions are 493 x 58 pixels.&lt;br/&gt;        --&amp;gt;&lt;br/&gt;        &amp;lt;!--&amp;lt;WixVariable Id=&amp;#39;WixUIBannerBmp&amp;#39; Value=&amp;#39;wix\Banner.bmp&amp;#39;/&amp;gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;        &lt;br/&gt;        &amp;lt;!--&lt;br/&gt;          Uncomment the next `WixVariable` tag to customize the installer&amp;#39;s&lt;br/&gt;          Graphical User Interface (GUI) and add a custom image to the first&lt;br/&gt;          dialog, or screen. See the WiX Toolset documentation for details about&lt;br/&gt;          customization.&lt;br/&gt;&lt;br/&gt;          The dialog BMP dimensions are 493 x 312 pixels.&lt;br/&gt;        --&amp;gt;&lt;br/&gt;        &amp;lt;!--&amp;lt;WixVariable Id=&amp;#39;WixUIDialogBmp&amp;#39; Value=&amp;#39;wix\Dialog.bmp&amp;#39;/&amp;gt;--&amp;gt;&lt;br/&gt;&lt;br/&gt;    &amp;lt;/Product&amp;gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;/Wix&amp;gt;&lt;br/&gt;
    </content>
    <updated>2026-04-06T04:14:44Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqsrh78q7eq9wjwlfq6ya5epk2wums8scvq8as056r8x6c460z2rwkcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q54jmhsx</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqsrh78q7eq9wjwlfq6ya5epk2wums8scvq8as056r8x6c460z2rwkcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q54jmhsx" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use std::{&lt;br/&gt;    fmt,&lt;br/&gt;    fs,&lt;br/&gt;    path::{Path, PathBuf},&lt;br/&gt;    str::FromStr,&lt;br/&gt;    sync::atomic::Ordering,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use nostr::{&lt;br/&gt;    event::{Event, EventId, Kind, Tag, TagKind, TagStandard},&lt;br/&gt;    filter::Alphabet,&lt;br/&gt;    key::PublicKey,&lt;br/&gt;    nips::{&lt;br/&gt;        nip01::Coordinate,&lt;br/&gt;        nip10::Marker,&lt;br/&gt;        nip19::{Nip19Coordinate, Nip19Event, ToBech32},&lt;br/&gt;        nip34::GitRepositoryAnnouncement,&lt;br/&gt;        nip65::{self, RelayMetadata},&lt;br/&gt;    },&lt;br/&gt;    types::RelayUrl,&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;use super::traits::TagsExt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{NOSTR_ADDRESS_FILE, parsers},&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;/// Returns the value of the given tag&lt;br/&gt;#[inline]&lt;br/&gt;fn tag_value(tag: &amp;amp;TagStandard) -&amp;gt; String {&lt;br/&gt;    tag.clone().to_vec().remove(1)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Parses the tag value into type `T` if possible.&lt;br/&gt;#[inline]&lt;br/&gt;fn parse_value&amp;lt;T: FromStr&amp;gt;(tag: &amp;amp;TagStandard) -&amp;gt; Option&amp;lt;T&amp;gt; {&lt;br/&gt;    tag_value(tag).parse().ok()&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Gets all values from the tag. If any value fails to parse, returns an empty&lt;br/&gt;/// vector.&lt;br/&gt;#[inline]&lt;br/&gt;fn tag_values&amp;lt;T&amp;gt;(tag: &amp;amp;TagStandard) -&amp;gt; Vec&amp;lt;T&amp;gt;&lt;br/&gt;where&lt;br/&gt;    T: FromStr &#43; fmt::Debug,&lt;br/&gt;    &amp;lt;T as FromStr&amp;gt;::Err: fmt::Debug,&lt;br/&gt;{&lt;br/&gt;    tag.clone()&lt;br/&gt;        .to_vec()&lt;br/&gt;        .into_iter()&lt;br/&gt;        .skip(1)&lt;br/&gt;        .map(|t| {&lt;br/&gt;            let result = T::from_str(t.as_str());&lt;br/&gt;            tracing::trace!(&amp;#34;Parsing `{t}` result: `{result:?}`&amp;#34;);&lt;br/&gt;            result&lt;br/&gt;        })&lt;br/&gt;        .collect::&amp;lt;Result&amp;lt;_, _&amp;gt;&amp;gt;()&lt;br/&gt;        .unwrap_or_default()&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Convert [`Event`] to [`GitRepositoryAnnouncement`]&lt;br/&gt;pub fn event_into_repo(event: Event, repo_id: impl Into&amp;lt;String&amp;gt;) -&amp;gt; GitRepositoryAnnouncement {&lt;br/&gt;    let tags = &amp;amp;event.tags;&lt;br/&gt;&lt;br/&gt;    GitRepositoryAnnouncement {&lt;br/&gt;        id:          repo_id.into(),&lt;br/&gt;        name:        tags.map_tag(TagKind::Name, tag_value),&lt;br/&gt;        description: tags.map_tag(TagKind::Description, tag_value),&lt;br/&gt;        euc:         tags&lt;br/&gt;            .map_marker(&lt;br/&gt;                TagKind::single_letter(Alphabet::R, false),&lt;br/&gt;                &amp;#34;euc&amp;#34;,&lt;br/&gt;                parse_value,&lt;br/&gt;            )&lt;br/&gt;            .flatten(),&lt;br/&gt;        web:         tags.dmap_tag(TagKind::Web, tag_values),&lt;br/&gt;        clone:       tags.dmap_tag(TagKind::Clone, tag_values),&lt;br/&gt;        relays:      tags.dmap_tag(TagKind::Relays, tag_values),&lt;br/&gt;        maintainers: tags.dmap_tag(TagKind::Maintainers, tag_values),&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Returns a new string with leading and trailing whitespace removed.&lt;br/&gt;pub fn str_trim(s: String) -&amp;gt; String {&lt;br/&gt;    s.trim().to_owned()&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Returns a vector with duplicate elements removed.&lt;br/&gt;pub fn dedup&amp;lt;I, T&amp;gt;(iter: I) -&amp;gt; Vec&amp;lt;T&amp;gt;&lt;br/&gt;where&lt;br/&gt;    T: std::cmp::Ord,&lt;br/&gt;    I: Iterator&amp;lt;Item = T&amp;gt;,&lt;br/&gt;{&lt;br/&gt;    let mut vector: Vec&amp;lt;T&amp;gt; = iter.collect();&lt;br/&gt;    vector.sort_unstable();&lt;br/&gt;    vector.dedup();&lt;br/&gt;    vector&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Sorts items from the iterator using the given key function.&lt;br/&gt;/// The sorting is unstable, but faster than stable sorting.&lt;br/&gt;pub fn sort_by_key&amp;lt;I, T, K&amp;gt;(iterator: I, key: impl FnMut(&amp;amp;T) -&amp;gt; K) -&amp;gt; impl Iterator&amp;lt;Item = T&amp;gt;&lt;br/&gt;where&lt;br/&gt;    I: IntoIterator&amp;lt;Item = T&amp;gt;,&lt;br/&gt;    K: Ord,&lt;br/&gt;{&lt;br/&gt;    let mut vector = Vec::&amp;lt;T&amp;gt;::from_iter(iterator);&lt;br/&gt;    vector.sort_unstable_by_key(key);&lt;br/&gt;    vector.into_iter()&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Creates a new NIP-19 nevent string from an event ID and up to 3 unique relay&lt;br/&gt;/// URLs.&lt;br/&gt;#[inline]&lt;br/&gt;pub fn new_nevent(event_id: EventId, relays: &amp;amp;[RelayUrl]) -&amp;gt; N34Result&amp;lt;String&amp;gt; {&lt;br/&gt;    Nip19Event::new(event_id)&lt;br/&gt;        .relays(&lt;br/&gt;            dedup(relays.iter().cloned())&lt;br/&gt;                .into_iter()&lt;br/&gt;                .take(3)&lt;br/&gt;                .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;(),&lt;br/&gt;        )&lt;br/&gt;        .to_bech32()&lt;br/&gt;        .map_err(N34Error::from)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Creates a NIP-19 naddr string for a git repository announcement and up to 3&lt;br/&gt;/// unique relay URLs.&lt;br/&gt;#[inline]&lt;br/&gt;pub fn repo_naddr(&lt;br/&gt;    repo_id: impl Into&amp;lt;String&amp;gt;,&lt;br/&gt;    pubk: PublicKey,&lt;br/&gt;    relays: &amp;amp;[RelayUrl],&lt;br/&gt;) -&amp;gt; N34Result&amp;lt;String&amp;gt; {&lt;br/&gt;    Nip19Coordinate::new(&lt;br/&gt;        Coordinate::new(Kind::GitRepoAnnouncement, pubk).identifier(repo_id),&lt;br/&gt;        dedup(relays.iter().cloned()).into_iter().take(3),&lt;br/&gt;    )&lt;br/&gt;    .to_bech32()&lt;br/&gt;    .map_err(N34Error::from)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Extracts write relay URLs from an event if present, otherwise returns an&lt;br/&gt;/// empty vector.&lt;br/&gt;pub fn add_write_relays(event: Option&amp;lt;&amp;amp;Event&amp;gt;) -&amp;gt; Vec&amp;lt;RelayUrl&amp;gt; {&lt;br/&gt;    let mut vector = Vec::new();&lt;br/&gt;    if let Some(event) = event {&lt;br/&gt;        vector.extend(&lt;br/&gt;            nip65::extract_owned_relay_list(event.clone())&lt;br/&gt;                .filter_map(|(r, m)| m.is_none_or(|m| m == RelayMetadata::Write).then_some(r)),&lt;br/&gt;        );&lt;br/&gt;    }&lt;br/&gt;    vector&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Extracts read relay URLs from an event if present, otherwise returns an&lt;br/&gt;/// empty vector.&lt;br/&gt;pub fn add_read_relays(event: Option&amp;lt;&amp;amp;Event&amp;gt;) -&amp;gt; Vec&amp;lt;RelayUrl&amp;gt; {&lt;br/&gt;    let mut vector = Vec::new();&lt;br/&gt;    if let Some(event) = event {&lt;br/&gt;        vector.extend(&lt;br/&gt;            nip65::extract_owned_relay_list(event.clone())&lt;br/&gt;                .filter_map(|(r, m)| m.is_none_or(|m| m == RelayMetadata::Read).then_some(r)),&lt;br/&gt;        );&lt;br/&gt;    }&lt;br/&gt;    vector&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;/// Opens the user&amp;#39;s default editor ($EDITOR) to edit a temporary file with&lt;br/&gt;/// given suffix, then reads and returns the file contents. The temporary file&lt;br/&gt;/// is automatically deleted.&lt;br/&gt;pub fn read_editor(file_pre_content: Option&amp;lt;&amp;amp;str&amp;gt;, file_suffix: &amp;amp;str) -&amp;gt; N34Result&amp;lt;String&amp;gt; {&lt;br/&gt;    let Ok(editor) = std::env::var(&amp;#34;EDITOR&amp;#34;) else {&lt;br/&gt;        return Err(N34Error::EditorNotFound);&lt;br/&gt;    };&lt;br/&gt;&lt;br/&gt;    let temp_path = tempfile::NamedTempFile::with_suffix(file_suffix)?.into_temp_path();&lt;br/&gt;&lt;br/&gt;    if let Some(pre_content) = file_pre_content {&lt;br/&gt;        fs::write(&amp;amp;temp_path, pre_content)?;&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    // Disable the logs to not show up in a terminal text editor&lt;br/&gt;    crate::EDITOR_OPEN.store(true, Ordering::Relaxed);&lt;br/&gt;    let exit_status = std::process::Command::new(&amp;amp;editor)&lt;br/&gt;        .arg(temp_path.to_str().expect(&amp;#34;The path is valid utf8&amp;#34;))&lt;br/&gt;        .spawn()?&lt;br/&gt;        .wait()?;&lt;br/&gt;    crate::EDITOR_OPEN.store(false, Ordering::Relaxed);&lt;br/&gt;&lt;br/&gt;    if !exit_status.success()&lt;br/&gt;        &amp;amp;&amp;amp; let Some(code) = exit_status.code()&lt;br/&gt;    {&lt;br/&gt;        return Err(N34Error::EditorErr(editor, code));&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    let content = fs::read_to_string(&amp;amp;temp_path)&lt;br/&gt;        .map_err(N34Error::from)?&lt;br/&gt;        .trim()&lt;br/&gt;        .to_owned();&lt;br/&gt;&lt;br/&gt;    if content.is_empty() {&lt;br/&gt;        return Err(N34Error::EmptyEditorFile);&lt;br/&gt;    }&lt;br/&gt;    Ok(content)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Returns the given content if it&amp;#39;s `Option::Some` or call [`read_editor`]&lt;br/&gt;pub fn get_content(&lt;br/&gt;    content: Option&amp;lt;impl AsRef&amp;lt;str&amp;gt;&amp;gt;,&lt;br/&gt;    quoted_content: Option&amp;lt;impl AsRef&amp;lt;str&amp;gt;&amp;gt;,&lt;br/&gt;    file_suffix: &amp;amp;str,&lt;br/&gt;) -&amp;gt; N34Result&amp;lt;String&amp;gt; {&lt;br/&gt;    if let Some(content) = content {&lt;br/&gt;        return Ok(content.as_ref().trim().to_owned());&lt;br/&gt;    }&lt;br/&gt;    read_editor(&lt;br/&gt;        quoted_content.map(|s| s.as_ref().to_owned()).as_deref(),&lt;br/&gt;        file_suffix,&lt;br/&gt;    )&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Path to the `nostr-address` file in current directory.&lt;br/&gt;#[inline]&lt;br/&gt;pub fn nostr_address_path() -&amp;gt; std::io::Result&amp;lt;PathBuf&amp;gt; {&lt;br/&gt;    std::env::current_dir().map(|p| p.join(NOSTR_ADDRESS_FILE))&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Returns the given coordinates if Some, otherwise attempts to read and parse&lt;br/&gt;/// coordinates from the specified file. Returns an empty vector if the file&lt;br/&gt;/// doesn&amp;#39;t exist.&lt;br/&gt;pub fn naddrs_or_file(&lt;br/&gt;    naddrs: Option&amp;lt;Vec&amp;lt;Nip19Coordinate&amp;gt;&amp;gt;,&lt;br/&gt;    address_file_path: &amp;amp;Path,&lt;br/&gt;) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;Nip19Coordinate&amp;gt;&amp;gt; {&lt;br/&gt;    if let Some(naddrs) = naddrs {&lt;br/&gt;        return Ok(naddrs);&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    if address_file_path.exists() {&lt;br/&gt;        parsers::parse_nostr_address_file(address_file_path)&lt;br/&gt;    } else {&lt;br/&gt;        Ok(Vec::new())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Generate a reply tag for an event with the given ID, relay URL (if any), and&lt;br/&gt;/// marker.&lt;br/&gt;#[inline]&lt;br/&gt;pub fn event_reply_tag(reply_to: &amp;amp;EventId, relay: Option&amp;lt;&amp;amp;RelayUrl&amp;gt;, marker: Marker) -&amp;gt; Tag {&lt;br/&gt;    Tag::custom(&lt;br/&gt;        TagKind::e(),&lt;br/&gt;        [&lt;br/&gt;            reply_to.to_hex(),&lt;br/&gt;            relay.map(|r| r.to_string()).unwrap_or_default(),&lt;br/&gt;            marker.to_string(),&lt;br/&gt;        ],&lt;br/&gt;    )&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Wraps text into lines no longer than max_width, breaking only at whitespace.&lt;br/&gt;pub fn smart_wrap(text: &amp;amp;str, max_width: usize) -&amp;gt; String {&lt;br/&gt;    text.lines()&lt;br/&gt;        .map(|line| {&lt;br/&gt;            if !line.trim().is_empty() {&lt;br/&gt;                line.split(&amp;#34; &amp;#34;)&lt;br/&gt;                    .fold((String::new(), 0), |(result, last_newline), word| {&lt;br/&gt;                        let result_len = result.chars().count();&lt;br/&gt;                        if result_len == 0 {&lt;br/&gt;                            (word.to_owned(), 0)&lt;br/&gt;                        } else if (result_len - last_newline) &#43; word.chars().count() &amp;gt; max_width {&lt;br/&gt;                            (format!(&amp;#34;{result}\n{word}&amp;#34;), result_len &#43; 1)&lt;br/&gt;                        } else {&lt;br/&gt;                            (format!(&amp;#34;{result} {word}&amp;#34;), last_newline)&lt;br/&gt;                        }&lt;br/&gt;                    })&lt;br/&gt;                    .0&lt;br/&gt;            } else {&lt;br/&gt;                String::new()&lt;br/&gt;            }&lt;br/&gt;        })&lt;br/&gt;        .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;()&lt;br/&gt;        .join(&amp;#34;\n&amp;#34;)&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Returns an error if the given naddrs is empty otherwise returned it&lt;br/&gt;pub fn check_empty_naddrs(naddrs: Vec&amp;lt;Nip19Coordinate&amp;gt;) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;Nip19Coordinate&amp;gt;&amp;gt; {&lt;br/&gt;    if naddrs.is_empty() {&lt;br/&gt;        return Err(N34Error::EmptyNaddrs);&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    Ok(naddrs)&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-06T04:14:31Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqs9tg2c5m4gnnc3h38judrjvq48hun9a59xxr5tmwa9uwpr5peh3wcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5u7cal6</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqs9tg2c5m4gnnc3h38judrjvq48hun9a59xxr5tmwa9uwpr5peh3wcpr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q5u7cal6" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;use convert_case::{Case, Casing};&lt;br/&gt;use nostr::hashes::sha1::Hash as Sha1Hash;&lt;br/&gt;use nostr::{&lt;br/&gt;    event::{Event, EventBuilder, EventId, Kind, Tag, TagKind, TagStandard, Tags},&lt;br/&gt;    key::PublicKey,&lt;br/&gt;    nips::{&lt;br/&gt;        nip01::Coordinate,&lt;br/&gt;        nip19::Nip19Coordinate,&lt;br/&gt;        nip21::Nip21,&lt;br/&gt;        nip34::{GitIssue, GitRepositoryAnnouncement},&lt;br/&gt;    },&lt;br/&gt;    parser::Token,&lt;br/&gt;    types::{RelayUrl, Url},&lt;br/&gt;};&lt;br/&gt;use nostr_keyring::KeyringError;&lt;br/&gt;&lt;br/&gt;use crate::cli::issue::ISSUE_ALT_PREFIX;&lt;br/&gt;use crate::cli::patch::{&lt;br/&gt;    LEGACY_NGIT_REVISION_ROOT_HASHTAG_CONTENT,&lt;br/&gt;    REVISION_ROOT_HASHTAG_CONTENT,&lt;br/&gt;    ROOT_HASHTAG_CONTENT,&lt;br/&gt;};&lt;br/&gt;use crate::error::{N34Error, N34Result};&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;/// A trait to add helper instance function to [`Tags`] type&lt;br/&gt;#[easy_ext::ext(TagsExt)]&lt;br/&gt;impl Tags {&lt;br/&gt;    /// Search for the given tag and map it value to a function&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn map_tag&amp;lt;T&amp;gt;(&amp;amp;self, kind: TagKind, f: impl FnOnce(&amp;amp;TagStandard) -&amp;gt; T) -&amp;gt; Option&amp;lt;T&amp;gt; {&lt;br/&gt;        self.find_standardized(kind).map(f)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Search for the given tag and map it value to a function. If the tag not&lt;br/&gt;    /// found return the default `T`&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn dmap_tag&amp;lt;T&amp;gt;(&amp;amp;self, kind: TagKind, f: impl FnOnce(&amp;amp;TagStandard) -&amp;gt; T) -&amp;gt; T&lt;br/&gt;    where&lt;br/&gt;        T: Default,&lt;br/&gt;    {&lt;br/&gt;        self.map_tag(kind, f).unwrap_or_default()&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Finds the first standard tag of the given kind with the specified&lt;br/&gt;    /// marker, then applies the function to the tag and returns the result.&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn map_marker&amp;lt;T&amp;gt;(&lt;br/&gt;        &amp;amp;self,&lt;br/&gt;        kind: TagKind,&lt;br/&gt;        marker: &amp;amp;str,&lt;br/&gt;        f: impl FnOnce(&amp;amp;TagStandard) -&amp;gt; T,&lt;br/&gt;    ) -&amp;gt; Option&amp;lt;T&amp;gt; {&lt;br/&gt;        self.filter_standardized(kind)&lt;br/&gt;            .find(|t| (*t).clone().to_vec().last().is_some_and(|m| m == marker))&lt;br/&gt;            .map(f)&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Trait for building [`GitRepositoryAnnouncement`] events&lt;br/&gt;#[easy_ext::ext(NewGitRepositoryAnnouncement)]&lt;br/&gt;impl EventBuilder {&lt;br/&gt;    /// Creates a new [`GitRepositoryAnnouncement`] event builder with the given&lt;br/&gt;    /// repository details.&lt;br/&gt;    #[allow(clippy::too_many_arguments)]&lt;br/&gt;    pub fn new_git_repo(&lt;br/&gt;        repo_id: String,&lt;br/&gt;        name: Option&amp;lt;String&amp;gt;,&lt;br/&gt;        description: Option&amp;lt;String&amp;gt;,&lt;br/&gt;        web: Vec&amp;lt;Url&amp;gt;,&lt;br/&gt;        clone: Vec&amp;lt;Url&amp;gt;,&lt;br/&gt;        relays: Vec&amp;lt;RelayUrl&amp;gt;,&lt;br/&gt;        maintainers: Vec&amp;lt;PublicKey&amp;gt;,&lt;br/&gt;        labels: Vec&amp;lt;String&amp;gt;,&lt;br/&gt;        force_id: bool,&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;EventBuilder&amp;gt; {&lt;br/&gt;        let repo_id = repo_id.trim();&lt;br/&gt;        let kebab_repo_id = repo_id.to_case(Case::Kebab);&lt;br/&gt;        if repo_id.is_empty() || (!force_id &amp;amp;&amp;amp; repo_id != kebab_repo_id) {&lt;br/&gt;            if repo_id != kebab_repo_id {&lt;br/&gt;                tracing::error!(&lt;br/&gt;                    &amp;#34;The repo id should be `{kebab_repo_id}` (kebab-case). Use `--force-id` to \&lt;br/&gt;                     override this check&amp;#34;&lt;br/&gt;                );&lt;br/&gt;            }&lt;br/&gt;            return Err(N34Error::InvalidRepoId);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        Ok(&lt;br/&gt;            EventBuilder::git_repository_announcement(GitRepositoryAnnouncement {&lt;br/&gt;                id: repo_id.to_owned(),&lt;br/&gt;                name,&lt;br/&gt;                description,&lt;br/&gt;                web,&lt;br/&gt;                clone,&lt;br/&gt;                relays,&lt;br/&gt;                euc: None,&lt;br/&gt;                maintainers,&lt;br/&gt;            })?&lt;br/&gt;            .dedup_tags()&lt;br/&gt;            .tags(labels.into_iter().map(Tag::hashtag)),&lt;br/&gt;        )&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Creates a new [`GitIssue`] event builder with the given&lt;br/&gt;    /// issue details.&lt;br/&gt;    pub fn new_git_issue(&lt;br/&gt;        coordinates: &amp;amp;[Coordinate],&lt;br/&gt;        content: String,&lt;br/&gt;        subject: Option&amp;lt;String&amp;gt;,&lt;br/&gt;        labels: Vec&amp;lt;String&amp;gt;,&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;EventBuilder&amp;gt; {&lt;br/&gt;        let mut coordinates = coordinates.iter();&lt;br/&gt;        let first_coordinate = coordinates.next().ok_or(N34Error::EmptyNaddrs)?;&lt;br/&gt;&lt;br/&gt;        let mut event_builder = EventBuilder::git_issue(GitIssue {&lt;br/&gt;            repository: first_coordinate.clone(),&lt;br/&gt;            content,&lt;br/&gt;            subject: subject.clone(),&lt;br/&gt;            labels: labels.into_iter().map(|l| l.trim().to_owned()).collect(),&lt;br/&gt;        })&lt;br/&gt;        .map_err(N34Error::from)?&lt;br/&gt;        .tags(&lt;br/&gt;            coordinates&lt;br/&gt;                .clone()&lt;br/&gt;                .map(|c| Tag::coordinate(c.clone(), None)),&lt;br/&gt;        )&lt;br/&gt;        .tags(coordinates.map(|c| Tag::public_key(c.public_key)));&lt;br/&gt;&lt;br/&gt;        if let Some(issue_subject) = subject {&lt;br/&gt;            event_builder =&lt;br/&gt;                event_builder.tag(Tag::alt(format!(&amp;#34;{ISSUE_ALT_PREFIX}{issue_subject}&amp;#34;)))&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        Ok(event_builder)&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Helper functions for [`Token`] type&lt;br/&gt;#[easy_ext::ext(TokenUtils)]&lt;br/&gt;impl Token&amp;lt;&amp;#39;_&amp;gt; {&lt;br/&gt;    /// Returns `Some((public_key, relays))` from the givin token if it&amp;#39;s npub1&lt;br/&gt;    /// or nprofile1&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_public_key(&amp;amp;self) -&amp;gt; Option&amp;lt;(PublicKey, Vec&amp;lt;RelayUrl&amp;gt;)&amp;gt; {&lt;br/&gt;        match self {&lt;br/&gt;            Token::Nostr(nip21) =&amp;gt; {&lt;br/&gt;                match nip21 {&lt;br/&gt;                    Nip21::Pubkey(pkey) =&amp;gt; Some((*pkey, Vec::new())),&lt;br/&gt;                    Nip21::Profile(profile) =&amp;gt; Some((profile.public_key, profile.relays.clone())),&lt;br/&gt;                    _ =&amp;gt; None,&lt;br/&gt;                }&lt;br/&gt;            }&lt;br/&gt;            _ =&amp;gt; None,&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns `Some((note_id, relays))` from the givin token if it&amp;#39;s note1 or&lt;br/&gt;    /// nevent1&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_event_id(&amp;amp;self) -&amp;gt; Option&amp;lt;(EventId, Vec&amp;lt;RelayUrl&amp;gt;)&amp;gt; {&lt;br/&gt;        match self {&lt;br/&gt;            Token::Nostr(nip21) =&amp;gt; {&lt;br/&gt;                match nip21 {&lt;br/&gt;                    Nip21::EventId(event_id) =&amp;gt; Some((*event_id, Vec::new())),&lt;br/&gt;                    Nip21::Event(event) =&amp;gt; Some((event.event_id, event.relays.clone())),&lt;br/&gt;                    _ =&amp;gt; None,&lt;br/&gt;                }&lt;br/&gt;            }&lt;br/&gt;            _ =&amp;gt; None,&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns `Some(hashtag)` from the givin token if it&amp;#39;s a hashtag&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_hashtag(&amp;amp;self) -&amp;gt; Option&amp;lt;String&amp;gt; {&lt;br/&gt;        match self {&lt;br/&gt;            Token::Hashtag(tag) =&amp;gt; Some(tag.trim().to_owned()),&lt;br/&gt;            _ =&amp;gt; None,&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Utility functions for working with lists of NIP-19 coordinates&lt;br/&gt;#[easy_ext::ext(NaddrsUtils)]&lt;br/&gt;impl Vec&amp;lt;Nip19Coordinate&amp;gt; {&lt;br/&gt;    /// Converts these coordinate addresses to basic coordinates&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn into_coordinates(self) -&amp;gt; Vec&amp;lt;Coordinate&amp;gt; {&lt;br/&gt;        self.into_iter().map(|n| n.coordinate).collect()&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns all repository owners&amp;#39; public keys from these coordinates.&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_owners(&amp;amp;self) -&amp;gt; Vec&amp;lt;PublicKey&amp;gt; {&lt;br/&gt;        self.iter().map(|n| n.public_key).collect()&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Extracts all relay URLs from these coordinates&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_relays(&amp;amp;self) -&amp;gt; Vec&amp;lt;RelayUrl&amp;gt; {&lt;br/&gt;        self.iter().flat_map(|n| n.relays.clone()).collect()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Utility functions for working with lists of repository announcement&lt;br/&gt;#[easy_ext::ext(ReposUtils)]&lt;br/&gt;impl Vec&amp;lt;GitRepositoryAnnouncement&amp;gt; {&lt;br/&gt;    /// Extracts all relay URLs from these repositories&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_relays(&amp;amp;self) -&amp;gt; Vec&amp;lt;RelayUrl&amp;gt; {&lt;br/&gt;        self.iter().flat_map(|n| n.relays.clone()).collect()&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Extract all the maintainers from these repositories&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_maintainers(&amp;amp;self) -&amp;gt; Vec&amp;lt;PublicKey&amp;gt; {&lt;br/&gt;        self.iter().flat_map(|r| r.maintainers.clone()).collect()&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Gets the first EUC hash from the reposotoies if it exists.&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_euc(&amp;amp;self) -&amp;gt; Option&amp;lt;&amp;amp;Sha1Hash&amp;gt; {&lt;br/&gt;        self.iter().find_map(|r| r.euc.as_ref())&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Utility functions for working with patch events&lt;br/&gt;#[easy_ext::ext(GitPatchUtils)]&lt;br/&gt;impl Event {&lt;br/&gt;    /// Returns whether the patch is a root or not&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn is_root_patch(&amp;amp;self) -&amp;gt; bool {&lt;br/&gt;        self.kind == Kind::GitPatch&lt;br/&gt;            &amp;amp;&amp;amp; self&lt;br/&gt;                .tags&lt;br/&gt;                .filter(TagKind::t())&lt;br/&gt;                .any(|t| t.content() == Some(ROOT_HASHTAG_CONTENT))&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns whether the patch is patch-revision or not&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn is_revision_patch(&amp;amp;self) -&amp;gt; bool {&lt;br/&gt;        self.kind == Kind::GitPatch&lt;br/&gt;            &amp;amp;&amp;amp; self.tags.filter(TagKind::t()).any(|t| {&lt;br/&gt;                [&lt;br/&gt;                    Some(REVISION_ROOT_HASHTAG_CONTENT),&lt;br/&gt;                    Some(LEGACY_NGIT_REVISION_ROOT_HASHTAG_CONTENT),&lt;br/&gt;                ]&lt;br/&gt;                .contains(&amp;amp;t.content())&lt;br/&gt;            })&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Gets the root patch ID from a patch-revision event by finding the `e`&lt;br/&gt;    /// tag that replies to it. Fails if no such tag is found or if the tag&lt;br/&gt;    /// contains an invalid event ID.&lt;br/&gt;    pub fn root_patch_from_revision(&amp;amp;self) -&amp;gt; N34Result&amp;lt;EventId&amp;gt; {&lt;br/&gt;        self.tags&lt;br/&gt;            .iter()&lt;br/&gt;            .find(|tag| tag.is_reply())&lt;br/&gt;            .ok_or_else(|| {&lt;br/&gt;                N34Error::InvalidEvent(&lt;br/&gt;                    &amp;#34;A patch revision without `e`-reply to the root patch&amp;#34;.to_owned(),&lt;br/&gt;                )&lt;br/&gt;            })?&lt;br/&gt;            .content()&lt;br/&gt;            .ok_or_else(|| N34Error::InvalidEvent(&amp;#34;`e` tag without an event&amp;#34;.to_owned()))?&lt;br/&gt;            .parse()&lt;br/&gt;            .map_err(|err| N34Error::InvalidEvent(format!(&amp;#34;Invalid event ID in `e` tag: {err}&amp;#34;)))&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// Utility functions for working with issue events&lt;br/&gt;#[easy_ext::ext(GitIssueUtils)]&lt;br/&gt;impl Event {&lt;br/&gt;    /// Gets the subject line of the issue or &amp;#34;N/A&amp;#34; if none exists&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_issue_subject(&amp;amp;self) -&amp;gt; &amp;amp;str {&lt;br/&gt;        self.tags&lt;br/&gt;            .find(TagKind::Subject)&lt;br/&gt;            .and_then(|t| t.content())&lt;br/&gt;            .unwrap_or(&amp;#34;N/A&amp;#34;)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Gets all issue labels formatted as comma-separated hashtags (e.g. &amp;#34;#bug,&lt;br/&gt;    /// #feature&amp;#34;)&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn extract_issue_labels(&amp;amp;self) -&amp;gt; String {&lt;br/&gt;        self.tags&lt;br/&gt;            .filter(TagKind::t())&lt;br/&gt;            .filter_map(|t| t.content().map(|l| format!(&amp;#34;#{l}&amp;#34;)))&lt;br/&gt;            .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;()&lt;br/&gt;            .join(&amp;#34;, &amp;#34;)&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;#[easy_ext::ext(NostrKeyringErrorUtils)]&lt;br/&gt;impl nostr_keyring::Error {&lt;br/&gt;    /// Checks if the error indicates a missing keyring entry.&lt;br/&gt;    #[inline]&lt;br/&gt;    pub fn is_keyring_no_entry(&amp;amp;self) -&amp;gt; bool {&lt;br/&gt;        matches!(self, nostr_keyring::Error::Keyring(KeyringError::NoEntry))&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-06T04:14:19Z</updated>
  </entry>

  <entry>
    <id>https://nostr.ae/nevent1qqszdjul4xpgfvc2klqhuecyecpwfvjr7dh5kcvjvnu2dw0mn9pzpuspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q59v7skr</id>
    
      <title type="html">// n34 - A CLI to interact with NIP-34 and other stuff related to ...</title>
    
    <link rel="alternate" href="https://nostr.ae/nevent1qqszdjul4xpgfvc2klqhuecyecpwfvjr7dh5kcvjvnu2dw0mn9pzpuspr9mhxue69uhhyetvv9ujuumgv9jx7amzd9czucm0d5pzq23wpzt24lyrruyvnyvwf597jqefmkwmj30lc68gtc2n9zaza7q59v7skr" />
    <content type="html">
      // n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr&lt;br/&gt;// Copyright (C) 2025 Awiteb &amp;lt;a@4rs.nl&amp;gt;&lt;br/&gt;//&lt;br/&gt;// This program is free software: you can redistribute it and/or modify&lt;br/&gt;// it under the terms of the GNU General Public License as published by&lt;br/&gt;// the Free Software Foundation, either version 3 of the License, or&lt;br/&gt;// (at your option) any later version.&lt;br/&gt;//&lt;br/&gt;// This program is distributed in the hope that it will be useful,&lt;br/&gt;// but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br/&gt;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;br/&gt;// GNU General Public License for more details.&lt;br/&gt;//&lt;br/&gt;// You should have received a copy of the GNU General Public License&lt;br/&gt;// along with this program. If not, see &amp;lt;&lt;a href=&#34;https://gnu.org/licenses/gpl-3.0.html&amp;gt&#34;&gt;https://gnu.org/licenses/gpl-3.0.html&amp;gt&lt;/a&gt;;.&lt;br/&gt;&lt;br/&gt;/// Extension traits for nostr types.&lt;br/&gt;pub mod traits;&lt;br/&gt;/// Utility functions for nostr.&lt;br/&gt;pub mod utils;&lt;br/&gt;&lt;br/&gt;use std::{collections::HashSet, time::Duration};&lt;br/&gt;&lt;br/&gt;use futures::future;&lt;br/&gt;use nostr::{&lt;br/&gt;    event::{Event, EventId, Kind, Tag, TagKind, TagStandard, Tags, UnsignedEvent},&lt;br/&gt;    filter::Filter,&lt;br/&gt;    key::PublicKey,&lt;br/&gt;    nips::{&lt;br/&gt;        nip01::{Coordinate, Metadata},&lt;br/&gt;        nip19::ToBech32,&lt;br/&gt;        nip22,&lt;br/&gt;        nip34::GitRepositoryAnnouncement,&lt;br/&gt;    },&lt;br/&gt;    parser::NostrParser,&lt;br/&gt;    types::RelayUrl,&lt;br/&gt;};&lt;br/&gt;use nostr_sdk::{Client, ClientOptions};&lt;br/&gt;use traits::TokenUtils;&lt;br/&gt;&lt;br/&gt;use crate::{&lt;br/&gt;    cli::{CliOptions, issue::IssueStatus, patch::PatchStatus},&lt;br/&gt;    error::{N34Error, N34Result},&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;/// Timeout duration for the client.&lt;br/&gt;const CLIENT_TIMEOUT: Duration = Duration::from_millis(1500);&lt;br/&gt;/// Length of a Nostr npub (public key) in characters.&lt;br/&gt;const NPUB_LEN: usize = 63;&lt;br/&gt;&lt;br/&gt;/// Parsed content details&lt;br/&gt;#[derive(Clone)]&lt;br/&gt;pub struct ContentDetails {&lt;br/&gt;    /// Public keys of users mentioned in the content.&lt;br/&gt;    pub p_tagged:     HashSet&amp;lt;PublicKey&amp;gt;,&lt;br/&gt;    /// Event IDs and optional relay URLs for quoted events.&lt;br/&gt;    pub quotes:       HashSet&amp;lt;(EventId, Option&amp;lt;RelayUrl&amp;gt;)&amp;gt;,&lt;br/&gt;    /// Hashtags found in the content.&lt;br/&gt;    pub hashtags:     HashSet&amp;lt;String&amp;gt;,&lt;br/&gt;    /// Relays where mentioned users and quoted authors are read.&lt;br/&gt;    pub write_relays: HashSet&amp;lt;RelayUrl&amp;gt;,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;/// A client for interacting with the Nostr relays&lt;br/&gt;#[derive(Clone)]&lt;br/&gt;pub struct NostrClient {&lt;br/&gt;    /// The underlying Nostr client implementation&lt;br/&gt;    pub client: Client,&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl ContentDetails {&lt;br/&gt;    /// Create a new [`ContentDetails`] instance&lt;br/&gt;    pub fn new(&lt;br/&gt;        users: impl IntoIterator&amp;lt;Item = PublicKey&amp;gt;,&lt;br/&gt;        quotes: impl IntoIterator&amp;lt;Item = (EventId, Option&amp;lt;RelayUrl&amp;gt;)&amp;gt;,&lt;br/&gt;        hashtags: impl IntoIterator&amp;lt;Item = String&amp;gt;,&lt;br/&gt;        write_relays: impl IntoIterator&amp;lt;Item = RelayUrl&amp;gt;,&lt;br/&gt;    ) -&amp;gt; Self {&lt;br/&gt;        Self {&lt;br/&gt;            p_tagged:     HashSet::from_iter(users),&lt;br/&gt;            quotes:       HashSet::from_iter(quotes),&lt;br/&gt;            hashtags:     HashSet::from_iter(hashtags),&lt;br/&gt;            write_relays: HashSet::from_iter(write_relays),&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Converts the instance into a list of tags including hashtags, p-tagged&lt;br/&gt;    /// users, and quoted events.&lt;br/&gt;    pub fn into_tags(self) -&amp;gt; Tags {&lt;br/&gt;        let mut tags = Tags::new();&lt;br/&gt;        tags.extend(self.hashtags.into_iter().map(Tag::hashtag));&lt;br/&gt;        tags.extend(self.p_tagged.into_iter().map(Tag::public_key));&lt;br/&gt;        tags.extend(self.quotes.into_iter().map(|(event_id, relay_url)| {&lt;br/&gt;            // TODO: Add the author public key if we know it&lt;br/&gt;            Tag::from_standardized(TagStandard::Quote {&lt;br/&gt;                event_id,&lt;br/&gt;                relay_url,&lt;br/&gt;                public_key: None,&lt;br/&gt;            })&lt;br/&gt;        }));&lt;br/&gt;        tags&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;impl NostrClient {&lt;br/&gt;    /// Creates a new [`NostrClient`] with the given client and options.&lt;br/&gt;    const fn new(client: Client) -&amp;gt; Self {&lt;br/&gt;        Self { client }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Initializes a new [`NostrClient`] instance and connects to the specified&lt;br/&gt;    /// relays.&lt;br/&gt;    pub async fn init(options: &amp;amp;CliOptions, relays: &amp;amp;[RelayUrl]) -&amp;gt; Self {&lt;br/&gt;        let mut client_builder =&lt;br/&gt;            Client::builder().opts(ClientOptions::new().verify_subscriptions(true));&lt;br/&gt;&lt;br/&gt;        if let Ok(Some(signer)) = options.signer().await {&lt;br/&gt;            client_builder = client_builder.signer(signer);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        let client = Self::new(client_builder.build());&lt;br/&gt;&lt;br/&gt;        client.add_relays(relays).await;&lt;br/&gt;        client&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    //// Returns the users public key&lt;br/&gt;    pub async fn pubkey(&amp;amp;self) -&amp;gt; N34Result&amp;lt;PublicKey&amp;gt; {&lt;br/&gt;        self.client&lt;br/&gt;            .signer()&lt;br/&gt;            .await?&lt;br/&gt;            .get_public_key()&lt;br/&gt;            .await&lt;br/&gt;            .map_err(N34Error::SignerError)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Add relays and connect to them&lt;br/&gt;    pub async fn add_relays(&amp;amp;self, relays: &amp;amp;[RelayUrl]) {&lt;br/&gt;        if relays.is_empty() {&lt;br/&gt;            return;&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        let mut tasks = Vec::new();&lt;br/&gt;        for relay in relays {&lt;br/&gt;            let relay = relay.clone();&lt;br/&gt;            let client = self.client.clone();&lt;br/&gt;            tasks.push(tokio::spawn(async move {&lt;br/&gt;                client&lt;br/&gt;                    .add_relay(&amp;amp;relay)&lt;br/&gt;                    .await&lt;br/&gt;                    .expect(&amp;#34;It&amp;#39;s a valid relay url&amp;#34;);&lt;br/&gt;                if let Err(err) = client.try_connect_relay(&amp;amp;relay, CLIENT_TIMEOUT).await {&lt;br/&gt;                    tracing::error!(&amp;#34;Failed to connect to relay &amp;#39;{relay}&amp;#39;: {err}&amp;#34;);&lt;br/&gt;                }&lt;br/&gt;            }));&lt;br/&gt;        }&lt;br/&gt;        future::join_all(tasks).await;&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Add a relay hint and connect to it&lt;br/&gt;    pub async fn add_relay_hint(&amp;amp;self, hint: Option&amp;lt;RelayUrl&amp;gt;) {&lt;br/&gt;        if let Some(relay) = hint {&lt;br/&gt;            self.add_relays(&amp;amp;[relay]).await&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// broadcast an event to the given relays&lt;br/&gt;    pub async fn broadcast(&amp;amp;self, event: &amp;amp;Event, relays: &amp;amp;[RelayUrl]) -&amp;gt; N34Result&amp;lt;()&amp;gt; {&lt;br/&gt;        self.client.send_event_to(relays, event).await?;&lt;br/&gt;        Ok(())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Broadcasts an unsigned event to given relays, optionally broadcast the&lt;br/&gt;    /// relays list event. Returns URLs of relays that successfully received&lt;br/&gt;    /// the event.&lt;br/&gt;    pub async fn send_event_to(&lt;br/&gt;        &amp;amp;self,&lt;br/&gt;        mut event: UnsignedEvent,&lt;br/&gt;        relays_list: Option&amp;lt;&amp;amp;Event&amp;gt;,&lt;br/&gt;        relays: &amp;amp;[RelayUrl],&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;RelayUrl&amp;gt;&amp;gt; {&lt;br/&gt;        self.add_relays(relays).await;&lt;br/&gt;        let event_id = event.id();&lt;br/&gt;&lt;br/&gt;        let (result, ..) = futures::join!(&lt;br/&gt;            async {&lt;br/&gt;                N34Result::Ok(&lt;br/&gt;                    self.client&lt;br/&gt;                        .send_event_to(relays, &amp;amp;event.sign(&amp;amp;self.client.signer().await?).await?)&lt;br/&gt;                        .await?,&lt;br/&gt;                )&lt;br/&gt;            },&lt;br/&gt;            async {&lt;br/&gt;                if let Some(event) = relays_list {&lt;br/&gt;                    let _ = self.client.send_event_to(relays, event).await;&lt;br/&gt;                }&lt;br/&gt;            }&lt;br/&gt;        );&lt;br/&gt;        let result = result?;&lt;br/&gt;&lt;br/&gt;        for relay in &amp;amp;result.success {&lt;br/&gt;            tracing::info!(event_id = %event_id, relay = %relay, &amp;#34;Event sent successfully&amp;#34;);&lt;br/&gt;        }&lt;br/&gt;        for (relay, reason) in &amp;amp;result.failed {&lt;br/&gt;            tracing::warn!(event_id = %event_id, relay = %relay, reason = %reason, &amp;#34;Failed to send event&amp;#34;);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        Ok(result.success.into_iter().collect())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Fetches the first event matching the given filter, or None if no event&lt;br/&gt;    /// is found.&lt;br/&gt;    pub async fn fetch_event(&amp;amp;self, filter: Filter) -&amp;gt; N34Result&amp;lt;Option&amp;lt;Event&amp;gt;&amp;gt; {&lt;br/&gt;        Ok(self&lt;br/&gt;            .client&lt;br/&gt;            .fetch_events(filter.limit(1), CLIENT_TIMEOUT)&lt;br/&gt;            .await?&lt;br/&gt;            .first_owned())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Fetches the events matching the given filter&lt;br/&gt;    pub async fn fetch_events(&amp;amp;self, filter: Filter) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;Event&amp;gt;&amp;gt; {&lt;br/&gt;        // Multiply timeout by 5 to account for multiple events being fetched&lt;br/&gt;        Ok(self&lt;br/&gt;            .client&lt;br/&gt;            .fetch_events(filter, CLIENT_TIMEOUT * 5)&lt;br/&gt;            .await?&lt;br/&gt;            .to_vec())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Try to fetch the repositories and returns them&lt;br/&gt;    pub async fn fetch_repos(&lt;br/&gt;        &amp;amp;self,&lt;br/&gt;        repo_naddrs: &amp;amp;[Coordinate],&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;GitRepositoryAnnouncement&amp;gt;&amp;gt; {&lt;br/&gt;        future::join_all(repo_naddrs.iter().map(|c| {&lt;br/&gt;            async {&lt;br/&gt;                self.fetch_event(&lt;br/&gt;                    Filter::new()&lt;br/&gt;                        .author(c.public_key)&lt;br/&gt;                        .identifier(&amp;amp;c.identifier)&lt;br/&gt;                        .kind(Kind::GitRepoAnnouncement),&lt;br/&gt;                )&lt;br/&gt;                .await?&lt;br/&gt;                .map(|e| utils::event_into_repo(e, &amp;amp;c.identifier))&lt;br/&gt;                .ok_or(N34Error::NotFoundRepo)&lt;br/&gt;            }&lt;br/&gt;        }))&lt;br/&gt;        .await&lt;br/&gt;        .into_iter()&lt;br/&gt;        .collect()&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Fetch the patch by the given id. None if not found&lt;br/&gt;    pub async fn fetch_patch(&amp;amp;self, patch_id: EventId) -&amp;gt; N34Result&amp;lt;Event&amp;gt; {&lt;br/&gt;        self.fetch_event(Filter::new().id(patch_id).kind(Kind::GitPatch))&lt;br/&gt;            .await?&lt;br/&gt;            .ok_or(N34Error::CanNotFoundPatch)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns the username for a given public key. If no username is found,&lt;br/&gt;    /// falls back to a shortened version of the public key.&lt;br/&gt;    pub async fn get_username(&amp;amp;self, user: PublicKey) -&amp;gt; String {&lt;br/&gt;        self.fetch_event(Filter::new().kind(Kind::Metadata).author(user))&lt;br/&gt;            .await&lt;br/&gt;            .ok()&lt;br/&gt;            .flatten()&lt;br/&gt;            .and_then(|e| Metadata::try_from(&amp;amp;e).ok())&lt;br/&gt;            .and_then(|m| m.display_name.or(m.name))&lt;br/&gt;            .unwrap_or_else(|| {&lt;br/&gt;                let pubkey = user.to_bech32().expect(&amp;#34;The error is `Infallible`&amp;#34;);&lt;br/&gt;                format!(&amp;#34;{}...{}&amp;#34;, &amp;amp;pubkey[..8], &amp;amp;pubkey[NPUB_LEN - 8..])&lt;br/&gt;            })&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Get the latest status of an issue by its ID, only considering status&lt;br/&gt;    /// events from authorized_pubkeys. If no valid status event is found,&lt;br/&gt;    /// defaults to Open.&lt;br/&gt;    pub async fn fetch_issue_status(&lt;br/&gt;        &amp;amp;self,&lt;br/&gt;        issue_id: EventId,&lt;br/&gt;        authorized_pubkeys: Vec&amp;lt;PublicKey&amp;gt;,&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;IssueStatus&amp;gt; {&lt;br/&gt;        self.fetch_events(&lt;br/&gt;            Filter::new()&lt;br/&gt;                .event(issue_id)&lt;br/&gt;                .kinds([&lt;br/&gt;                    Kind::GitStatusOpen,&lt;br/&gt;                    Kind::GitStatusApplied,&lt;br/&gt;                    Kind::GitStatusClosed,&lt;br/&gt;                ])&lt;br/&gt;                .authors(utils::dedup(authorized_pubkeys.into_iter())),&lt;br/&gt;        )&lt;br/&gt;        .await?&lt;br/&gt;        .into_iter()&lt;br/&gt;        .max_by_key(|e| e.created_at)&lt;br/&gt;        .map(|status| IssueStatus::try_from(status.kind))&lt;br/&gt;        .unwrap_or_else(|| Ok(IssueStatus::Open))&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Gets the status of a patch. If it&amp;#39;s a revision patch, checks if it&amp;#39;s&lt;br/&gt;    /// closed when the root patch is already merged/applied but doesn&amp;#39;t&lt;br/&gt;    /// reference this revision. Defaults to Open status if no status event&lt;br/&gt;    /// is found.&lt;br/&gt;    pub async fn fetch_patch_status(&lt;br/&gt;        &amp;amp;self,&lt;br/&gt;        root_patch: EventId,&lt;br/&gt;        root_revision: Option&amp;lt;EventId&amp;gt;,&lt;br/&gt;        authorized_pubkeys: Vec&amp;lt;PublicKey&amp;gt;,&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;PatchStatus&amp;gt; {&lt;br/&gt;        let (root_status, event_tags) = self&lt;br/&gt;            .fetch_events(&lt;br/&gt;                Filter::new()&lt;br/&gt;                    .event(root_patch)&lt;br/&gt;                    .kinds([&lt;br/&gt;                        Kind::GitStatusOpen,&lt;br/&gt;                        Kind::GitStatusApplied,&lt;br/&gt;                        Kind::GitStatusClosed,&lt;br/&gt;                        Kind::GitStatusDraft,&lt;br/&gt;                    ])&lt;br/&gt;                    .authors(utils::dedup(authorized_pubkeys.into_iter())),&lt;br/&gt;            )&lt;br/&gt;            .await?&lt;br/&gt;            .into_iter()&lt;br/&gt;            .max_by_key(|e| e.created_at)&lt;br/&gt;            .map(|status| N34Result::Ok((PatchStatus::try_from(status.kind)?, status.tags)))&lt;br/&gt;            .unwrap_or_else(|| Ok((PatchStatus::Open, Tags::new())))?;&lt;br/&gt;&lt;br/&gt;        if let Some(revision_id) = root_revision&lt;br/&gt;            &amp;amp;&amp;amp; root_status.is_merged_or_applied()&lt;br/&gt;            &amp;amp;&amp;amp; !event_tags&lt;br/&gt;                .filter(TagKind::e())&lt;br/&gt;                .any(|t| t.is_reply() &amp;amp;&amp;amp; t.content().is_some_and(|c| c == revision_id.to_hex()))&lt;br/&gt;        {&lt;br/&gt;            return Ok(PatchStatus::Closed);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        Ok(root_status)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    pub async fn fetch_patch_series(&lt;br/&gt;        &amp;amp;self,&lt;br/&gt;        root_patch_id: EventId,&lt;br/&gt;        root_patch_author: PublicKey,&lt;br/&gt;    ) -&amp;gt; N34Result&amp;lt;Vec&amp;lt;Event&amp;gt;&amp;gt; {&lt;br/&gt;        Ok(self&lt;br/&gt;            .fetch_events(&lt;br/&gt;                Filter::new()&lt;br/&gt;                    .kind(Kind::GitPatch)&lt;br/&gt;                    .author(root_patch_author)&lt;br/&gt;                    .event(root_patch_id),&lt;br/&gt;            )&lt;br/&gt;            .await?&lt;br/&gt;            .into_iter()&lt;br/&gt;            .filter(|e| {&lt;br/&gt;                e.tags.iter().any(|t| {&lt;br/&gt;                    t.is_root() &amp;amp;&amp;amp; t.content().is_some_and(|c| c == root_patch_id.to_hex())&lt;br/&gt;                })&lt;br/&gt;            })&lt;br/&gt;            .collect())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Finds the root issue or patch for a given event. If the event is already&lt;br/&gt;    /// a root (issue/patch), returns it directly. For comments, follows&lt;br/&gt;    /// parent/root references until finding the root or failing. Returns&lt;br/&gt;    /// None if no root can be found.&lt;br/&gt;    pub async fn find_root(&amp;amp;self, mut event: Event) -&amp;gt; N34Result&amp;lt;Option&amp;lt;Event&amp;gt;&amp;gt; {&lt;br/&gt;        if !matches!(event.kind, Kind::GitIssue | Kind::GitPatch | Kind::Comment) {&lt;br/&gt;            return Err(N34Error::CanNotReplyToEvent);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        loop {&lt;br/&gt;            if matches!(event.kind, Kind::GitIssue | Kind::GitPatch) {&lt;br/&gt;                return Ok(Some(event));&lt;br/&gt;            }&lt;br/&gt;&lt;br/&gt;            if let Some(nip22::CommentTarget::Event { id, relay_hint, .. }) =&lt;br/&gt;                nip22::extract_root(&amp;amp;event)&lt;br/&gt;            {&lt;br/&gt;                self.add_relay_hint(relay_hint.cloned()).await;&lt;br/&gt;                let root_event = self.fetch_event(Filter::new().id(*id)).await?;&lt;br/&gt;                if let Some(ref root_event) = root_event&lt;br/&gt;                    &amp;amp;&amp;amp; !matches!(root_event.kind, Kind::GitIssue | Kind::GitPatch)&lt;br/&gt;                {&lt;br/&gt;                    return Err(N34Error::CanNotReplyToEvent);&lt;br/&gt;                }&lt;br/&gt;                return Ok(root_event);&lt;br/&gt;            } else if let Some(nip22::CommentTarget::Event { id, relay_hint, .. }) =&lt;br/&gt;                nip22::extract_parent(&amp;amp;event)&lt;br/&gt;            {&lt;br/&gt;                self.add_relay_hint(relay_hint.cloned()).await;&lt;br/&gt;                if let Ok(Some(parent_event)) = self.fetch_event(Filter::new().id(*id)).await {&lt;br/&gt;                    event = parent_event;&lt;br/&gt;                    continue;&lt;br/&gt;                }&lt;br/&gt;            }&lt;br/&gt;&lt;br/&gt;            // Break if: no root/parent tags found, parent/root event fetch failed&lt;br/&gt;            break;&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        Ok(None)&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Fetches the relay list (kind 10002) for the given user. Returns None if&lt;br/&gt;    /// no relays are found.&lt;br/&gt;    pub async fn user_relays_list(&amp;amp;self, user: PublicKey) -&amp;gt; N34Result&amp;lt;Option&amp;lt;Event&amp;gt;&amp;gt; {&lt;br/&gt;        self.fetch_event(Filter::new().author(user).kind(Kind::RelayList))&lt;br/&gt;            .await&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Gets the author of the specified event, if found.&lt;br/&gt;    pub async fn event_author(&amp;amp;self, event_id: EventId) -&amp;gt; N34Result&amp;lt;Option&amp;lt;PublicKey&amp;gt;&amp;gt; {&lt;br/&gt;        Ok(self&lt;br/&gt;            .fetch_event(Filter::new().id(event_id))&lt;br/&gt;            .await?&lt;br/&gt;            .map(|e| e.pubkey))&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns the read relays of the given user if found, otherwise empty&lt;br/&gt;    /// vector&lt;br/&gt;    pub async fn read_relays_from_user(&amp;amp;self, user: PublicKey) -&amp;gt; Vec&amp;lt;RelayUrl&amp;gt; {&lt;br/&gt;        utils::add_read_relays(self.user_relays_list(user).await.ok().flatten().as_ref())&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Returns the read relays of the given users if found, otherwise empty&lt;br/&gt;    /// vector&lt;br/&gt;    pub async fn read_relays_from_users(&amp;amp;self, users: &amp;amp;[PublicKey]) -&amp;gt; Vec&amp;lt;RelayUrl&amp;gt; {&lt;br/&gt;        self.fetch_events(&lt;br/&gt;            Filter::new()&lt;br/&gt;                .kind(nostr::event::Kind::RelayList)&lt;br/&gt;                .authors(utils::dedup(users.iter().copied())),&lt;br/&gt;        )&lt;br/&gt;        .await&lt;br/&gt;        .unwrap_or_default()&lt;br/&gt;        .into_iter()&lt;br/&gt;        .flat_map(|e| utils::add_read_relays(Some(&amp;amp;e)))&lt;br/&gt;        .collect()&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    /// Parse the given content and returns the details that inside it&lt;br/&gt;    pub async fn parse_content(&amp;amp;self, content: &amp;amp;str) -&amp;gt; ContentDetails {&lt;br/&gt;        let mut write_relays = Vec::new();&lt;br/&gt;        let tokens = NostrParser::new().parse(content).collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;();&lt;br/&gt;&lt;br/&gt;        let mut p_tagged_users = tokens&lt;br/&gt;            .iter()&lt;br/&gt;            .filter_map(TokenUtils::extract_public_key)&lt;br/&gt;            .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;();&lt;br/&gt;        let quotes = tokens&lt;br/&gt;            .iter()&lt;br/&gt;            .filter_map(TokenUtils::extract_event_id)&lt;br/&gt;            .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;();&lt;br/&gt;        let hashtags = tokens&lt;br/&gt;            .iter()&lt;br/&gt;            .filter_map(TokenUtils::extract_hashtag)&lt;br/&gt;            .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;();&lt;br/&gt;&lt;br/&gt;        for (user, relays) in &amp;amp;p_tagged_users {&lt;br/&gt;            self.add_relays(relays).await;&lt;br/&gt;            write_relays.extend(self.read_relays_from_user(*user).await);&lt;br/&gt;        }&lt;br/&gt;        for (event_id, relays) in &amp;amp;quotes {&lt;br/&gt;            self.add_relays(relays).await;&lt;br/&gt;            // Add the event author to the p-tagged users&lt;br/&gt;            if let Ok(Some(author)) = self.event_author(*event_id).await {&lt;br/&gt;                p_tagged_users.push((author, Vec::new()));&lt;br/&gt;                write_relays.extend(self.read_relays_from_user(author).await);&lt;br/&gt;            }&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        ContentDetails::new(&lt;br/&gt;            p_tagged_users.into_iter().map(|(p, _)| p),&lt;br/&gt;            quotes.into_iter().map(|(e, r)| (e, r.first().cloned())),&lt;br/&gt;            hashtags,&lt;br/&gt;            write_relays,&lt;br/&gt;        )&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;
    </content>
    <updated>2026-04-06T04:14:07Z</updated>
  </entry>

</feed>