-
Notifications
You must be signed in to change notification settings - Fork 479
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
1,127 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
{ | ||
"name": "@nostrwatch/nocap-route66", | ||
"type": "module", | ||
"version": "0.0.1", | ||
"description": "A library for transforming NOCAP output into Nostr events of kind 30166", | ||
"entry": "src/index.ts", | ||
"exports": { | ||
".": { | ||
"import": "./dist/index.js", | ||
"require": "./dist/node/index.js", | ||
"types": "./dist/index.d.ts" | ||
}, | ||
"./web": { | ||
"import": "./dist/index.js", | ||
"types": "./dist/index.d.ts" | ||
}, | ||
"./umd": { | ||
"import": "./dist/umd/index.js", | ||
"types": "./dist/index.d.ts" | ||
} | ||
}, | ||
"scripts": { | ||
"build": "tsc && tsc-alias && webpack --config webpack.config.js", | ||
"build:browser": "webpack --config webpack.config.js --env target=web", | ||
"build:node": "webpack --config webpack.config.js --env target=node", | ||
"test": "vitest", | ||
"lint": "eslint . --ext .ts", | ||
"clean": "rimraf dist" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/yourusername/nocapd-route66.git" | ||
}, | ||
"keywords": [ | ||
"nostr", | ||
"route66", | ||
"nocap", | ||
"event", | ||
"library" | ||
], | ||
"author": "Your Name <[email protected]>", | ||
"license": "MIT", | ||
"dependencies": { | ||
"@nostrwatch/logger": "*", | ||
"@nostrwatch/nocap": "workspace:^", | ||
"nostr-geotags": "0.7.1", | ||
"nostr-tools": "2.7.2" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^20.0.0", | ||
"eslint": "^8.0.0", | ||
"eslint-config-prettier": "^9.0.0", | ||
"eslint-plugin-prettier": "^5.0.0", | ||
"prettier": "^3.4.2", | ||
"rimraf": "^5.0.0", | ||
"ts-loader": "^9.0.0", | ||
"tsc-alias": "^1.8.10", | ||
"typescript": "^5.0.0", | ||
"vitest": "^0.34.1", | ||
"webpack": "^5.0.0", | ||
"webpack-cli": "^5.0.0" | ||
}, | ||
"files": [ | ||
"dist/**/*" | ||
], | ||
"engines": { | ||
"node": ">=14.0.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
// Transform.test.ts | ||
import { describe, it, expect, beforeEach, vi } from 'vitest'; | ||
import { Transform } from './Transform.js'; | ||
|
||
// Mock dependencies if necessary | ||
vi.mock('@nostrwatch/logger', () => ({ | ||
Logger: class { | ||
err = vi.fn(); | ||
info = vi.fn(); | ||
warn = vi.fn(); | ||
}, | ||
default: class { | ||
err = vi.fn(); | ||
info = vi.fn(); | ||
warn = vi.fn(); | ||
} | ||
})); | ||
|
||
vi.mock('nostr-tools', () => ({ | ||
getEventHash: vi.fn().mockReturnValue('mocked_event_hash'), | ||
})); | ||
|
||
describe('Transform Class', () => { | ||
const pubkey = 'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890'; | ||
let baseInstance: Transform; | ||
|
||
// Create a concrete subclass for testing | ||
class TestBase extends Transform { | ||
constructor(kind: number, pubkey: string) { | ||
super(kind, pubkey); | ||
} | ||
|
||
generateTags(check: any): string[][] { | ||
return [['tag1', 'value1'], ['tag2', 'value2']]; | ||
} | ||
} | ||
|
||
beforeEach(() => { | ||
baseInstance = new TestBase(9999, pubkey); | ||
}); | ||
|
||
describe('Initialization', () => { | ||
it('should create an instance of Transform', () => { | ||
expect(baseInstance).toBeInstanceOf(Transform); | ||
expect(baseInstance.kind).toBe(9999); | ||
expect(baseInstance.pubkey).toBe(pubkey); | ||
expect(baseInstance.logger).toBeDefined(); | ||
}); | ||
|
||
it('should throw an error if kind is undefined', () => { | ||
expect(() => new TestBase(undefined as unknown as number, pubkey)).toThrow( | ||
'Kind must be defined' | ||
); | ||
}); | ||
|
||
it('should throw an error if pubkey is undefined', () => { | ||
expect(() => new TestBase(9999, undefined as unknown as string)).toThrow( | ||
'DAEMON_PUBKEY must be defined' | ||
); | ||
}); | ||
}); | ||
|
||
describe('tpl()', () => { | ||
it('should return a template with default values', () => { | ||
const tpl = baseInstance.tpl(); | ||
expect(tpl).toEqual({ | ||
id: null, | ||
pubkey, | ||
kind: 9999, | ||
created_at: expect.any(Number), | ||
tags: [], | ||
content: '', | ||
}); | ||
}); | ||
|
||
it('should include data if provided', () => { | ||
const data = { | ||
checked_at: 1620000000, | ||
content: 'Test Content', | ||
tags: [['test', 'tag']], | ||
}; | ||
const tpl = baseInstance.tpl(data); | ||
expect(tpl).toEqual({ | ||
id: null, | ||
pubkey, | ||
kind: 9999, | ||
created_at: 1620000000, | ||
tags: [['test', 'tag']], | ||
content: 'Test Content', | ||
}); | ||
}); | ||
}); | ||
|
||
describe('generateEvent()', () => { | ||
it('should generate an event with correct properties', () => { | ||
const data = { | ||
url: 'wss://example.com', | ||
info: { data: { key: 'value' } }, | ||
}; | ||
const event = baseInstance.generateEvent(data); | ||
expect(event.id).toBe('mocked_event_hash'); | ||
expect(event.pubkey).toBe(pubkey); | ||
expect(event.kind).toBe(9999); | ||
expect(event.created_at).toBeGreaterThan(0); | ||
expect(event.tags).toEqual([['tag1', 'value1'], ['tag2', 'value2']]); | ||
expect(event.content).toBe(JSON.stringify({ key: 'value' })); | ||
}); | ||
|
||
it('should handle errors in content serialization', () => { | ||
const data = { | ||
url: "wss://someurl.xyz", | ||
info: { data: undefined }, | ||
}; | ||
// Simulate error in JSON.stringify | ||
const originalStringify = JSON.stringify; | ||
JSON.stringify = () => { | ||
throw new Error('Serialization error'); | ||
}; | ||
|
||
const event = baseInstance.generateEvent(data); | ||
expect(event.content).toBe('{}'); | ||
// Restore JSON.stringify | ||
JSON.stringify = originalStringify; | ||
}); | ||
}); | ||
|
||
describe('dedupLabels()', () => { | ||
it('should deduplicate labels correctly', () => { | ||
const tags = [ | ||
['L', 'label1'], | ||
['L', 'label2'], | ||
['l', 'value1', 'label1'], | ||
['l', 'value2', 'label1'], | ||
['l', 'value1', 'label1'], // Duplicate | ||
['L', 'label2'], | ||
['l', 'value3', 'label2'], | ||
]; | ||
const dedupedTags = baseInstance.dedupLabels(tags); | ||
console.log('dedupedTags', dedupedTags); | ||
expect(dedupedTags).toEqual(expect.arrayContaining([ | ||
['L', 'label1'], | ||
['L', 'label2'], | ||
['l', 'value1', 'label1'], | ||
['l', 'value2', 'label1'], | ||
['l', 'value3', 'label2'], | ||
])); | ||
}); | ||
}); | ||
|
||
|
||
describe('removeLabels()', () => { | ||
it('should remove labels correctly', () => { | ||
const tags = [ | ||
['L', 'label1'], | ||
['l', 'value1', 'label1'], | ||
['t', 'tag1'], | ||
['l', 'value2', 'label1'], | ||
['L', 'label2'], | ||
['p', 'pubkey'], | ||
]; | ||
const filteredTags = baseInstance.removeLabels(tags); | ||
expect(filteredTags).toEqual([ | ||
['t', 'tag1'], | ||
['p', 'pubkey'], | ||
]); | ||
}); | ||
}); | ||
|
||
describe('json()', () => { | ||
it('should return the current event', () => { | ||
const data = { | ||
url: 'wss://example.com', | ||
}; | ||
baseInstance.generateEvent(data); | ||
const event = baseInstance.json(); | ||
expect(event).toBeDefined(); | ||
expect(event.id).toBe('mocked_event_hash'); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// KindBase.ts | ||
import { | ||
getEventHash, | ||
} from 'nostr-tools'; | ||
import Logger from '@nostrwatch/logger'; | ||
import { type IResult } from '@nostrwatch/nocap'; | ||
|
||
|
||
export interface ITransform { | ||
generateEvent(data: IResult): any; | ||
generateTags(data: IResult): string[][]; | ||
dedupLabels(tags: string[][]): string[][]; | ||
removeLabels(tags: string[][]): string[][]; | ||
} | ||
|
||
export abstract class Transform { | ||
logger: Logger; | ||
event: any; | ||
|
||
constructor(public kind: number, public pubkey: string) { | ||
if (!kind) { | ||
throw new Error('Kind must be defined'); | ||
} | ||
if (!pubkey) { | ||
throw new Error('DAEMON_PUBKEY must be defined'); | ||
} | ||
this.logger = new Logger(`@nostrwatch/publisher/event: ${kind}`); | ||
} | ||
|
||
tpl(data?: any) { | ||
const id = null; | ||
const pubkey = this.pubkey; | ||
const kind = this.kind; | ||
const created_at = data?.checked_at | ||
? data.checked_at | ||
: Math.round(Date.now() / 1000); | ||
const content = data?.content ?? ''; | ||
const tags = data?.tags ?? []; | ||
|
||
return { id, pubkey, kind, created_at, tags, content }; | ||
} | ||
|
||
json() { | ||
return this.event; | ||
} | ||
|
||
dedupLabels(tags: string[][]) { | ||
const labels = new Set(); | ||
let deduped = tags.filter((tag) => { | ||
if (tag[0] === 'L') { | ||
if (labels.has(tag[1])) { | ||
return false; | ||
} | ||
labels.add(tag[1]); | ||
} | ||
return true; | ||
}); | ||
|
||
const lTags = new Map(); | ||
deduped.forEach((tag) => { | ||
if (tag[0] === 'l') { | ||
const label = tag[2]; | ||
const value = tag[1]; | ||
if (!lTags.has(label)) { | ||
lTags.set(label, new Set()); | ||
} | ||
const labelMap = lTags.get(label) | ||
if (labelMap.has(value)) { | ||
return; | ||
} | ||
labelMap.add(value); | ||
lTags.set(label, labelMap); | ||
} | ||
}); | ||
|
||
deduped = deduped.filter( tag => tag[0] !== 'l'); | ||
|
||
deduped.push( | ||
...Array.from(lTags.entries()).flatMap(([key, values]) => { | ||
return Array.from(values).map((value) => ['l', value, key]); | ||
}) | ||
); | ||
|
||
return deduped; | ||
} | ||
|
||
removeLabels(tags: string[][]) { | ||
const labels = new Set(); | ||
return tags.filter((tag) => { | ||
if (tag[0] === 'L' || tag[0] === 'l') { | ||
return false | ||
} | ||
return true; | ||
}); | ||
} | ||
|
||
|
||
generateEvent(data: IResult) { | ||
this.event = this._generateEvent(data); | ||
this.event.id = getEventHash(this.event); | ||
return this.event; | ||
} | ||
|
||
protected _generateEvent(data: IResult) { | ||
let content = '{}'; | ||
const tags = this.generateTags(data); | ||
const nip11 = data?.info?.data; | ||
|
||
if (nip11) { | ||
try { | ||
content = JSON.stringify(nip11); | ||
} catch (e) { | ||
this.logger.err(`generateEvent(): Error: ${e}`); | ||
this.logger.info(nip11); | ||
} | ||
} | ||
|
||
return { | ||
...this.tpl(), | ||
content, | ||
tags, | ||
}; | ||
} | ||
|
||
abstract generateTags(check: IResult): string[][]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { Transform } from './Transform.js'; | ||
export { Kind30166 } from './kinds/index.js'; |
Oops, something went wrong.