From 710dc7978c20c9dc0cc7bddc6e4e9d3205111f41 Mon Sep 17 00:00:00 2001 From: tusharshah21 Date: Thu, 26 Mar 2026 06:05:40 +0000 Subject: [PATCH 1/2] add upload pHash check --- automatedCopyrightFingerprinting.test.js | 202 ++++++++++++++++++ routes/video.js | 44 ++++ ...automatedCopyrightFingerprintingService.js | 104 +++++++++ 3 files changed, 350 insertions(+) create mode 100644 automatedCopyrightFingerprinting.test.js create mode 100644 src/services/automatedCopyrightFingerprintingService.js diff --git a/automatedCopyrightFingerprinting.test.js b/automatedCopyrightFingerprinting.test.js new file mode 100644 index 0000000..f14072c --- /dev/null +++ b/automatedCopyrightFingerprinting.test.js @@ -0,0 +1,202 @@ +const express = require('express'); +const request = require('supertest'); +const fs = require('fs').promises; +const path = require('path'); + +const AutomatedCopyrightFingerprintingService = require('./src/services/automatedCopyrightFingerprintingService'); + +function createDatabaseAdapter() { + const state = { + protectedVideoHashes: [], + videos: [], + videoFingerprints: [], + protectedHashIdCounter: 1, + fingerprintIdCounter: 1, + }; + + return { + run: async (sql, params = []) => { + if (sql.includes('CREATE TABLE IF NOT EXISTS protected_video_hashes')) { + return { changes: 0 }; + } + + if (sql.includes('CREATE TABLE IF NOT EXISTS video_fingerprints')) { + return { changes: 0 }; + } + + if (sql.includes('INSERT INTO protected_video_hashes')) { + const [phash, label, createdAt] = params; + state.protectedVideoHashes.push({ + id: state.protectedHashIdCounter, + phash, + label, + created_at: createdAt, + }); + state.protectedHashIdCounter += 1; + return { changes: 1 }; + } + + if (sql.includes('INSERT INTO videos')) { + const [id, title, description, creatorId, originalFilename, filePath, fileSize, visibility] = params; + state.videos.push({ + id, + title, + description, + creator_id: creatorId, + original_filename: originalFilename, + file_path: filePath, + file_size: fileSize, + status: 'uploaded', + message: null, + visibility, + }); + return { changes: 1 }; + } + + if (sql.includes('UPDATE videos') && sql.includes('SET status = ?, message = ?')) { + const [status, message, videoId] = params; + const video = state.videos.find((item) => item.id === videoId); + if (video) { + video.status = status; + video.message = message; + return { changes: 1 }; + } + return { changes: 0 }; + } + + if (sql.includes('INSERT INTO video_fingerprints')) { + const [videoId, phash, matchedProtectedHashId, status, createdAt] = params; + state.videoFingerprints.push({ + id: state.fingerprintIdCounter, + video_id: videoId, + phash, + matched_protected_hash_id: matchedProtectedHashId, + status, + created_at: createdAt, + }); + state.fingerprintIdCounter += 1; + return { changes: 1 }; + } + + throw new Error(`Unsupported SQL in test adapter: ${sql}`); + }, + get: async (sql, params = []) => { + if (sql.includes('FROM protected_video_hashes WHERE phash = ?')) { + const [phash] = params; + const match = state.protectedVideoHashes.find((item) => item.phash === phash); + if (!match) return undefined; + return { + id: match.id, + phash: match.phash, + label: match.label, + createdAt: match.created_at, + }; + } + + if (sql.includes('SELECT status, message FROM videos WHERE id = ?')) { + const [videoId] = params; + const video = state.videos.find((item) => item.id === videoId); + if (!video) return undefined; + return { status: video.status, message: video.message }; + } + + if (sql.includes('FROM video_fingerprints WHERE video_id = ?')) { + const [videoId] = params; + const row = state.videoFingerprints.find((item) => item.video_id === videoId); + if (!row) return undefined; + return { + status: row.status, + matchedProtectedHashId: row.matched_protected_hash_id, + }; + } + + throw new Error(`Unsupported SQL in test adapter: ${sql}`); + }, + }; +} + +describe('Automated copyright fingerprinting on upload', () => { + let app; + let database; + let videoWorker; + let fingerprintingService; + let createVideoRoutes; + + beforeEach(async () => { + jest.resetModules(); + createVideoRoutes = require('./routes/video'); + + database = createDatabaseAdapter(); + fingerprintingService = new AutomatedCopyrightFingerprintingService(database); + await fingerprintingService.ensureSchema(); + + videoWorker = { + addTranscodingJob: jest.fn().mockResolvedValue({ id: 'job-1' }), + getVideoStatus: jest.fn(), + addAdaptiveBitrateJob: jest.fn(), + getRecommendedQuality: jest.fn(), + getQueueStats: jest.fn(), + }; + + app = express(); + app.use('/api/videos', createVideoRoutes({}, database, videoWorker)); + }); + + afterEach(async () => { + await fs.rm(path.join(process.cwd(), 'uploads'), { recursive: true, force: true }); + }); + + it('flags upload for manual review when pHash matches a protected hash', async () => { + const fileBuffer = Buffer.from('copyright-protected-video-signature-001'); + const phash = fingerprintingService.buildPerceptualHashFromBuffer(fileBuffer); + + await database.run( + 'INSERT INTO protected_video_hashes (phash, label, created_at) VALUES (?, ?, ?)', + [phash, 'protected-master', new Date().toISOString()], + ); + + const res = await request(app) + .post('/api/videos/upload') + .field('title', 'Reupload attempt') + .field('creatorId', 'creator-1') + .attach('video', fileBuffer, { filename: 'protected.mp4', contentType: 'video/mp4' }); + + expect(res.statusCode).toBe(202); + expect(res.body.success).toBe(true); + expect(res.body.data.status).toBe('flagged_for_review'); + expect(videoWorker.addTranscodingJob).not.toHaveBeenCalled(); + + const video = await database.get('SELECT status, message FROM videos WHERE id = ?', [res.body.data.videoId]); + expect(video.status).toBe('flagged_for_review'); + expect(video.message).toContain('manual review'); + + const fingerprint = await database.get( + 'SELECT status, matched_protected_hash_id AS matchedProtectedHashId FROM video_fingerprints WHERE video_id = ?', + [res.body.data.videoId], + ); + expect(fingerprint.status).toBe('flagged_for_review'); + expect(fingerprint.matchedProtectedHashId).toBeTruthy(); + }); + + it('continues normal upload flow when no protected hash match exists', async () => { + const fileBuffer = Buffer.from('original-creator-video-content-xyz'); + + const res = await request(app) + .post('/api/videos/upload') + .field('title', 'Original upload') + .field('creatorId', 'creator-2') + .attach('video', fileBuffer, { filename: 'original.mp4', contentType: 'video/mp4' }); + + expect(res.statusCode).toBe(201); + expect(res.body.success).toBe(true); + expect(res.body.data.status).toBe('uploaded'); + expect(videoWorker.addTranscodingJob).toHaveBeenCalledTimes(1); + + const fingerprint = await database.get( + 'SELECT status, matched_protected_hash_id AS matchedProtectedHashId FROM video_fingerprints WHERE video_id = ?', + [res.body.data.videoId], + ); + expect(fingerprint.status).toBe('uploaded'); + expect(fingerprint.matchedProtectedHashId).toBeNull(); + }); +}); diff --git a/routes/video.js b/routes/video.js index e1e84c3..551f93d 100644 --- a/routes/video.js +++ b/routes/video.js @@ -3,6 +3,7 @@ const multer = require('multer'); const path = require('path'); const fs = require('fs').promises; const { v4: uuidv4 } = require('uuid'); +const AutomatedCopyrightFingerprintingService = require('../src/services/automatedCopyrightFingerprintingService'); const router = express.Router(); @@ -34,6 +35,8 @@ const upload = multer({ }); function createVideoRoutes(config, database, videoWorker) { + const fingerprintingService = new AutomatedCopyrightFingerprintingService(database); + router.post('/upload', upload.single('video'), async (req, res) => { try { if (!req.file) { @@ -53,6 +56,10 @@ function createVideoRoutes(config, database, videoWorker) { } const videoId = uuidv4(); + + await fingerprintingService.ensureSchema(); + const perceptualHash = await fingerprintingService.generatePerceptualHash(req.file.path); + const protectedHashMatch = await fingerprintingService.findProtectedMatch(perceptualHash); await database.run( `INSERT INTO videos @@ -70,6 +77,43 @@ function createVideoRoutes(config, database, videoWorker) { ] ); + if (protectedHashMatch) { + await database.run( + `UPDATE videos + SET status = ?, message = ?, updated_at = CURRENT_TIMESTAMP + WHERE id = ?`, + [ + 'flagged_for_review', + 'Potential copyright match detected. Upload flagged for manual review.', + videoId, + ], + ); + + await fingerprintingService.recordFingerprint({ + videoId, + phash: perceptualHash, + matchedProtectedHashId: protectedHashMatch.id, + status: 'flagged_for_review', + }); + + return res.status(202).json({ + success: true, + data: { + videoId, + title, + status: 'flagged_for_review', + message: 'Upload flagged for manual review due to copyright fingerprint match.', + }, + }); + } + + await fingerprintingService.recordFingerprint({ + videoId, + phash: perceptualHash, + matchedProtectedHashId: null, + status: 'uploaded', + }); + const transcodingJob = await videoWorker.addTranscodingJob( videoId, req.file.path, diff --git a/src/services/automatedCopyrightFingerprintingService.js b/src/services/automatedCopyrightFingerprintingService.js new file mode 100644 index 0000000..adedd7d --- /dev/null +++ b/src/services/automatedCopyrightFingerprintingService.js @@ -0,0 +1,104 @@ +const fs = require('fs').promises; + +class AutomatedCopyrightFingerprintingService { + constructor(database) { + this.database = database; + } + + async ensureSchema() { + await this.run( + `CREATE TABLE IF NOT EXISTS protected_video_hashes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + phash TEXT NOT NULL UNIQUE, + label TEXT, + created_at TEXT NOT NULL + )`, + [], + ); + + await this.run( + `CREATE TABLE IF NOT EXISTS video_fingerprints ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + video_id TEXT NOT NULL, + phash TEXT NOT NULL, + matched_protected_hash_id INTEGER, + status TEXT NOT NULL, + created_at TEXT NOT NULL, + FOREIGN KEY (matched_protected_hash_id) REFERENCES protected_video_hashes(id) + )`, + [], + ); + } + + async generatePerceptualHash(filePath) { + const buffer = await fs.readFile(filePath); + return this.buildPerceptualHashFromBuffer(buffer); + } + + buildPerceptualHashFromBuffer(buffer) { + const sampleCount = 64; + + if (!buffer || buffer.length === 0) { + return '0000000000000000'; + } + + const samples = []; + for (let i = 0; i < sampleCount; i += 1) { + const index = Math.floor((i * (buffer.length - 1)) / (sampleCount - 1)); + samples.push(buffer[index]); + } + + const average = samples.reduce((sum, value) => sum + value, 0) / sampleCount; + const bits = samples.map((value) => (value >= average ? 1 : 0)); + + let hex = ''; + for (let i = 0; i < bits.length; i += 4) { + const nibble = (bits[i] << 3) | (bits[i + 1] << 2) | (bits[i + 2] << 1) | bits[i + 3]; + hex += nibble.toString(16); + } + + return hex; + } + + async findProtectedMatch(phash) { + return this.get( + 'SELECT id, phash, label, created_at AS createdAt FROM protected_video_hashes WHERE phash = ? LIMIT 1', + [phash], + ); + } + + async recordFingerprint({ videoId, phash, matchedProtectedHashId, status }) { + await this.run( + `INSERT INTO video_fingerprints + (video_id, phash, matched_protected_hash_id, status, created_at) + VALUES (?, ?, ?, ?, ?)`, + [videoId, phash, matchedProtectedHashId || null, status, new Date().toISOString()], + ); + } + + async run(sql, params = []) { + if (typeof this.database.run === 'function') { + return this.database.run(sql, params); + } + + if (this.database && this.database.db && typeof this.database.db.prepare === 'function') { + return this.database.db.prepare(sql).run(...params); + } + + throw new Error('Unsupported database adapter: run() is not available'); + } + + async get(sql, params = []) { + if (typeof this.database.get === 'function') { + return this.database.get(sql, params); + } + + if (this.database && this.database.db && typeof this.database.db.prepare === 'function') { + return this.database.db.prepare(sql).get(...params); + } + + throw new Error('Unsupported database adapter: get() is not available'); + } +} + +module.exports = AutomatedCopyrightFingerprintingService; \ No newline at end of file From fe3ffff0b3ae35ddfa8621d103d4febdb23dcc18 Mon Sep 17 00:00:00 2001 From: tusharshah21 Date: Thu, 26 Mar 2026 06:13:07 +0000 Subject: [PATCH 2/2] sync lockfile --- package-lock.json | 419 ++++++---------------------------------------- 1 file changed, 53 insertions(+), 366 deletions(-) diff --git a/package-lock.json b/package-lock.json index d07ffd6..5152cf7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "dependencies": { "@stellar/stellar-sdk": "^14.6.1", + "amqplib": "^0.10.3", "aws-sdk": "^2.1500.0", "axios": "^1.6.2", "better-sqlite3": "^12.8.0", @@ -2160,6 +2161,19 @@ "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", "license": "MIT" }, + "node_modules/amqplib": { + "version": "0.10.9", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.9.tgz", + "integrity": "sha512-jwSftI4QjS3mizvnSnOrPGYiUnm1vI2OP1iXeOUz5pb74Ua0nbf6nPyyTzuiCLEE3fMpaJORXh2K/TQ08H5xGA==", + "license": "MIT", + "dependencies": { + "buffer-more-ints": "~1.0.0", + "url-parse": "~1.5.10" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -2814,6 +2828,12 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/buffer-more-ints": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==", + "license": "MIT" + }, "node_modules/buffer/node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -4902,252 +4922,6 @@ "node": ">= 0.10" } }, - "node_modules/ipfs-car": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/ipfs-car/-/ipfs-car-0.7.0.tgz", - "integrity": "sha512-9ser6WWZ1ZMTCGbcVkRXUzOrpQ4SIiLfzIEnk+3LQsXbV09yeZg3ijhRuEXozEIYE68Go9JmOFshamsK9iKlNQ==", - "license": "(Apache-2.0 AND MIT)", - "dependencies": { - "@ipld/car": "^3.2.3", - "@web-std/blob": "^3.0.1", - "bl": "^5.0.0", - "blockstore-core": "^1.0.2", - "browser-readablestream-to-it": "^1.0.2", - "idb-keyval": "^6.0.3", - "interface-blockstore": "^2.0.2", - "ipfs-core-types": "^0.8.3", - "ipfs-core-utils": "^0.12.1", - "ipfs-unixfs-exporter": "^7.0.4", - "ipfs-unixfs-importer": "^9.0.4", - "ipfs-utils": "^9.0.2", - "it-all": "^1.0.5", - "it-last": "^1.0.5", - "it-pipe": "^1.1.0", - "meow": "^9.0.0", - "move-file": "^2.1.0", - "multiformats": "^9.6.3", - "stream-to-it": "^0.2.3", - "streaming-iterables": "^6.0.0", - "uint8arrays": "^3.0.0" - }, - "bin": { - "🚘": "dist/cjs/cli/cli.js", - "ipfs-car": "dist/cjs/cli/cli.js" - } - }, - "node_modules/ipfs-car/node_modules/any-signal": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/any-signal/-/any-signal-2.1.2.tgz", - "integrity": "sha512-B+rDnWasMi/eWcajPcCWSlYc7muXOrcYrqgyzcdKisl2H/WTlQ0gip1KyQfr0ZlxJdsuWCj/LWwQm7fhyhRfIQ==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "native-abort-controller": "^1.0.3" - } - }, - "node_modules/ipfs-car/node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "license": "MIT", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/ipfs-car/node_modules/blob-to-it": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/blob-to-it/-/blob-to-it-1.0.4.tgz", - "integrity": "sha512-iCmk0W4NdbrWgRRuxOriU8aM5ijeVLI61Zulsmg/lUHNr7pYjoj+U77opLefNagevtrrbMt3JQ5Qip7ar178kA==", - "license": "ISC", - "dependencies": { - "browser-readablestream-to-it": "^1.0.3" - } - }, - "node_modules/ipfs-car/node_modules/browser-readablestream-to-it": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/browser-readablestream-to-it/-/browser-readablestream-to-it-1.0.3.tgz", - "integrity": "sha512-+12sHB+Br8HIh6VAMVEG5r3UXCyESIgDW7kzk3BjIXa43DVqVwL7GC5TW3jeh+72dtcH99pPVpw0X8i0jt+/kw==", - "license": "ISC" - }, - "node_modules/ipfs-car/node_modules/interface-datastore": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-6.1.1.tgz", - "integrity": "sha512-AmCS+9CT34pp2u0QQVXjKztkuq3y5T+BIciuiHDDtDZucZD8VudosnSdUyXJV6IsRkN5jc4RFDhCk1O6Q3Gxjg==", - "license": "MIT", - "dependencies": { - "interface-store": "^2.0.2", - "nanoid": "^3.0.2", - "uint8arrays": "^3.0.0" - } - }, - "node_modules/ipfs-car/node_modules/interface-store": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-2.0.2.tgz", - "integrity": "sha512-rScRlhDcz6k199EkHqT8NpM87ebN89ICOzILoBHgaG36/WX50N32BnU/kpZgCGPLhARRAWUUX5/cyaIjt7Kipg==", - "license": "(Apache-2.0 OR MIT)" - }, - "node_modules/ipfs-car/node_modules/ipfs-core-types": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/ipfs-core-types/-/ipfs-core-types-0.8.4.tgz", - "integrity": "sha512-sbRZA1QX3xJ6ywTiVQZMOxhlhp4osAZX2SXx3azOLxAtxmGWDMkHYt722VV4nZ2GyJy8qyk5GHQIZ0uvQnpaTg==", - "deprecated": "js-IPFS has been deprecated in favour of Helia - please see https://github.com/ipfs/js-ipfs/issues/4336 for details", - "license": "(Apache-2.0 OR MIT)", - "dependencies": { - "interface-datastore": "^6.0.2", - "multiaddr": "^10.0.0", - "multiformats": "^9.4.13" - } - }, - "node_modules/ipfs-car/node_modules/ipfs-core-utils": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/ipfs-core-utils/-/ipfs-core-utils-0.12.2.tgz", - "integrity": "sha512-RfxP3rPhXuqKIUmTAUhmee6fmaV3A7LMnjOUikRKpSyqESz/DR7aGK7tbttMxkZdkSEr0rFXlqbyb0vVwmn0wQ==", - "deprecated": "js-IPFS has been deprecated in favour of Helia - please see https://github.com/ipfs/js-ipfs/issues/4336 for details", - "license": "MIT", - "dependencies": { - "any-signal": "^2.1.2", - "blob-to-it": "^1.0.1", - "browser-readablestream-to-it": "^1.0.1", - "debug": "^4.1.1", - "err-code": "^3.0.1", - "ipfs-core-types": "^0.8.4", - "ipfs-unixfs": "^6.0.3", - "ipfs-utils": "^9.0.2", - "it-all": "^1.0.4", - "it-map": "^1.0.4", - "it-peekable": "^1.0.2", - "it-to-stream": "^1.0.0", - "merge-options": "^3.0.4", - "multiaddr": "^10.0.0", - "multiaddr-to-uri": "^8.0.0", - "multiformats": "^9.4.13", - "nanoid": "^3.1.23", - "parse-duration": "^1.0.0", - "timeout-abort-controller": "^1.1.1", - "uint8arrays": "^3.0.0" - } - }, - "node_modules/ipfs-car/node_modules/ipfs-unixfs": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-6.0.9.tgz", - "integrity": "sha512-0DQ7p0/9dRB6XCb0mVCTli33GzIzSVx5udpJuVM47tGcD+W+Bl4LsnoLswd3ggNnNEakMv1FdoFITiEnchXDqQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "err-code": "^3.0.1", - "protobufjs": "^6.10.2" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/ipfs-car/node_modules/it-all": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/it-all/-/it-all-1.0.6.tgz", - "integrity": "sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==", - "license": "ISC" - }, - "node_modules/ipfs-car/node_modules/it-last": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/it-last/-/it-last-1.0.6.tgz", - "integrity": "sha512-aFGeibeiX/lM4bX3JY0OkVCFkAw8+n9lkukkLNivbJRvNz8lI3YXv5xcqhFUV2lDJiraEK3OXRDbGuevnnR67Q==", - "license": "ISC" - }, - "node_modules/ipfs-car/node_modules/it-map": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/it-map/-/it-map-1.0.6.tgz", - "integrity": "sha512-XT4/RM6UHIFG9IobGlQPFQUrlEKkU4eBUFG3qhWhfAdh1JfF2x11ShCrKCdmZ0OiZppPfoLuzcfA4cey6q3UAQ==", - "license": "ISC" - }, - "node_modules/ipfs-car/node_modules/it-peekable": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/it-peekable/-/it-peekable-1.0.3.tgz", - "integrity": "sha512-5+8zemFS+wSfIkSZyf0Zh5kNN+iGyccN02914BY4w/Dj+uoFEoPSvj5vaWn8pNZJNSxzjW0zHRxC3LUb2KWJTQ==", - "license": "ISC" - }, - "node_modules/ipfs-car/node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "license": "Apache-2.0" - }, - "node_modules/ipfs-car/node_modules/multiformats": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", - "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", - "license": "(Apache-2.0 AND MIT)" - }, - "node_modules/ipfs-car/node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/ipfs-car/node_modules/protobufjs": { - "version": "6.11.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", - "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, - "node_modules/ipfs-car/node_modules/retimer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/retimer/-/retimer-2.0.0.tgz", - "integrity": "sha512-KLXY85WkEq2V2bKex/LOO1ViXVn2KGYe4PYysAdYdjmraYIUsVkXu8O4am+8+5UbaaGl1qho4aqAAPHNQ4GSbg==", - "license": "MIT" - }, - "node_modules/ipfs-car/node_modules/timeout-abort-controller": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/timeout-abort-controller/-/timeout-abort-controller-1.1.1.tgz", - "integrity": "sha512-BsF9i3NAJag6T0ZEjki9j654zoafI2X6ayuNd6Tp8+Ul6Tr5s4jo973qFeiWrRSweqvskC+AHDKUmIW4b7pdhQ==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "retimer": "^2.0.0" - } - }, - "node_modules/ipfs-car/node_modules/uint8arrays": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", - "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", - "license": "MIT", - "dependencies": { - "multiformats": "^9.4.2" - } - }, "node_modules/ipfs-core-types": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/ipfs-core-types/-/ipfs-core-types-0.14.1.tgz", @@ -5407,126 +5181,6 @@ "multiformats": "^9.4.2" } }, - "node_modules/ipfs-unixfs-importer": { - "version": "9.0.10", - "resolved": "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-9.0.10.tgz", - "integrity": "sha512-W+tQTVcSmXtFh7FWYWwPBGXJ1xDgREbIyI1E5JzDcimZLIyT5gGMfxR3oKPxxWj+GKMpP5ilvMQrbsPzWcm3Fw==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@ipld/dag-pb": "^2.0.2", - "@multiformats/murmur3": "^1.0.3", - "bl": "^5.0.0", - "err-code": "^3.0.1", - "hamt-sharding": "^2.0.0", - "interface-blockstore": "^2.0.3", - "ipfs-unixfs": "^6.0.0", - "it-all": "^1.0.5", - "it-batch": "^1.0.8", - "it-first": "^1.0.6", - "it-parallel-batch": "^1.0.9", - "merge-options": "^3.0.4", - "multiformats": "^9.4.2", - "rabin-wasm": "^0.1.4", - "uint8arrays": "^3.0.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/ipfs-unixfs-importer/node_modules/@ipld/dag-pb": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-2.1.18.tgz", - "integrity": "sha512-ZBnf2fuX9y3KccADURG5vb9FaOeMjFkCrNysB0PtftME/4iCTjxfaLoNq/IAh5fTqUOMXvryN6Jyka4ZGuMLIg==", - "license": "(Apache-2.0 AND MIT)", - "dependencies": { - "multiformats": "^9.5.4" - } - }, - "node_modules/ipfs-unixfs-importer/node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "license": "MIT", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/ipfs-unixfs-importer/node_modules/ipfs-unixfs": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-6.0.9.tgz", - "integrity": "sha512-0DQ7p0/9dRB6XCb0mVCTli33GzIzSVx5udpJuVM47tGcD+W+Bl4LsnoLswd3ggNnNEakMv1FdoFITiEnchXDqQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "err-code": "^3.0.1", - "protobufjs": "^6.10.2" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/ipfs-unixfs-importer/node_modules/it-all": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/it-all/-/it-all-1.0.6.tgz", - "integrity": "sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==", - "license": "ISC" - }, - "node_modules/ipfs-unixfs-importer/node_modules/it-first": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/it-first/-/it-first-1.0.7.tgz", - "integrity": "sha512-nvJKZoBpZD/6Rtde6FXqwDqDZGF1sCADmr2Zoc0hZsIvnE449gRFnGctxDf09Bzc/FWnHXAdaHVIetY6lrE0/g==", - "license": "ISC" - }, - "node_modules/ipfs-unixfs-importer/node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "license": "Apache-2.0" - }, - "node_modules/ipfs-unixfs-importer/node_modules/multiformats": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", - "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", - "license": "(Apache-2.0 AND MIT)" - }, - "node_modules/ipfs-unixfs-importer/node_modules/protobufjs": { - "version": "6.11.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", - "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, - "node_modules/ipfs-unixfs-importer/node_modules/uint8arrays": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", - "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", - "license": "MIT", - "dependencies": { - "multiformats": "^9.4.2" - } - }, "node_modules/ipfs-utils": { "version": "9.0.14", "resolved": "https://registry.npmjs.org/ipfs-utils/-/ipfs-utils-9.0.14.tgz", @@ -8317,6 +7971,12 @@ "node": ">=0.4.x" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, "node_modules/quick-lru": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", @@ -8623,6 +8283,12 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -9731,6 +9397,16 @@ "querystring": "0.2.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/utf8-codec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/utf8-codec/-/utf8-codec-1.0.0.tgz", @@ -9945,6 +9621,17 @@ "native-abort-controller": "^1.0.3" } }, + "node_modules/web3.storage/node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/web3.storage/node_modules/blob-to-it": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/blob-to-it/-/blob-to-it-1.0.4.tgz",