most-box 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/electron/main.js +31 -10
- package/out/404/index.html +2 -2
- package/out/404.html +2 -2
- package/out/__next.__PAGE__.txt +6 -6
- package/out/__next._full.txt +15 -15
- package/out/__next._head.txt +3 -3
- package/out/__next._index.txt +7 -7
- package/out/__next._tree.txt +4 -4
- package/out/_next/static/chunks/{0hpev4am9jpmu.css → 0.4ov9319fecg.css} +1 -1
- package/out/_next/static/chunks/0._8s6pbfw.xk.js +1 -0
- package/out/_next/static/chunks/02jvyg27pp0mz.js +1 -0
- package/out/_next/static/chunks/02zzxfop_k6tl.css +1 -0
- package/out/_next/static/chunks/06lttvu7563zo.css +1 -0
- package/out/_next/static/chunks/{0pt.5cg1t09qs.js → 08qqf0rsi3oot.js} +1 -1
- package/out/_next/static/chunks/{0adx~d-j05c9d.css → 09h7l4i38xc7q.css} +1 -1
- package/out/_next/static/chunks/09hqx79-jkvm_.css +1 -0
- package/out/_next/static/chunks/09vcm1ku9k7o8.js +1 -0
- package/out/_next/static/chunks/0c6e_d2jq179x.js +1 -0
- package/out/_next/static/chunks/0ccalho416.d7.js +1 -0
- package/out/_next/static/chunks/0exj_tg.ew-t3.js +1 -0
- package/out/_next/static/chunks/0f-wz5d~tv-r4.js +1 -0
- package/out/_next/static/chunks/0f0jhsujtf-61.js +1 -0
- package/out/_next/static/chunks/0hyds~bp.auvh.js +1 -0
- package/out/_next/static/chunks/0ig8a4sazk3~2.css +1 -0
- package/out/_next/static/chunks/0iq1h7g4dudg8.js +1 -0
- package/out/_next/static/chunks/0knnzo9aih48r.js +1 -0
- package/out/_next/static/chunks/0nzyk~sg_tn._.js +1 -0
- package/out/_next/static/chunks/{12nr19.nnn6s3.js → 0t_3xxx4zkerp.js} +2 -2
- package/out/_next/static/chunks/0w~2fjq86t7c7.css +1 -0
- package/out/_next/static/chunks/0xrqerhosrp9~.js +1 -0
- package/out/_next/static/chunks/0xx_10jns1.s7.css +1 -0
- package/out/_next/static/chunks/14-fm9r_mom81.js +1 -0
- package/out/_next/static/chunks/14jhy~xia8lh8.js +1 -0
- package/out/_next/static/chunks/{turbopack-0xta0kqwzkf28.js → turbopack-05qngmxam3ar~.js} +1 -1
- package/out/_not-found/__next._full.txt +12 -12
- package/out/_not-found/__next._head.txt +3 -3
- package/out/_not-found/__next._index.txt +7 -7
- package/out/_not-found/__next._not-found.__PAGE__.txt +4 -4
- package/out/_not-found/__next._not-found.txt +3 -3
- package/out/_not-found/__next._tree.txt +2 -2
- package/out/_not-found/index.html +2 -2
- package/out/_not-found/index.txt +12 -12
- package/out/admin/__next._full.txt +14 -14
- package/out/admin/__next._head.txt +3 -3
- package/out/admin/__next._index.txt +7 -7
- package/out/admin/__next._tree.txt +3 -3
- package/out/admin/__next.admin.__PAGE__.txt +4 -4
- package/out/admin/__next.admin.txt +4 -4
- package/out/admin/index.html +2 -2
- package/out/admin/index.txt +14 -14
- package/out/app/__next._full.txt +13 -13
- package/out/app/__next._head.txt +3 -3
- package/out/app/__next._index.txt +7 -7
- package/out/app/__next._tree.txt +2 -2
- package/out/app/__next.app.__PAGE__.txt +4 -4
- package/out/app/__next.app.txt +3 -3
- package/out/app/index.html +2 -2
- package/out/app/index.txt +13 -13
- package/out/chat/__next._full.txt +14 -14
- package/out/chat/__next._head.txt +3 -3
- package/out/chat/__next._index.txt +7 -7
- package/out/chat/__next._tree.txt +3 -3
- package/out/chat/__next.chat.__PAGE__.txt +4 -4
- package/out/chat/__next.chat.txt +4 -4
- package/out/chat/index.html +2 -2
- package/out/chat/index.txt +14 -14
- package/out/chat/join/__next._full.txt +14 -14
- package/out/chat/join/__next._head.txt +3 -3
- package/out/chat/join/__next._index.txt +7 -7
- package/out/chat/join/__next._tree.txt +3 -3
- package/out/chat/join/__next.chat.join.__PAGE__.txt +4 -4
- package/out/chat/join/__next.chat.join.txt +3 -3
- package/out/chat/join/__next.chat.txt +4 -4
- package/out/chat/join/index.html +2 -2
- package/out/chat/join/index.txt +14 -14
- package/out/download/__next._full.txt +34 -36
- package/out/download/__next._head.txt +3 -3
- package/out/download/__next._index.txt +7 -7
- package/out/download/__next._tree.txt +4 -4
- package/out/download/__next.download.__PAGE__.txt +7 -7
- package/out/download/__next.download.txt +3 -3
- package/out/download/index.html +2 -2
- package/out/download/index.txt +34 -36
- package/out/favicon.ico +0 -0
- package/out/game/__next._full.txt +20 -0
- package/out/game/__next._head.txt +5 -0
- package/out/game/__next._index.txt +9 -0
- package/out/game/__next._tree.txt +5 -0
- package/out/game/__next.game.__PAGE__.txt +6 -0
- package/out/game/__next.game.txt +5 -0
- package/out/game/gandengyan/__next._full.txt +26 -0
- package/out/game/gandengyan/__next._head.txt +5 -0
- package/out/game/gandengyan/__next._index.txt +9 -0
- package/out/game/gandengyan/__next._tree.txt +6 -0
- package/out/game/gandengyan/__next.game.gandengyan.__PAGE__.txt +10 -0
- package/out/game/gandengyan/__next.game.gandengyan.txt +5 -0
- package/out/game/gandengyan/__next.game.txt +5 -0
- package/out/game/gandengyan/index.html +15 -0
- package/out/game/gandengyan/index.txt +26 -0
- package/out/game/index.html +1 -0
- package/out/game/index.txt +20 -0
- package/out/game/zhajinhua/__next._full.txt +25 -0
- package/out/game/zhajinhua/__next._head.txt +5 -0
- package/out/game/zhajinhua/__next._index.txt +9 -0
- package/out/game/zhajinhua/__next._tree.txt +5 -0
- package/out/game/zhajinhua/__next.game.txt +5 -0
- package/out/game/zhajinhua/__next.game.zhajinhua.__PAGE__.txt +9 -0
- package/out/game/zhajinhua/__next.game.zhajinhua.txt +5 -0
- package/out/game/zhajinhua/index.html +15 -0
- package/out/game/zhajinhua/index.txt +25 -0
- package/out/index.html +2 -2
- package/out/index.txt +15 -15
- package/out/logo-512.png +0 -0
- package/out/logo.ico +0 -0
- package/out/logo.svg +12 -0
- package/out/note/__next._full.txt +13 -13
- package/out/note/__next._head.txt +3 -3
- package/out/note/__next._index.txt +7 -7
- package/out/note/__next._tree.txt +2 -2
- package/out/note/__next.note.__PAGE__.txt +4 -4
- package/out/note/__next.note.txt +3 -3
- package/out/note/index.html +2 -2
- package/out/note/index.txt +13 -13
- package/out/ping/__next._full.txt +14 -14
- package/out/ping/__next._head.txt +3 -3
- package/out/ping/__next._index.txt +7 -7
- package/out/ping/__next._tree.txt +3 -3
- package/out/ping/__next.ping.__PAGE__.txt +5 -5
- package/out/ping/__next.ping.txt +3 -3
- package/out/ping/index.html +2 -2
- package/out/ping/index.txt +14 -14
- package/out/web3/__next._full.txt +13 -13
- package/out/web3/__next._head.txt +3 -3
- package/out/web3/__next._index.txt +7 -7
- package/out/web3/__next._tree.txt +2 -2
- package/out/web3/__next.web3.__PAGE__.txt +4 -4
- package/out/web3/__next.web3.txt +3 -3
- package/out/web3/ed25519/__next._full.txt +11 -11
- package/out/web3/ed25519/__next._head.txt +3 -3
- package/out/web3/ed25519/__next._index.txt +7 -7
- package/out/web3/ed25519/__next._tree.txt +2 -2
- package/out/web3/ed25519/__next.web3.ed25519.__PAGE__.txt +2 -2
- package/out/web3/ed25519/__next.web3.ed25519.txt +3 -3
- package/out/web3/ed25519/__next.web3.txt +3 -3
- package/out/web3/ed25519/index.html +1 -1
- package/out/web3/ed25519/index.txt +11 -11
- package/out/web3/index.html +2 -2
- package/out/web3/index.txt +13 -13
- package/out/web3/tools/__next._full.txt +11 -11
- package/out/web3/tools/__next._head.txt +3 -3
- package/out/web3/tools/__next._index.txt +7 -7
- package/out/web3/tools/__next._tree.txt +2 -2
- package/out/web3/tools/__next.web3.tools.__PAGE__.txt +2 -2
- package/out/web3/tools/__next.web3.tools.txt +3 -3
- package/out/web3/tools/__next.web3.txt +3 -3
- package/out/web3/tools/index.html +1 -1
- package/out/web3/tools/index.txt +11 -11
- package/package.json +6 -5
- package/public/favicon.ico +0 -0
- package/public/logo-512.png +0 -0
- package/public/logo.ico +0 -0
- package/public/logo.svg +12 -0
- package/server/index.js +0 -8
- package/server/src/config.js +1 -1
- package/server/src/core/gameRoom.js +222 -0
- package/server/src/core/zhajinhua.js +563 -0
- package/server/src/games/gandengyan.js +354 -413
- package/server/src/http/app.js +22 -14
- package/server/src/http/uploads.js +1 -0
- package/server/src/index.js +275 -90
- package/server/src/utils/avatar.js +3 -2
- package/out/_next/static/chunks/04mo7rr..0_1q.js +0 -1
- package/out/_next/static/chunks/06rf3qq5ggs6v.js +0 -1
- package/out/_next/static/chunks/07td.jq7xff84.css +0 -1
- package/out/_next/static/chunks/0_0oph_z1az14.js +0 -1
- package/out/_next/static/chunks/0ao1lbi4b.sfa.js +0 -1
- package/out/_next/static/chunks/0cl7d~7abnk_p.css +0 -1
- package/out/_next/static/chunks/0d306t1wvjpdx.js +0 -1
- package/out/_next/static/chunks/0g_a~e050bgzg.css +0 -1
- package/out/_next/static/chunks/0m_5nb6x8qy._.js +0 -1
- package/out/_next/static/chunks/0n.ayxmsar6e5.js +0 -1
- package/out/_next/static/chunks/0olqjomda37-e.js +0 -1
- package/out/_next/static/chunks/0qgx9t4jx16ua.css +0 -1
- package/out/_next/static/chunks/0s~g.l~x049o2.js +0 -1
- package/out/_next/static/chunks/0voe1.ttrh84k.css +0 -1
- package/out/_next/static/chunks/0wtf0xsiicxx6.js +0 -1
- package/out/_next/static/chunks/0x.ky97owcxxs.js +0 -1
- package/out/_next/static/chunks/0ysj5b94vu4ri.js +0 -1
- package/out/_next/static/chunks/153-sz7s.qml2.js +0 -1
- package/out/_next/static/chunks/17cwkb2yn_akx.js +0 -1
- package/out/_next/static/chunks/184hxsuf-5c84.js +0 -1
- package/out/gandengyan/__next._full.txt +0 -25
- package/out/gandengyan/__next._head.txt +0 -5
- package/out/gandengyan/__next._index.txt +0 -9
- package/out/gandengyan/__next._tree.txt +0 -5
- package/out/gandengyan/__next.gandengyan.__PAGE__.txt +0 -10
- package/out/gandengyan/__next.gandengyan.txt +0 -5
- package/out/gandengyan/index.html +0 -15
- package/out/gandengyan/index.txt +0 -25
- /package/out/_next/static/{aPEZ4zaaR5W3WpSZ0dFsa → 2smv1H9Y4Z2Ri-SL-UFgR}/_buildManifest.js +0 -0
- /package/out/_next/static/{aPEZ4zaaR5W3WpSZ0dFsa → 2smv1H9Y4Z2Ri-SL-UFgR}/_clientMiddlewareManifest.js +0 -0
- /package/out/_next/static/{aPEZ4zaaR5W3WpSZ0dFsa → 2smv1H9Y4Z2Ri-SL-UFgR}/_ssgManifest.js +0 -0
- /package/out/{pwa-512x512.png → avatar.png} +0 -0
- /package/public/{pwa-512x512.png → avatar.png} +0 -0
package/server/src/http/app.js
CHANGED
|
@@ -672,16 +672,16 @@ export function createApp(engine, options = {}) {
|
|
|
672
672
|
return c.json({ error: parsed.error }, 400)
|
|
673
673
|
}
|
|
674
674
|
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
if (
|
|
675
|
+
const localAvailability = await engine.getLocalCidAvailability(body.link, {
|
|
676
|
+
ownerAddress: c.get('userAddress'),
|
|
677
|
+
})
|
|
678
|
+
if (localAvailability) {
|
|
679
679
|
return c.json({
|
|
680
680
|
success: true,
|
|
681
681
|
available: true,
|
|
682
682
|
cid: parsed.cid,
|
|
683
|
-
fileName:
|
|
684
|
-
size: Number(
|
|
683
|
+
fileName: localAvailability.fileName,
|
|
684
|
+
size: Number(localAvailability.size) || null,
|
|
685
685
|
alreadyExists: true,
|
|
686
686
|
})
|
|
687
687
|
}
|
|
@@ -719,11 +719,13 @@ export function createApp(engine, options = {}) {
|
|
|
719
719
|
return c.json({ error: parsed.error }, 400)
|
|
720
720
|
}
|
|
721
721
|
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
if (
|
|
726
|
-
console.log(
|
|
722
|
+
const localAvailability = await engine.getLocalCidAvailability(body.link, {
|
|
723
|
+
ownerAddress: c.get('userAddress'),
|
|
724
|
+
})
|
|
725
|
+
if (localAvailability) {
|
|
726
|
+
console.log(
|
|
727
|
+
`[MostBox] CID content already exists locally: ${parsed.cid}`
|
|
728
|
+
)
|
|
727
729
|
try {
|
|
728
730
|
const result = await engine.downloadFile(body.link, taskId, {
|
|
729
731
|
ownerAddress: c.get('userAddress'),
|
|
@@ -801,7 +803,7 @@ export function createApp(engine, options = {}) {
|
|
|
801
803
|
})
|
|
802
804
|
return c.json({ success: true, ...result })
|
|
803
805
|
} catch (err) {
|
|
804
|
-
return c
|
|
806
|
+
return badRequestOrAppError(c, err)
|
|
805
807
|
}
|
|
806
808
|
})
|
|
807
809
|
|
|
@@ -959,7 +961,13 @@ export function createApp(engine, options = {}) {
|
|
|
959
961
|
})
|
|
960
962
|
|
|
961
963
|
app.get('/api/channels', c => {
|
|
962
|
-
return c.json(
|
|
964
|
+
return c.json(
|
|
965
|
+
engine.listChannels({
|
|
966
|
+
ownerAddress: c.get('userAddress'),
|
|
967
|
+
type: c.req.query('type'),
|
|
968
|
+
excludeType: c.req.query('excludeType'),
|
|
969
|
+
})
|
|
970
|
+
)
|
|
963
971
|
})
|
|
964
972
|
|
|
965
973
|
app.delete('/api/channels/:name', async c => {
|
|
@@ -1065,7 +1073,7 @@ export function createApp(engine, options = {}) {
|
|
|
1065
1073
|
})
|
|
1066
1074
|
return c.json({ success: true, ...result })
|
|
1067
1075
|
} catch (err) {
|
|
1068
|
-
return c
|
|
1076
|
+
return badRequestOrAppError(c, err)
|
|
1069
1077
|
}
|
|
1070
1078
|
})
|
|
1071
1079
|
|
package/server/src/index.js
CHANGED
|
@@ -48,7 +48,6 @@ import {
|
|
|
48
48
|
SWARM_KEEP_ALIVE_INTERVAL,
|
|
49
49
|
SWARM_RANDOM_PUNCH_INTERVAL,
|
|
50
50
|
DRIVE_ENTRY_TIMEOUT,
|
|
51
|
-
DRIVE_SYNC_TIMEOUT,
|
|
52
51
|
STREAM_READ_TIMEOUT,
|
|
53
52
|
FILE_WRITE_CHUNK_SIZE,
|
|
54
53
|
DOWNLOAD_POLL_INTERVAL_MIN,
|
|
@@ -67,12 +66,28 @@ import {
|
|
|
67
66
|
} from './config.js'
|
|
68
67
|
|
|
69
68
|
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
|
69
|
+
const CHAT_FILE_ROOT = 'chat-file'
|
|
70
70
|
|
|
71
71
|
function normalizeOwnerAddress(address) {
|
|
72
72
|
const value = String(address || '').trim()
|
|
73
73
|
return /^0x[a-fA-F0-9]{40}$/.test(value) ? value.toLowerCase() : ''
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
function getPathBaseName(fileName) {
|
|
77
|
+
const parts = String(fileName || '').split('/').filter(Boolean)
|
|
78
|
+
return parts[parts.length - 1] || 'unnamed_file'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getDisplayPathFolder(fileName) {
|
|
82
|
+
const parts = String(fileName || '').split('/').filter(Boolean)
|
|
83
|
+
parts.pop()
|
|
84
|
+
return parts.join('/')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildMostLink(cid, fileName) {
|
|
88
|
+
return `most://${cid}?filename=${encodeURIComponent(fileName)}`
|
|
89
|
+
}
|
|
90
|
+
|
|
76
91
|
function createOfflineSwarm() {
|
|
77
92
|
return {
|
|
78
93
|
connections: new Set(),
|
|
@@ -528,6 +543,10 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
528
543
|
}
|
|
529
544
|
}
|
|
530
545
|
|
|
546
|
+
this.#assertDisplayNameAvailable(safeFileName, {
|
|
547
|
+
ownerAddress,
|
|
548
|
+
})
|
|
549
|
+
|
|
531
550
|
// 获取或创建该 CID 对应的 drive
|
|
532
551
|
let drive = this.#drives.get(name)
|
|
533
552
|
|
|
@@ -644,39 +663,48 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
644
663
|
const cidString = parsed.cid
|
|
645
664
|
console.log(`[MostBox] Parsed CID: ${cidString}`)
|
|
646
665
|
const { driveName: name } = this.#getCidInfo(cidString)
|
|
666
|
+
const linkFileName = sanitizeFilename(parsed.fileName)
|
|
647
667
|
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
668
|
+
const localContent = await this.#getLocalCidContent(cidString, {
|
|
669
|
+
ownerAddress,
|
|
670
|
+
public: true,
|
|
671
|
+
allowHoldingFallback: true,
|
|
672
|
+
})
|
|
673
|
+
if (localContent) {
|
|
674
|
+
const existingFile = localContent.fileRecord
|
|
675
|
+
console.log(
|
|
676
|
+
`[MostBox] CID content already exists locally: ${cidString}`
|
|
677
|
+
)
|
|
653
678
|
const existingHolding = this.#holdings.find(
|
|
654
679
|
item => item.cid === cidString
|
|
655
680
|
)
|
|
656
|
-
const existingSize = Number(existingFile.size)
|
|
657
681
|
await this.#joinCidTopicInternal(cidString, {
|
|
658
682
|
server: true,
|
|
659
683
|
client: false,
|
|
660
684
|
})
|
|
661
685
|
this.#upsertHolding({
|
|
662
686
|
cid: cidString,
|
|
663
|
-
fileName:
|
|
687
|
+
fileName:
|
|
688
|
+
existingHolding?.fileName || existingFile?.fileName || linkFileName,
|
|
664
689
|
size:
|
|
665
690
|
existingHolding?.size ??
|
|
666
|
-
(Number.isFinite(
|
|
691
|
+
(Number.isFinite(localContent.size) ? localContent.size : 0),
|
|
667
692
|
localPath:
|
|
668
|
-
existingHolding?.localPath || existingFile
|
|
669
|
-
driveName: existingFile
|
|
693
|
+
existingHolding?.localPath || existingFile?.localPath || null,
|
|
694
|
+
driveName: existingFile?.driveName || name,
|
|
670
695
|
source: existingHolding?.source || 'published',
|
|
671
696
|
})
|
|
672
697
|
return {
|
|
673
698
|
taskId,
|
|
674
|
-
fileName:
|
|
699
|
+
fileName: linkFileName,
|
|
675
700
|
alreadyExists: true,
|
|
676
701
|
}
|
|
677
702
|
}
|
|
678
703
|
|
|
679
|
-
|
|
704
|
+
this.#assertDisplayNameAvailable(linkFileName, {
|
|
705
|
+
ownerAddress,
|
|
706
|
+
excludeCid: cidString,
|
|
707
|
+
})
|
|
680
708
|
|
|
681
709
|
if (taskState.aborted) throw new Error('Download cancelled')
|
|
682
710
|
|
|
@@ -924,11 +952,14 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
924
952
|
const existingIndex = this.#publishedFiles.findIndex(
|
|
925
953
|
f => f.cid === cidString && this.#recordMatchesOwner(f, ownerAddress)
|
|
926
954
|
)
|
|
955
|
+
this.#assertDisplayNameAvailable(sanitizedFileName, {
|
|
956
|
+
ownerAddress,
|
|
957
|
+
excludeCid: cidString,
|
|
958
|
+
})
|
|
927
959
|
if (existingIndex !== -1) {
|
|
928
960
|
const existing = this.#publishedFiles[existingIndex]
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
}
|
|
961
|
+
existing.fileName = sanitizedFileName
|
|
962
|
+
existing.driveName = name
|
|
932
963
|
existing.publishedAt = new Date().toISOString()
|
|
933
964
|
} else {
|
|
934
965
|
this.#publishedFiles.push({
|
|
@@ -959,6 +990,35 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
959
990
|
}
|
|
960
991
|
}
|
|
961
992
|
|
|
993
|
+
/**
|
|
994
|
+
* 快速检查 most:// 链接对应的 CID 内容是否已在本机可读。
|
|
995
|
+
*/
|
|
996
|
+
async getLocalCidAvailability(link, options = {}) {
|
|
997
|
+
this.#ensureInitialized()
|
|
998
|
+
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
999
|
+
const parsed = parseMostLink(link)
|
|
1000
|
+
if (parsed.error) {
|
|
1001
|
+
throw new ValidationError(parsed.error)
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const localContent = await this.#getLocalCidContent(parsed.cid, {
|
|
1005
|
+
ownerAddress,
|
|
1006
|
+
public: true,
|
|
1007
|
+
allowHoldingFallback: true,
|
|
1008
|
+
})
|
|
1009
|
+
if (!localContent) {
|
|
1010
|
+
return null
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
return {
|
|
1014
|
+
available: true,
|
|
1015
|
+
cid: parsed.cid,
|
|
1016
|
+
fileName: sanitizeFilename(parsed.fileName),
|
|
1017
|
+
size: localContent.size,
|
|
1018
|
+
alreadyExists: true,
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
962
1022
|
/**
|
|
963
1023
|
* 检测 most:// 链接当前是否能找到可下载内容,但不读取文件内容。
|
|
964
1024
|
* @param {string} link - most:// 链接
|
|
@@ -978,15 +1038,17 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
978
1038
|
|
|
979
1039
|
const cidString = parsed.cid
|
|
980
1040
|
const { driveName: name } = this.#getCidInfo(cidString)
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1041
|
+
const localContent = await this.#getLocalCidContent(cidString, {
|
|
1042
|
+
ownerAddress,
|
|
1043
|
+
public: true,
|
|
1044
|
+
allowHoldingFallback: true,
|
|
1045
|
+
})
|
|
1046
|
+
if (localContent) {
|
|
985
1047
|
return {
|
|
986
1048
|
available: true,
|
|
987
1049
|
cid: cidString,
|
|
988
|
-
fileName:
|
|
989
|
-
size:
|
|
1050
|
+
fileName: sanitizeFilename(parsed.fileName),
|
|
1051
|
+
size: localContent.size,
|
|
990
1052
|
alreadyExists: true,
|
|
991
1053
|
}
|
|
992
1054
|
}
|
|
@@ -1172,6 +1234,20 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1172
1234
|
|
|
1173
1235
|
const { driveName } = this.#getCidInfo(fileRecord.cid)
|
|
1174
1236
|
|
|
1237
|
+
const existingIndex = this.#publishedFiles.findIndex(
|
|
1238
|
+
f => f.cid === fileRecord.cid && this.#recordMatchesOwner(f, ownerAddress)
|
|
1239
|
+
)
|
|
1240
|
+
if (existingIndex !== -1) {
|
|
1241
|
+
this.#trashFiles.splice(index, 1)
|
|
1242
|
+
this.#saveTrashMetadata()
|
|
1243
|
+
return this.listPublishedFiles({ ownerAddress })
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
this.#assertDisplayNameAvailable(fileRecord.fileName, {
|
|
1247
|
+
ownerAddress,
|
|
1248
|
+
excludeCid: fileRecord.cid,
|
|
1249
|
+
})
|
|
1250
|
+
|
|
1175
1251
|
this.#publishedFiles.push({
|
|
1176
1252
|
fileName: fileRecord.fileName,
|
|
1177
1253
|
cid: fileRecord.cid,
|
|
@@ -1361,6 +1437,10 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1361
1437
|
throw new Error('File not found')
|
|
1362
1438
|
}
|
|
1363
1439
|
const safeFileName = sanitizeFilename(newFileName)
|
|
1440
|
+
this.#assertDisplayNameAvailable(safeFileName, {
|
|
1441
|
+
ownerAddress,
|
|
1442
|
+
excludeCid: cid,
|
|
1443
|
+
})
|
|
1364
1444
|
this.#publishedFiles[index].fileName = safeFileName
|
|
1365
1445
|
this.#publishedFiles[index].publishedAt = new Date().toISOString()
|
|
1366
1446
|
this.#savePublishedMetadata()
|
|
@@ -1382,7 +1462,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1382
1462
|
this.#ensureInitialized()
|
|
1383
1463
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1384
1464
|
const prefix = oldPath + '/'
|
|
1385
|
-
const
|
|
1465
|
+
const updates = []
|
|
1386
1466
|
|
|
1387
1467
|
for (const file of this.#publishedFiles) {
|
|
1388
1468
|
if (
|
|
@@ -1393,16 +1473,27 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1393
1473
|
const newFileName = sanitizeFilename(
|
|
1394
1474
|
remainder ? newPath + '/' + remainder : newPath
|
|
1395
1475
|
)
|
|
1396
|
-
|
|
1397
|
-
file.publishedAt = new Date().toISOString()
|
|
1398
|
-
updatedFiles.push({
|
|
1399
|
-
cid: file.cid,
|
|
1400
|
-
fileName: file.fileName,
|
|
1401
|
-
link: `most://${file.cid}?filename=${encodeURIComponent(file.fileName)}`,
|
|
1402
|
-
})
|
|
1476
|
+
updates.push({ file, newFileName })
|
|
1403
1477
|
}
|
|
1404
1478
|
}
|
|
1405
1479
|
|
|
1480
|
+
for (const { file, newFileName } of updates) {
|
|
1481
|
+
this.#assertDisplayNameAvailable(newFileName, {
|
|
1482
|
+
ownerAddress,
|
|
1483
|
+
excludeCid: file.cid,
|
|
1484
|
+
})
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
const updatedFiles = updates.map(({ file, newFileName }) => {
|
|
1488
|
+
file.fileName = newFileName
|
|
1489
|
+
file.publishedAt = new Date().toISOString()
|
|
1490
|
+
return {
|
|
1491
|
+
cid: file.cid,
|
|
1492
|
+
fileName: file.fileName,
|
|
1493
|
+
link: `most://${file.cid}?filename=${encodeURIComponent(file.fileName)}`,
|
|
1494
|
+
}
|
|
1495
|
+
})
|
|
1496
|
+
|
|
1406
1497
|
if (updatedFiles.length > 0) {
|
|
1407
1498
|
this.#savePublishedMetadata()
|
|
1408
1499
|
}
|
|
@@ -1678,28 +1769,23 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1678
1769
|
options = {}
|
|
1679
1770
|
) {
|
|
1680
1771
|
this.#ensureInitialized()
|
|
1772
|
+
if (typeof offset === 'object' && offset !== null) {
|
|
1773
|
+
options = offset
|
|
1774
|
+
offset = 0
|
|
1775
|
+
limit = DEFAULT_READ_LIMIT
|
|
1776
|
+
}
|
|
1681
1777
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1682
1778
|
|
|
1683
|
-
const
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
)
|
|
1688
|
-
if (!fileRecord) {
|
|
1779
|
+
const localContent = await this.#getLocalCidContent(cid, {
|
|
1780
|
+
ownerAddress,
|
|
1781
|
+
public: options.public,
|
|
1782
|
+
})
|
|
1783
|
+
if (!localContent) {
|
|
1689
1784
|
throw new Error('File not found')
|
|
1690
1785
|
}
|
|
1691
1786
|
|
|
1692
|
-
const drive = await this.#getDriveForFile(fileRecord)
|
|
1693
|
-
|
|
1694
|
-
// Hyperdrive 中 key 为 '/' + cid
|
|
1695
1787
|
const driveKey = '/' + cid
|
|
1696
|
-
const
|
|
1697
|
-
wait: true,
|
|
1698
|
-
timeout: DRIVE_ENTRY_TIMEOUT,
|
|
1699
|
-
})
|
|
1700
|
-
if (!entry || !entry.value) {
|
|
1701
|
-
throw new Error('File content not available')
|
|
1702
|
-
}
|
|
1788
|
+
const { drive } = localContent
|
|
1703
1789
|
|
|
1704
1790
|
const chunks = []
|
|
1705
1791
|
const stream = drive.createReadStream(driveKey, {
|
|
@@ -1743,25 +1829,16 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1743
1829
|
this.#ensureInitialized()
|
|
1744
1830
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1745
1831
|
|
|
1746
|
-
const
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
)
|
|
1751
|
-
if (!fileRecord) {
|
|
1832
|
+
const localContent = await this.#getLocalCidContent(cid, {
|
|
1833
|
+
ownerAddress,
|
|
1834
|
+
public: options.public,
|
|
1835
|
+
})
|
|
1836
|
+
if (!localContent) {
|
|
1752
1837
|
throw new Error('File not found')
|
|
1753
1838
|
}
|
|
1754
1839
|
|
|
1755
|
-
const drive = await this.#getDriveForFile(fileRecord)
|
|
1756
|
-
|
|
1757
1840
|
const driveKey = '/' + cid
|
|
1758
|
-
const
|
|
1759
|
-
wait: true,
|
|
1760
|
-
timeout: DRIVE_ENTRY_TIMEOUT,
|
|
1761
|
-
})
|
|
1762
|
-
if (!entry || !entry.value || !entry.value.blob) {
|
|
1763
|
-
throw new Error('File content not available')
|
|
1764
|
-
}
|
|
1841
|
+
const { drive, entry, fileRecord } = localContent
|
|
1765
1842
|
|
|
1766
1843
|
const totalSize = entry.value.blob.byteLength || 0
|
|
1767
1844
|
|
|
@@ -1808,19 +1885,63 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1808
1885
|
return { buffer, fileName: fileRecord.fileName, totalSize }
|
|
1809
1886
|
}
|
|
1810
1887
|
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1888
|
+
async #hasLocalDriveContent(drive, key) {
|
|
1889
|
+
try {
|
|
1890
|
+
return await drive.has(key)
|
|
1891
|
+
} catch {
|
|
1892
|
+
return false
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
async #getLocalCidContent(cid, options = {}) {
|
|
1897
|
+
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1898
|
+
const fileRecord = this.#publishedFiles.find(
|
|
1899
|
+
f =>
|
|
1900
|
+
f.cid === cid &&
|
|
1901
|
+
(options.public || this.#recordMatchesOwner(f, ownerAddress))
|
|
1902
|
+
)
|
|
1903
|
+
if (!options.allowHoldingFallback && !fileRecord) {
|
|
1904
|
+
return null
|
|
1905
|
+
}
|
|
1906
|
+
const holding = this.#holdings.find(item => item.cid === cid)
|
|
1907
|
+
const { driveName } = this.#getCidInfo(cid)
|
|
1908
|
+
const drive = await this.#getOrCreateDrive(
|
|
1909
|
+
fileRecord?.driveName || holding?.driveName || driveName,
|
|
1910
|
+
{ server: true, client: false }
|
|
1911
|
+
)
|
|
1912
|
+
const driveKey = '/' + cid
|
|
1913
|
+
|
|
1914
|
+
try {
|
|
1915
|
+
const entry = await drive.entry(driveKey, { wait: false })
|
|
1916
|
+
if (!entry?.value?.blob) {
|
|
1917
|
+
return null
|
|
1918
|
+
}
|
|
1919
|
+
const hasContent = await this.#hasLocalDriveContent(drive, driveKey)
|
|
1920
|
+
if (!hasContent) {
|
|
1921
|
+
return null
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
const size =
|
|
1925
|
+
Number(entry.value.blob.byteLength) ||
|
|
1926
|
+
Number(fileRecord?.size) ||
|
|
1927
|
+
Number(holding?.size) ||
|
|
1928
|
+
0
|
|
1929
|
+
return {
|
|
1930
|
+
drive,
|
|
1931
|
+
entry,
|
|
1932
|
+
size,
|
|
1933
|
+
fileRecord: fileRecord || {
|
|
1934
|
+
cid,
|
|
1935
|
+
fileName: holding?.fileName || cid,
|
|
1936
|
+
driveName: holding?.driveName || driveName,
|
|
1937
|
+
localPath: holding?.localPath || null,
|
|
1938
|
+
size,
|
|
1939
|
+
ownerAddress,
|
|
1940
|
+
},
|
|
1941
|
+
}
|
|
1942
|
+
} catch {
|
|
1943
|
+
return null
|
|
1821
1944
|
}
|
|
1822
|
-
await this.#syncDrive(drive)
|
|
1823
|
-
return drive
|
|
1824
1945
|
}
|
|
1825
1946
|
|
|
1826
1947
|
// --- 频道管理 ---
|
|
@@ -1834,6 +1955,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1834
1955
|
async createChannel(name, type = 'personal', options = {}) {
|
|
1835
1956
|
this.#ensureInitialized()
|
|
1836
1957
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
1958
|
+
const channelType = String(type || 'personal').trim() || 'personal'
|
|
1837
1959
|
|
|
1838
1960
|
if (!CHANNEL_NAME_REGEX.test(name)) {
|
|
1839
1961
|
throw new Error('频道名只能包含字母、数字、下划线和连字符')
|
|
@@ -1879,7 +2001,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1879
2001
|
discoveryKey: b4a.toString(discoveryKey, 'hex'),
|
|
1880
2002
|
coreKey: b4a.toString(core.key, 'hex'),
|
|
1881
2003
|
createdAt: new Date().toISOString(),
|
|
1882
|
-
type,
|
|
2004
|
+
type: channelType,
|
|
1883
2005
|
ownerAddress,
|
|
1884
2006
|
members: ownerAddress ? [ownerAddress] : [],
|
|
1885
2007
|
remoteCoreKeys: [],
|
|
@@ -2105,12 +2227,19 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2105
2227
|
listChannels(options = {}) {
|
|
2106
2228
|
this.#ensureInitialized()
|
|
2107
2229
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
2230
|
+
const type = String(options.type || '').trim()
|
|
2231
|
+
const excludeType = String(options.excludeType || '').trim()
|
|
2108
2232
|
|
|
2109
2233
|
return this.#channels
|
|
2110
2234
|
.filter(c => {
|
|
2111
2235
|
if (!ownerAddress) return true
|
|
2112
2236
|
return Array.isArray(c.members) && c.members.includes(ownerAddress)
|
|
2113
2237
|
})
|
|
2238
|
+
.filter(c => {
|
|
2239
|
+
if (type) return c.type === type
|
|
2240
|
+
if (excludeType) return c.type !== excludeType
|
|
2241
|
+
return true
|
|
2242
|
+
})
|
|
2114
2243
|
.map(c => ({
|
|
2115
2244
|
name: c.name,
|
|
2116
2245
|
coreKey: c.coreKey,
|
|
@@ -2172,7 +2301,11 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2172
2301
|
const start = Math.max(0, total - offset - limit)
|
|
2173
2302
|
const end = total - offset
|
|
2174
2303
|
|
|
2175
|
-
return unique
|
|
2304
|
+
return unique
|
|
2305
|
+
.slice(start, end)
|
|
2306
|
+
.map(({ _coreKey, _index, ...msg }) =>
|
|
2307
|
+
this.#normalizeChannelMessageForResponse(name, msg)
|
|
2308
|
+
)
|
|
2176
2309
|
}
|
|
2177
2310
|
|
|
2178
2311
|
/**
|
|
@@ -2304,6 +2437,44 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2304
2437
|
}
|
|
2305
2438
|
}
|
|
2306
2439
|
|
|
2440
|
+
#normalizeChannelMessageForResponse(channelName, message) {
|
|
2441
|
+
const attachment = message?.attachment
|
|
2442
|
+
if (!attachment?.cid || !attachment.fileName) {
|
|
2443
|
+
return message
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
const oldFileName = sanitizeFilename(String(attachment.fileName))
|
|
2447
|
+
const channelPrefix = `${CHAT_FILE_ROOT}/${channelName}/`
|
|
2448
|
+
const fileName = oldFileName.startsWith(channelPrefix)
|
|
2449
|
+
? oldFileName
|
|
2450
|
+
: `${channelPrefix}${getPathBaseName(oldFileName)}`
|
|
2451
|
+
const link = buildMostLink(attachment.cid, fileName)
|
|
2452
|
+
const content =
|
|
2453
|
+
typeof message.content === 'string' &&
|
|
2454
|
+
(message.content === attachment.link ||
|
|
2455
|
+
parseMostLink(message.content).cid === attachment.cid)
|
|
2456
|
+
? link
|
|
2457
|
+
: message.content
|
|
2458
|
+
|
|
2459
|
+
if (
|
|
2460
|
+
fileName === attachment.fileName &&
|
|
2461
|
+
link === attachment.link &&
|
|
2462
|
+
content === message.content
|
|
2463
|
+
) {
|
|
2464
|
+
return message
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
return {
|
|
2468
|
+
...message,
|
|
2469
|
+
content,
|
|
2470
|
+
attachment: {
|
|
2471
|
+
...attachment,
|
|
2472
|
+
fileName,
|
|
2473
|
+
link,
|
|
2474
|
+
},
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2307
2478
|
#getCidInfo(cid) {
|
|
2308
2479
|
return getCidInfo(cid)
|
|
2309
2480
|
}
|
|
@@ -2684,6 +2855,31 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2684
2855
|
return normalizeOwnerAddress(record.ownerAddress) === normalizedOwner
|
|
2685
2856
|
}
|
|
2686
2857
|
|
|
2858
|
+
#assertDisplayNameAvailable(fileName, options = {}) {
|
|
2859
|
+
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
2860
|
+
const safeFileName = sanitizeFilename(fileName)
|
|
2861
|
+
const folder = getDisplayPathFolder(safeFileName)
|
|
2862
|
+
const baseName = getPathBaseName(safeFileName)
|
|
2863
|
+
const conflict = this.#publishedFiles.find(file => {
|
|
2864
|
+
if (
|
|
2865
|
+
options.excludeCid &&
|
|
2866
|
+
file.cid === options.excludeCid &&
|
|
2867
|
+
this.#recordMatchesOwner(file, ownerAddress)
|
|
2868
|
+
) {
|
|
2869
|
+
return false
|
|
2870
|
+
}
|
|
2871
|
+
if (!this.#recordMatchesOwner(file, ownerAddress)) return false
|
|
2872
|
+
const existingFileName = sanitizeFilename(file.fileName)
|
|
2873
|
+
return (
|
|
2874
|
+
getDisplayPathFolder(existingFileName) === folder &&
|
|
2875
|
+
getPathBaseName(existingFileName) === baseName
|
|
2876
|
+
)
|
|
2877
|
+
})
|
|
2878
|
+
if (conflict) {
|
|
2879
|
+
throw new ConflictError(`已有同名文件: ${safeFileName}`)
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2687
2883
|
#hasPublishedReference(cid) {
|
|
2688
2884
|
return this.#publishedFiles.some(file => file.cid === cid)
|
|
2689
2885
|
}
|
|
@@ -2732,20 +2928,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2732
2928
|
}
|
|
2733
2929
|
}
|
|
2734
2930
|
|
|
2735
|
-
async #syncDrive(drive, timeout = DRIVE_SYNC_TIMEOUT) {
|
|
2736
|
-
try {
|
|
2737
|
-
const updated = await Promise.race([
|
|
2738
|
-
drive.update(),
|
|
2739
|
-
new Promise((_, reject) =>
|
|
2740
|
-
setTimeout(() => reject(new Error('Sync timeout')), timeout)
|
|
2741
|
-
),
|
|
2742
|
-
])
|
|
2743
|
-
return updated
|
|
2744
|
-
} catch {
|
|
2745
|
-
return false
|
|
2746
|
-
}
|
|
2747
|
-
}
|
|
2748
|
-
|
|
2749
2931
|
#getMetadataPath() {
|
|
2750
2932
|
return path.join(this.#options.dataPath, 'published-files.json')
|
|
2751
2933
|
}
|
|
@@ -2903,7 +3085,10 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2903
3085
|
if (entry && entry.type === 'message') {
|
|
2904
3086
|
this.emit('channel:message', {
|
|
2905
3087
|
channel: channelName,
|
|
2906
|
-
message:
|
|
3088
|
+
message: this.#normalizeChannelMessageForResponse(
|
|
3089
|
+
channelName,
|
|
3090
|
+
entry
|
|
3091
|
+
),
|
|
2907
3092
|
})
|
|
2908
3093
|
}
|
|
2909
3094
|
} catch (err) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createAvatar } from '@dicebear/core'
|
|
2
2
|
import { botttsNeutral } from '@dicebear/collection'
|
|
3
3
|
|
|
4
|
-
export function generateAvatar(address) {
|
|
5
|
-
if (
|
|
4
|
+
export function generateAvatar(address, avatar) {
|
|
5
|
+
if (avatar) return avatar
|
|
6
|
+
if (!address) return '/avatar.png'
|
|
6
7
|
return createAvatar(botttsNeutral, {
|
|
7
8
|
seed: 'most.box@' + address,
|
|
8
9
|
flip: true,
|