aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-16 12:57:19 -0700
committerAdam Soltys <[email protected]>2026-10-16 12:57:19 -0700
commit1dd70cc2c2b9b0a9f65a704ded9e322afabf736f (patch)
tree95df69f7c311632326fb93670d88159cc8d41502 /tools
parentc88f0d860c089e49cebabd53d652859d04143457 (diff)
downloadcoinosv3-server-1dd70cc2c2b9b0a9f65a704ded9e322afabf736f.tar.gz
coinosv3-server-1dd70cc2c2b9b0a9f65a704ded9e322afabf736f.zip
Make this the coinosv3-server repo: the services, with coinosv3 pinned
Carved out of coinosv3 (names, feed, nwcpush, blossom, vmedia, bridge and their tests, with history). The shared wallet code they import (src/ark, noffer, bolt12, popular-feed) now comes from a coinosv3 submodule pinned at 082fc2e, the commit the services have actually run on, so moving here changes no behaviour; bumping it is its own commit. The two text helpers translation uses arrived on the server later than that commit and are vendored under names/vendor at the versions deployed, until the pin moves past them. Root package.json pins the npm versions installed on the server. compose.yml runs names, nwcpush and blossom from this checkout (read-only) with configs and state under $STATE, outside the repo, and pins the Bun image. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'tools')
-rw-r--r--tools/public-feed-cache-test.js2
-rw-r--r--tools/translate-test.js119
2 files changed, 1 insertions, 120 deletions
diff --git a/tools/public-feed-cache-test.js b/tools/public-feed-cache-test.js
index 77f833a..412caef 100644
--- a/tools/public-feed-cache-test.js
+++ b/tools/public-feed-cache-test.js
@@ -1,6 +1,6 @@
import assert from 'node:assert/strict';
import { buildSnapshot, createSnapshotCache, snapshotResponse } from '../feed/cache.js';
-import { popularCandidates } from '../src/popular-feed.js';
+import { popularCandidates } from '../coinosv3/src/popular-feed.js';
const id = 'a'.repeat(64), author = 'b'.repeat(64);
const reaction = (pk, kind = 7, tags = []) => ({ kind, pubkey: pk, created_at: 100, tags: [['e', id], ...tags] });
const post = { kind: 1, id, pubkey: author, created_at: 90, content: 'This is a post about the things we build together.', tags: [] };
diff --git a/tools/translate-test.js b/tools/translate-test.js
deleted file mode 100644
index d3fe095..0000000
--- a/tools/translate-test.js
+++ /dev/null
@@ -1,119 +0,0 @@
-// Translation: posts that read foreign are shown in the app's language
-// ("Translated from Spanish · Show original"), automatically by default;
-// with the switch off they offer Translate instead; ⋯ → Translate asks for
-// any post. The registrar's /translate is stubbed here (window.fetch shim).
-// Run: bun tools/translate-test.js
-import puppeteer from './puppeteer.js';
-import { cacheKeyFor } from '../src/wallet.js';
-import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
-import { buildHtml } from '../build.js';
-const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
-let ok = true;
-const check = (n, c, d = '') => { console.log(` ${c ? '✓' : '✗'} ${n}${d ? ' — ' + d : ''}`); if (!c) ok = false; };
-const AUTHOR = (() => { const sk = generateSecretKey(); return { sk, pk: getPublicKey(sk) }; })();
-const now = Math.floor(Date.now() / 1000);
-const post = (content, ago) => finalizeEvent({ kind: 1, created_at: now - ago, tags: [], content }, AUTHOR.sk);
-const ES = post('Hola a todos, hoy compré café con bitcoin en el mercado y fue muy fácil para todos los que estaban allí', 100);
-const JA = post('ビットコインは素晴らしいです。明日また会いましょう。', 200);
-const TAG = post('#総合部絵チャ', 250);
-const EN = post('An ordinary post in English about the weather and the market today', 300);
-const ANSWERS = {
- [ES.content]: { text: 'Hello everyone, today I bought coffee with bitcoin at the market and it was very easy for everyone there', source: 'es' },
- [JA.content]: { text: 'Bitcoin is great. Let’s meet again tomorrow.', source: 'ja' },
- [EN.content]: { text: EN.content, source: 'en' },
- [TAG.content]: { text: '#総合部絵チャ (general art chat)', source: 'ja' },
-};
-
-const html = await buildHtml({ minify: true, pwa: false });
-const server = Bun.serve({ port: 5303, fetch: () => new Response(html, { headers: { 'content-type': 'text/html' } }) });
-const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: 'new', args: ['--no-sandbox'] });
-const page = await browser.newPage();
-await page.evaluateOnNewDocument((answers) => {
- window.__tr = [];
- try { delete self.Translator; } catch {} // the registrar path, every time
- const real = window.fetch;
- window.fetch = async (url, opts) => {
- if (String(url) === 'https://names.coinos.io/translate') {
- const body = JSON.parse(opts.body);
- window.__tr.push(body);
- const a = answers[body.text];
- await new Promise((r) => setTimeout(r, 150));
- return new Response(JSON.stringify(a || { error: 'nope' }), { status: a ? 200 : 503, headers: { 'content-type': 'application/json' } });
- }
- return real(url, opts);
- };
- const Real = window.WebSocket;
- window.WebSocket = function (...a) {
- const ws = new Real(...a);
- const send = ws.send.bind(ws);
- ws.send = (data) => {
- try { const m = JSON.parse(data); if (m[0] === 'REQ') { setTimeout(() => ws.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EOSE', m[1]]) })), 0); return; } if (m[0] === 'EVENT') return; } catch {}
- return send(data);
- };
- return ws;
- };
- window.WebSocket.prototype = Real.prototype; Object.assign(window.WebSocket, Real);
-}, ANSWERS);
-const errs = [];
-page.on('pageerror', (e) => errs.push(String(e)));
-const click = (t) => page.evaluate((x) => { const e = [...document.querySelectorAll('button')].find((n) => n.textContent.trim().toLowerCase().includes(x)); if (e) { e.click(); return true; } return false; }, t);
-const text = () => page.evaluate(() => document.body.innerText);
-const waitText = async (x, ms = 25000) => { for (let i = 0; i < ms / 250; i++) { if ((await text()).toLowerCase().includes(x.toLowerCase())) return true; await sleep(250); } return false; };
-const has = async (x) => (await text()).includes(x);
-const row = (m) => page.evaluate((m) => { const r = [...document.querySelectorAll('[data-zap-post]')].find((n) => n.textContent.includes(m)); return r ? r.innerText : null; }, m);
-const openFeed = async () => { await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]')?.click()); await sleep(1200); };
-
-try {
- await page.setViewport({ width: 390, height: 844 });
- await page.goto('http://localhost:5303/', { waitUntil: 'domcontentloaded' });
- await page.evaluate(() => { localStorage.setItem('btc-wallet-network', 'regtest'); localStorage.setItem('btc-wallet-lang', 'en'); });
- await page.reload({ waitUntil: 'domcontentloaded' });
- await sleep(400);
- await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
- const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
- await waitText('receive', 20000);
- const base = cacheKeyFor(mn + '\n');
- await page.evaluate(([k, pk, ns]) => {
- localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', pk]], c: '', at: Math.floor(Date.now() / 1000) }));
- localStorage.setItem(k + ':feedNotes', JSON.stringify(ns));
- localStorage.setItem(k + ':profiles', JSON.stringify({ [pk]: { name: 'Globetrotter', t: Date.now() } }));
- localStorage.setItem('btc-wallet-feed:' + k, 'following');
- }, [base, AUTHOR.pk, [ES, JA, TAG, EN]]);
- await page.reload({ waitUntil: 'domcontentloaded' });
- await waitText('receive', 20000);
- await openFeed();
-
- console.log('\n[automatic, the default]');
- check('the Spanish post is shown in English', await waitText('today I bought coffee with bitcoin', 10000) && !(await has('compré café')));
- check('...saying where it came from', /Translated from Spanish/.test(await row('today I bought coffee') || ''), (await row('today I bought coffee') || '').split('\n').slice(-6).join(' | '));
- check('the Japanese post too', await waitText('Bitcoin is great', 5000) && /Translated from Japanese/.test(await row('Bitcoin is great') || ''));
- check('a post that is only a Japanese hashtag gets its tag glossed, the tag kept', await waitText('#総合部絵チャ (general art chat)', 5000));
- const asked = await page.evaluate(() => window.__tr.map((b) => b.text.slice(0, 12) + '>' + b.target));
- check('the English post is left alone (never sent)', asked.length === 3 && !asked.some((x) => x.startsWith('An ordinary')), asked.join(', '));
- await page.evaluate(() => { const r = [...document.querySelectorAll('[data-zap-post]')].find((n) => n.textContent.includes('today I bought coffee')); [...r.querySelectorAll('.note-translate-btn')].find((b) => /original/i.test(b.textContent)).click(); });
- await sleep(400);
- check('Show original brings the Spanish back', await has('compré café') && !(await has('today I bought coffee')));
- check('...with the offer to translate again', /Translate/.test(await row('compré café') || ''));
-
- console.log('\n[⋯ → Translate on a post the guess calls English]');
- await page.evaluate(() => { const r = [...document.querySelectorAll('[data-zap-post]')].find((n) => n.textContent.includes('An ordinary post')); [...r.querySelectorAll('button')].find((b) => b.getAttribute('aria-label') === 'More').click(); });
- await sleep(300);
- check('the menu offers Translate', await page.evaluate(() => [...document.querySelectorAll('.confirm-pop button')].some((b) => /Translate/.test(b.textContent))));
- await page.evaluate(() => [...document.querySelectorAll('.confirm-pop button')].find((b) => /Translate/.test(b.textContent)).click());
- check('it says the post is already in English', await waitText('already in your language', 4000));
-
- console.log('\n[automatic switched off]');
- await page.evaluate(() => localStorage.setItem('coinos-auto-translate', '0'));
- await page.reload({ waitUntil: 'domcontentloaded' });
- await waitText('receive', 20000);
- await openFeed();
- check('foreign posts stay as written', await waitText('明日また会いましょう', 10000) && !(await has('Bitcoin is great')));
- check('...offering Translate', /Translate/.test(await row('明日また') || ''));
- await page.evaluate(() => { const r = [...document.querySelectorAll('[data-zap-post]')].find((n) => n.textContent.includes('明日また')); r.querySelector('.note-translate-btn').click(); });
- check('tapping it translates (remembered from before: no new request)', await waitText('Bitcoin is great', 5000));
- check('no page errors', errs.length === 0, errs.slice(0, 2).join(' | '));
-} finally { await browser.close(); server.stop(true); }
-console.log(ok ? '\n✅ posts translate into the reader\'s language' : '\n❌ failed');
-process.exit(ok ? 0 : 1);