// 往 App 的 SQLite 里注入一条"对方发来的图片消息", 用来肉眼验证: // 1. 图片气泡是不是直接显示图 (而不是 "[图片] xxx" 的文件卡片) // 2. 右键菜单里有没有"复制图片" // // 用法: node scripts/selftest/inject-image-msg.cjs // // 注入完让 App 重启一次 —— 设备列表是启动时从 DB 读的。 const path = require('node:path') const fs = require('node:fs') const zlib = require('node:zlib') const Database = require('better-sqlite3') const dbPath = process.argv[2] if (!dbPath) { console.error('用法: node scripts/selftest/inject-image-msg.cjs ') process.exit(1) } if (!fs.existsSync(dbPath)) { console.error(`找不到 DB: ${dbPath}`) process.exit(1) } // ---- 手写一张 PNG (红黄渐变 + 蓝色方块), 当作"对方发来的图" ---- let crcTable = null function crc32(buf) { if (!crcTable) { crcTable = new Int32Array(256) for (let n = 0; n < 256; n++) { let c = n for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1 crcTable[n] = c } } let c = -1 for (let i = 0; i < buf.length; i++) c = crcTable[(c ^ buf[i]) & 0xff] ^ (c >>> 8) return c ^ -1 } function makePng(w, h, pixel) { const raw = Buffer.alloc((w * 3 + 1) * h) for (let y = 0; y < h; y++) { const rs = y * (w * 3 + 1) raw[rs] = 0 for (let x = 0; x < w; x++) { const [r, g, b] = pixel(x, y) const o = rs + 1 + x * 3 raw[o] = r; raw[o + 1] = g; raw[o + 2] = b } } const chunk = (type, data) => { const len = Buffer.alloc(4); len.writeUInt32BE(data.length) const td = Buffer.concat([Buffer.from(type, 'ascii'), data]) const crc = Buffer.alloc(4); crc.writeUInt32BE(crc32(td) >>> 0) return Buffer.concat([len, td, crc]) } const ihdr = Buffer.alloc(13) ihdr.writeUInt32BE(w, 0); ihdr.writeUInt32BE(h, 4) ihdr[8] = 8; ihdr[9] = 2 return Buffer.concat([ Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), chunk('IHDR', ihdr), chunk('IDAT', zlib.deflateSync(raw)), chunk('IEND', Buffer.alloc(0)), ]) } const W = 640, H = 420 const png = makePng(W, H, (x, y) => { // 渐变底 + 中间一个深色方块 + 底下画几道白条 (能看出缩放后的锯齿) const r = 240 - Math.floor((x / W) * 180) const g = 120 + Math.floor((y / H) * 120) const b = 60 + Math.floor((x / W) * 150) if (x > W * 0.28 && x < W * 0.72 && y > H * 0.22 && y < H * 0.66) return [30, 40, 90] if (y > H * 0.78 && y < H * 0.84 && x > W * 0.1 && x < W * 0.9) return [255, 255, 255] if (y > H * 0.88 && y < H * 0.93 && x > W * 0.1 && x < W * 0.7) return [255, 255, 255] return [r, g, b] }) const thumbDataUrl = 'data:image/png;base64,' + png.toString('base64') const db = new Database(dbPath) db.pragma('journal_mode = WAL') // 本机 deviceId (持久化在 data/device.id) —— conversation_key 要用它算 const idFile = path.join(path.dirname(dbPath), 'device.id') const selfId = fs.readFileSync(idFile, 'utf8').trim() const peerId = 'peer-demo1111-0000-4000-8000-0000000000aa' const now = Date.now() const convKey = [peerId, selfId].sort().join('::') db.prepare(`INSERT OR REPLACE INTO devices (device_id, name, hostname, platform, app_version, last_ip, last_seen, drive_token) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run( peerId, '测试设备B', 'demo-b', 'linux', '0.1.0', '192.168.1.88', now, null, ) // 把原图也落到磁盘 —— 这样"右键复制"走的是**本地文件**那条路 (复制原图, 不是缩略图) const imgFile = path.join(path.dirname(dbPath), 'demo-image.png') fs.writeFileSync(imgFile, png) const body = { type: 'image', messageId: 'msg-demo-image-0001', ts: now, fromDeviceId: peerId, toDeviceId: selfId, fileId: 'file-demo-image-0001', name: '演示图片.png', size: png.length, mime: 'image/png', width: W, height: H, // 关键: 消息体里带缩略图 -> 气泡直接显示图 thumbDataUrl, // 本地原图 (接收端落地路径) —— 供"复制图片"和"点开看大图"用 savedPath: imgFile, } db.prepare(`INSERT OR REPLACE INTO messages (message_id, type, from_id, to_id, ts, body_json, status, conversation_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run( 'msg-demo-image-0001', 'image', peerId, selfId, now, JSON.stringify(body), 'delivered', convKey, ) // 再来一条文字消息, 方便看气泡排版 db.prepare(`INSERT OR REPLACE INTO messages (message_id, type, from_id, to_id, ts, body_json, status, conversation_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run( 'msg-demo-text-0001', 'text', peerId, selfId, now - 1000, JSON.stringify({ type: 'text', messageId: 'msg-demo-text-0001', ts: now - 1000, fromDeviceId: peerId, toDeviceId: selfId, content: '看这张图 👆', }), 'delivered', convKey, ) db.close() console.log(`已注入: 设备 "${peerId}" + 1 条图片消息 (${Math.round(png.length / 1024)}KB) + 1 条文字消息`) console.log(`self=${selfId} conversation_key=${convKey}`) console.log(`原图: ${imgFile}`) console.log(`DB: ${dbPath}`)