diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..6f650c1d9 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,100 @@ +# Repository Instructions for Copilot + +## NIPs + +- https://github.com/nostr-protocol/nips/blob/master/01.md +- https://github.com/nostr-protocol/nips/blob/master/02.md +- https://github.com/nostr-protocol/nips/blob/master/03.md +- https://github.com/nostr-protocol/nips/blob/master/04.md +- https://github.com/nostr-protocol/nips/blob/master/05.md +- https://github.com/nostr-protocol/nips/blob/master/06.md +- https://github.com/nostr-protocol/nips/blob/master/07.md +- https://github.com/nostr-protocol/nips/blob/master/08.md +- https://github.com/nostr-protocol/nips/blob/master/09.md +- https://github.com/nostr-protocol/nips/blob/master/10.md +- https://github.com/nostr-protocol/nips/blob/master/11.md +- https://github.com/nostr-protocol/nips/blob/master/12.md +- https://github.com/nostr-protocol/nips/blob/master/13.md +- https://github.com/nostr-protocol/nips/blob/master/14.md +- https://github.com/nostr-protocol/nips/blob/master/15.md +- https://github.com/nostr-protocol/nips/blob/master/16.md +- https://github.com/nostr-protocol/nips/blob/master/17.md +- https://github.com/nostr-protocol/nips/blob/master/18.md +- https://github.com/nostr-protocol/nips/blob/master/19.md +- https://github.com/nostr-protocol/nips/blob/master/20.md +- https://github.com/nostr-protocol/nips/blob/master/21.md +- https://github.com/nostr-protocol/nips/blob/master/22.md +- https://github.com/nostr-protocol/nips/blob/master/23.md +- https://github.com/nostr-protocol/nips/blob/master/24.md +- https://github.com/nostr-protocol/nips/blob/master/25.md +- https://github.com/nostr-protocol/nips/blob/master/26.md +- https://github.com/nostr-protocol/nips/blob/master/27.md +- https://github.com/nostr-protocol/nips/blob/master/28.md +- https://github.com/nostr-protocol/nips/blob/master/29.md +- https://github.com/nostr-protocol/nips/blob/master/30.md +- https://github.com/nostr-protocol/nips/blob/master/31.md +- https://github.com/nostr-protocol/nips/blob/master/32.md +- https://github.com/nostr-protocol/nips/blob/master/33.md +- https://github.com/nostr-protocol/nips/blob/master/34.md +- https://github.com/nostr-protocol/nips/blob/master/35.md +- https://github.com/nostr-protocol/nips/blob/master/36.md +- https://github.com/nostr-protocol/nips/blob/master/37.md +- https://github.com/nostr-protocol/nips/blob/master/38.md +- https://github.com/nostr-protocol/nips/blob/master/39.md +- https://github.com/nostr-protocol/nips/blob/master/40.md +- https://github.com/nostr-protocol/nips/blob/master/42.md +- https://github.com/nostr-protocol/nips/blob/master/44.md +- https://github.com/nostr-protocol/nips/blob/master/45.md +- https://github.com/nostr-protocol/nips/blob/master/46.md +- https://github.com/nostr-protocol/nips/blob/master/47.md +- https://github.com/nostr-protocol/nips/blob/master/48.md +- https://github.com/nostr-protocol/nips/blob/master/49.md +- https://github.com/nostr-protocol/nips/blob/master/50.md +- https://github.com/nostr-protocol/nips/blob/master/51.md +- https://github.com/nostr-protocol/nips/blob/master/52.md +- https://github.com/nostr-protocol/nips/blob/master/53.md +- https://github.com/nostr-protocol/nips/blob/master/54.md +- https://github.com/nostr-protocol/nips/blob/master/55.md +- https://github.com/nostr-protocol/nips/blob/master/56.md +- https://github.com/nostr-protocol/nips/blob/master/57.md +- https://github.com/nostr-protocol/nips/blob/master/58.md +- https://github.com/nostr-protocol/nips/blob/master/59.md +- https://github.com/nostr-protocol/nips/blob/master/60.md +- https://github.com/nostr-protocol/nips/blob/master/61.md +- https://github.com/nostr-protocol/nips/blob/master/62.md +- https://github.com/nostr-protocol/nips/blob/master/64.md +- https://github.com/nostr-protocol/nips/blob/master/65.md +- https://github.com/nostr-protocol/nips/blob/master/66.md +- https://github.com/nostr-protocol/nips/blob/master/68.md +- https://github.com/nostr-protocol/nips/blob/master/69.md +- https://github.com/nostr-protocol/nips/blob/master/70.md +- https://github.com/nostr-protocol/nips/blob/master/71.md +- https://github.com/nostr-protocol/nips/blob/master/72.md +- https://github.com/nostr-protocol/nips/blob/master/73.md +- https://github.com/nostr-protocol/nips/blob/master/75.md +- https://github.com/nostr-protocol/nips/blob/master/77.md +- https://github.com/nostr-protocol/nips/blob/master/78.md +- https://github.com/nostr-protocol/nips/blob/master/7D.md +- https://github.com/nostr-protocol/nips/blob/master/84.md +- https://github.com/nostr-protocol/nips/blob/master/86.md +- https://github.com/nostr-protocol/nips/blob/master/87.md +- https://github.com/nostr-protocol/nips/blob/master/88.md +- https://github.com/nostr-protocol/nips/blob/master/89.md +- https://github.com/nostr-protocol/nips/blob/master/90.md +- https://github.com/nostr-protocol/nips/blob/master/92.md +- https://github.com/nostr-protocol/nips/blob/master/94.md +- https://github.com/nostr-protocol/nips/blob/master/96.md +- https://github.com/nostr-protocol/nips/blob/master/98.md +- https://github.com/nostr-protocol/nips/blob/master/99.md +- https://github.com/nostr-protocol/nips/blob/master/A0.md +- https://github.com/nostr-protocol/nips/blob/master/B0.md +- https://github.com/nostr-protocol/nips/blob/master/B7.md +- https://github.com/nostr-protocol/nips/blob/master/C0.md +- https://github.com/nostr-protocol/nips/blob/master/C7.md + +## Guidelines + +- All changes must include unit tests and update relevant docs. +- Use clear names and remove unused imports. +- Prefer readable, maintainable code over clever shortcuts. +- Run `mvn -q verify` locally before pushing. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d002dfda4..18687d908 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,8 +5,10 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 10 + target-branch: "develop" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" open-pull-requests-limit: 10 + target-branch: "develop" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..62b0ede2f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,20 @@ +## Why now? + +Related issue: #____ + +## What changed? + + +## BREAKING + + + +## Review focus + + +## Checklist +- [ ] Scope ≤ 300 lines (or split/stack) +- [ ] Title is **verb + object** (e.g., “Refactor auth middleware to async”) +- [ ] Description links the issue and answers “why now?” +- [ ] **BREAKING** flagged if needed +- [ ] Tests/docs updated (if relevant) diff --git a/.github/workflows/assign-copilot-review.yml b/.github/workflows/assign-copilot-review.yml new file mode 100644 index 000000000..e17276e4a --- /dev/null +++ b/.github/workflows/assign-copilot-review.yml @@ -0,0 +1,24 @@ +name: Assign Copilot Reviewer + +on: + pull_request_target: + branches: [develop] + types: [opened, reopened, ready_for_review] + +permissions: + pull-requests: write + +jobs: + assign: + runs-on: ubuntu-latest + steps: + - name: Request review from GitHub Copilot + uses: actions/github-script@v7 + with: + script: | + await github.rest.pulls.requestReviewers({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + reviewers: ['github-copilot'] + }); diff --git a/.github/workflows/pr-quality-gate.yml b/.github/workflows/pr-quality-gate.yml new file mode 100644 index 000000000..a13329f6a --- /dev/null +++ b/.github/workflows/pr-quality-gate.yml @@ -0,0 +1,138 @@ +name: PR Quality Gate + +on: + pull_request: + branches: [develop] + types: [opened, edited, synchronize, ready_for_review] + +permissions: + issues: write + pull-requests: write + +jobs: + review: + if: ${{ github.actor != 'dependabot[bot]' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Verify Copilot instructions + run: test -s .github/copilot-instructions.md + - name: Run expertise standard checks + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + const owner = context.repo.owner; + const repo = context.repo.repo; + // Fetch fresh PR data + files + const { data: prData } = await github.rest.pulls.get({ + owner, repo, pull_number: pr.number + }); + // Sum changed lines (additions + deletions) + const totalChanged = prData.additions + prData.deletions; + // Pull files for basic heuristics (e.g., tests touched?) + const files = await github.paginate( + github.rest.pulls.listFiles, { owner, repo, pull_number: pr.number } + ); + const commits = await github.paginate( + github.rest.pulls.listCommits, { owner, repo, pull_number: pr.number } + ); + const extsCode = ['.js','.ts','.tsx','.jsx','.py','.rb','.go','.rs','.java','.kt','.cs','.php','.c','.cc','.cpp','.m','.mm','.swift','.scala','.sh','.yml','.yaml','.json','.toml']; + const extsTests = ['.spec.','.test.','/tests/','/__tests__/']; + const codeTouched = files.some(f => + extsCode.some(ext => f.filename.includes(ext))); + const testsTouched = files.some(f => + extsTests.some(tok => f.filename.includes(tok))); + // 1) Scope ≤ 300 lines (from GitHub blog checklist) + const scopeOK = totalChanged <= 300; + // 2) Title and commits follow type: description (verb + object) + const title = prData.title.trim(); + const types = ['feat','fix','docs','refactor','test','chore','ci','build','perf','style']; + const naming = `^(${types.join('|')}):\\s+[A-Z][^\\s]*\\s+.+`; + const titleOK = new RegExp(naming).test(title); + const commitsOK = commits.every(c => new RegExp(naming).test(c.commit.message.split('\\n')[0])); + // 3) Description “why now?” + links to issue + const body = (prData.body || '').trim(); + const hasIssueLink = /#[0-9]+|https?:\/\/github\.com\/.+\/issues\/[0-9]+/i.test(body); + const mentionsWhy = /\bwhy\b|\bbecause\b|\brationale\b|\bcontext\b/i.test(body); + const descOK = body.length >= 50 && (mentionsWhy || hasIssueLink); + // 4) BREAKING change highlighted + const breakingFlagPresent = /\*\*?BREAKING\*\*?|⚠️\s*BREAKING|BREAKING CHANGE/i.test(title) || /\*\*?BREAKING\*\*?|⚠️\s*BREAKING|BREAKING CHANGE/i.test(body); + // Heuristic: if "breaking" appears anywhere, require emphasis flag; otherwise pass. + const containsBreakingWord = /\bbreaking\b/i.test(title) || /\bbreaking\b/i.test(body); + const breakingOK = containsBreakingWord ? breakingFlagPresent : true; + // 5) Request specific feedback + const feedbackOK = /\b(feedback|review focus|please focus|looking for|need input)\b/i.test(body); + // Soft hint: if code changed but no tests changed, nudge (not blocking per article) + const testsHint = codeTouched && !testsTouched; + // Build result table + function row(name, ok, hint='') { + const status = ok ? '✅' : '❌'; + const extra = hint ? ` — ${hint}` : ''; + return `| ${status} | ${name}${extra} |`; + } + const report = [ + `### PR Quality Gate — AI-Era Expertise Standard`, + `This automated review checks your PR against the five items GitHub recommends for high-quality, human-in-the-loop reviews.`, + ``, + `| Pass | Check |`, + `|:----:|:------|`, + row(`Scope ≤ 300 changed lines (current: ${totalChanged})`, scopeOK, scopeOK ? '' : 'Consider splitting into smaller PRs (stacking).'), + row(`Title and commits use type: description (verb + object)`, titleOK && commitsOK), + row(`Description answers "why now?" and links an issue`, descOK, hasIssueLink ? '' : 'Add a linked issue (#123) or URL.'), + row(`Highlight breaking changes with **BREAKING** or ⚠️ BREAKING`, breakingOK, containsBreakingWord && !breakingFlagPresent ? 'Add explicit BREAKING flag.' : ''), + row(`Request specific feedback (e.g., "Concurrency strategy OK?")`, feedbackOK), + ``, + testsHint ? `> ℹ️ Heads-up: Code changed but tests weren’t touched. The blog suggests reviewers read tests first—consider adding or updating tests for clarity.` : ``, + ``, + `_This gate is derived from GitHub’s “Why developer expertise matters more than ever in the age of AI.”_` + ].filter(Boolean).join('\n'); + // Determine blocking result (fail if any required check fails) + const failures = []; + if (!scopeOK) failures.push('Scope > 300 lines'); + if (!titleOK || !commitsOK) failures.push('Naming format invalid'); + if (!descOK) failures.push('Description lacks why/issue link'); + if (!breakingOK) failures.push('Missing explicit BREAKING flag'); + if (!feedbackOK) failures.push('No specific feedback requested'); + const sameRepo = pr.head.repo.full_name === `${owner}/${repo}`; + if (sameRepo) { + try { + // Upsert a single sticky comment + const bot = (await github.rest.users.getAuthenticated()).data.login; + const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number: pr.number }); + const existing = comments.find(c => c.user?.login === bot && /PR Quality Gate — AI-Era/.test(c.body || '')); + if (existing) { + await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body: report }); + } else { + await github.rest.issues.createComment({ owner, repo, issue_number: pr.number, body: report }); + } + // Add labels for visibility + const addLabel = async (name) => { + await github.rest.issues.addLabels({ owner, repo, issue_number: pr.number, labels: [name] }); + }; + const removeLabel = async (name) => { + await github.rest.issues.removeLabel({ owner, repo, issue_number: pr.number, name }); + }; + if (failures.length) { + await addLabel('needs-quality-fixes'); + } else { + await removeLabel('needs-quality-fixes'); + await addLabel('quality-checked'); + } + } catch (error) { + if (error.message && error.message.includes('Resource not accessible by integration')) { + core.warning('Skipping comment and label updates due to insufficient permissions.'); + } else { + throw error; + } + } + } else { + core.warning('PR originates from a fork; skipping comment and label updates.'); + } + // Fail the job if there are blocking issues + if (failures.length) { + core.setFailed('PR failed the expertise standard: ' + failures.join(', ')); + } else { + core.info('PR passes the expertise standard.'); + } + diff --git a/AGENTS.md b/AGENTS.md index b918f158c..3d0450a94 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,97 @@ # Repo Guidelines +## NIPs + +- https://github.com/nostr-protocol/nips/blob/master/01.md +- https://github.com/nostr-protocol/nips/blob/master/02.md +- https://github.com/nostr-protocol/nips/blob/master/03.md +- https://github.com/nostr-protocol/nips/blob/master/04.md +- https://github.com/nostr-protocol/nips/blob/master/05.md +- https://github.com/nostr-protocol/nips/blob/master/06.md +- https://github.com/nostr-protocol/nips/blob/master/07.md +- https://github.com/nostr-protocol/nips/blob/master/08.md +- https://github.com/nostr-protocol/nips/blob/master/09.md +- https://github.com/nostr-protocol/nips/blob/master/10.md +- https://github.com/nostr-protocol/nips/blob/master/11.md +- https://github.com/nostr-protocol/nips/blob/master/12.md +- https://github.com/nostr-protocol/nips/blob/master/13.md +- https://github.com/nostr-protocol/nips/blob/master/14.md +- https://github.com/nostr-protocol/nips/blob/master/15.md +- https://github.com/nostr-protocol/nips/blob/master/16.md +- https://github.com/nostr-protocol/nips/blob/master/17.md +- https://github.com/nostr-protocol/nips/blob/master/18.md +- https://github.com/nostr-protocol/nips/blob/master/19.md +- https://github.com/nostr-protocol/nips/blob/master/20.md +- https://github.com/nostr-protocol/nips/blob/master/21.md +- https://github.com/nostr-protocol/nips/blob/master/22.md +- https://github.com/nostr-protocol/nips/blob/master/23.md +- https://github.com/nostr-protocol/nips/blob/master/24.md +- https://github.com/nostr-protocol/nips/blob/master/25.md +- https://github.com/nostr-protocol/nips/blob/master/26.md +- https://github.com/nostr-protocol/nips/blob/master/27.md +- https://github.com/nostr-protocol/nips/blob/master/28.md +- https://github.com/nostr-protocol/nips/blob/master/29.md +- https://github.com/nostr-protocol/nips/blob/master/30.md +- https://github.com/nostr-protocol/nips/blob/master/31.md +- https://github.com/nostr-protocol/nips/blob/master/32.md +- https://github.com/nostr-protocol/nips/blob/master/33.md +- https://github.com/nostr-protocol/nips/blob/master/34.md +- https://github.com/nostr-protocol/nips/blob/master/35.md +- https://github.com/nostr-protocol/nips/blob/master/36.md +- https://github.com/nostr-protocol/nips/blob/master/37.md +- https://github.com/nostr-protocol/nips/blob/master/38.md +- https://github.com/nostr-protocol/nips/blob/master/39.md +- https://github.com/nostr-protocol/nips/blob/master/40.md +- https://github.com/nostr-protocol/nips/blob/master/42.md +- https://github.com/nostr-protocol/nips/blob/master/44.md +- https://github.com/nostr-protocol/nips/blob/master/45.md +- https://github.com/nostr-protocol/nips/blob/master/46.md +- https://github.com/nostr-protocol/nips/blob/master/47.md +- https://github.com/nostr-protocol/nips/blob/master/48.md +- https://github.com/nostr-protocol/nips/blob/master/49.md +- https://github.com/nostr-protocol/nips/blob/master/50.md +- https://github.com/nostr-protocol/nips/blob/master/51.md +- https://github.com/nostr-protocol/nips/blob/master/52.md +- https://github.com/nostr-protocol/nips/blob/master/53.md +- https://github.com/nostr-protocol/nips/blob/master/54.md +- https://github.com/nostr-protocol/nips/blob/master/55.md +- https://github.com/nostr-protocol/nips/blob/master/56.md +- https://github.com/nostr-protocol/nips/blob/master/57.md +- https://github.com/nostr-protocol/nips/blob/master/58.md +- https://github.com/nostr-protocol/nips/blob/master/59.md +- https://github.com/nostr-protocol/nips/blob/master/60.md +- https://github.com/nostr-protocol/nips/blob/master/61.md +- https://github.com/nostr-protocol/nips/blob/master/62.md +- https://github.com/nostr-protocol/nips/blob/master/64.md +- https://github.com/nostr-protocol/nips/blob/master/65.md +- https://github.com/nostr-protocol/nips/blob/master/66.md +- https://github.com/nostr-protocol/nips/blob/master/68.md +- https://github.com/nostr-protocol/nips/blob/master/69.md +- https://github.com/nostr-protocol/nips/blob/master/70.md +- https://github.com/nostr-protocol/nips/blob/master/71.md +- https://github.com/nostr-protocol/nips/blob/master/72.md +- https://github.com/nostr-protocol/nips/blob/master/73.md +- https://github.com/nostr-protocol/nips/blob/master/75.md +- https://github.com/nostr-protocol/nips/blob/master/77.md +- https://github.com/nostr-protocol/nips/blob/master/78.md +- https://github.com/nostr-protocol/nips/blob/master/7D.md +- https://github.com/nostr-protocol/nips/blob/master/84.md +- https://github.com/nostr-protocol/nips/blob/master/86.md +- https://github.com/nostr-protocol/nips/blob/master/87.md +- https://github.com/nostr-protocol/nips/blob/master/88.md +- https://github.com/nostr-protocol/nips/blob/master/89.md +- https://github.com/nostr-protocol/nips/blob/master/90.md +- https://github.com/nostr-protocol/nips/blob/master/92.md +- https://github.com/nostr-protocol/nips/blob/master/94.md +- https://github.com/nostr-protocol/nips/blob/master/96.md +- https://github.com/nostr-protocol/nips/blob/master/98.md +- https://github.com/nostr-protocol/nips/blob/master/99.md +- https://github.com/nostr-protocol/nips/blob/master/A0.md +- https://github.com/nostr-protocol/nips/blob/master/B0.md +- https://github.com/nostr-protocol/nips/blob/master/B7.md +- https://github.com/nostr-protocol/nips/blob/master/C0.md +- https://github.com/nostr-protocol/nips/blob/master/C7.md + ## Description nostr-java is a java implementation of the nostr protocol. The specification is available on github, here: https://github.com/nostr-protocol/nips The URL format for the NIPs is https://github.com/nostr-protocol/nips/blob/master/XX.md where XX is the NIP number. For example, the specification for NIP-01 is available at the URL https://github.com/nostr-protocol/nips/blob/master/01.md etc. @@ -23,7 +115,19 @@ The URL format for the NIPs is https://github.com/nostr-protocol/nips/blob/maste ## Pull Requests +- Use the pull request template at `.github/pull_request_template.md` and fill out all sections. - Summarize the changes made and describe how they were tested. - Include any limitations or known issues in the description. - Add a "Network Access" section summarizing blocked domains if network requests were denied. -- Ensure all new features, modules, or dependencies are properly documented in the `README.md` file. \ No newline at end of file +- Ensure all new features, modules, or dependencies are properly documented in the `README.md` file. +## PR Quality Gate + +- PR summaries must reference modified files with file path citations (e.g. `F:path/to/file.java†L1-L2`). +- PR titles and commit messages must follow the `type: description` naming format. +- Allowed types: feat, fix, docs, refactor, test, chore, ci, build, perf, style. +- The description should be a concise verb + object phrase (e.g., `refactor: Refactor auth middleware to async`). +- Include a Testing section listing the commands run. Prefix each command with ✅, ⚠️, or ❌ and cite relevant terminal output. +- If network requests fail, add a Network Access section noting blocked domains. +- When TODOs or placeholders remain, include a Notes section. +- Review AI-generated changes with developer expertise, ensuring you understand why the code works and that it remains resilient, scalable, and secure. +- Use `rg` for search instead of `ls -R` or `grep -R`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..711c6e4f3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +# Contributing to nostr-java + +nostr-java implements the Nostr protocol. A complete index of current Nostr Implementation Possibilities (NIPs) is listed in [AGENTS.md](AGENTS.md). + +## Development Guidelines + +- Run `mvn -q verify` from the repository root before committing. +- Use `rg` for code searches instead of `ls -R` or `grep -R`. +- PR titles and commit messages must follow the `type: description` format. + - Allowed types: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `ci`, `build`, `perf`, `style`. + - The description must be a concise verb + object phrase (e.g., `refactor: update auth middleware to async`). +- Summaries in pull requests must cite file paths and include testing output. +- Open pull requests using the template at `.github/pull_request_template.md` and complete every section. + +By following these conventions, contributors help keep the codebase maintainable and aligned with the Nostr specifications. + diff --git a/README.md b/README.md index afc1de39f..ff133f683 100644 --- a/README.md +++ b/README.md @@ -10,54 +10,14 @@ - Maven - Java 21+ -## Getting Started -Clone the repository and build the modules. Installing them locally allows you to add the artifacts as dependencies in your own projects: +See [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md) for installation and usage instructions. -```bash -$ git clone https://github.com/tcheeric/nostr-java.git -$ cd nostr-java -$ ./mvnw clean install -``` - -You can also consume published artifacts from our Maven repository instead of building from source. - -### Maven - -```xml - - - nostr-java - https://maven.398ja.xyz/releases - - - - - xyz.tcheeric - nostr-java-api - [VERSION] - -``` - -Snapshot builds are available at `https://maven.398ja.xyz/snapshots`. - -### Gradle - -```gradle -repositories { - maven { url 'https://maven.398ja.xyz/releases' } -} - -dependencies { - implementation 'xyz.tcheeric:nostr-java-api:[VERSION]' -} -``` - -Replace `[VERSION]` with the latest release number from the [releases page](https://github.com/tcheeric/nostr-java/releases). +For a quick API walkthrough, see [`docs/howto/use-nostr-java-api.md`](docs/howto/use-nostr-java-api.md). See [`docs/CODEBASE_OVERVIEW.md`](docs/CODEBASE_OVERVIEW.md) for details about running tests and contributing. ## Examples -Example usages are located in the [`nostr-java-examples`](./nostr-java-examples) module. Additional demonstrations can be found in [nostr-client](https://github.com/tcheeric/nostr-client) and [SuperConductor](https://github.com/avlo/superconductor). +Examples are located in the [`nostr-java-examples`](./nostr-java-examples) module. ## Supported NIPs The API currently implements the following [NIPs](https://github.com/nostr-protocol/nips): diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md new file mode 100644 index 000000000..1d85c523d --- /dev/null +++ b/docs/GETTING_STARTED.md @@ -0,0 +1,51 @@ +# Getting Started + +## Prerequisites +- Maven +- Java 21+ + +## Building from Source + +```bash +git clone https://github.com/tcheeric/nostr-java.git +cd nostr-java +./mvnw clean install +``` + +## Using Maven + +Artifacts are published to `https://maven.398ja.xyz/releases`: + +```xml + + + nostr-java + https://maven.398ja.xyz/releases + + + + + xyz.tcheeric + nostr-java-api + [VERSION] + +``` + +Snapshot builds are available at `https://maven.398ja.xyz/snapshots`. + +## Using Gradle + +```gradle +repositories { + maven { url 'https://maven.398ja.xyz/releases' } +} + +dependencies { + implementation 'xyz.tcheeric:nostr-java-api:[VERSION]' +} +``` + +Replace `[VERSION]` with the latest release number from the [releases page](https://github.com/tcheeric/nostr-java/releases). + +Examples are available in the [`nostr-java-examples`](../nostr-java-examples) module. + diff --git a/docs/explanation/extending-events.md b/docs/explanation/extending-events.md new file mode 100644 index 000000000..89891a2f1 --- /dev/null +++ b/docs/explanation/extending-events.md @@ -0,0 +1,25 @@ +# Extending Events + +This project uses factories and registries to make it easy to introduce new event types while keeping core classes stable. + +## Factory and Registry Overview + +- **Event factories** (e.g. [`EventFactory`](../../nostr-java-api/src/main/java/nostr/api/factory/EventFactory.java) and its implementations) centralize event creation so that callers don't have to handle boilerplate like setting the sender, tags, or content. +- **TagRegistry** maps tag codes to concrete implementations, allowing additional tag types to be resolved at runtime without modifying `BaseTag`. + +## Adding a New Event Type + +1. **Define the kind.** Add a constant to [`Kind`](../../nostr-java-base/src/main/java/nostr/base/Kind.java) or reserve a custom value. +2. **Implement the event.** Create a class under `nostr.event.impl` that extends `GenericEvent` or a more specific base class. +3. **Provide a factory.** Implement a factory extending `EventFactory` to encapsulate default tags and content for the new event. +4. **Register tags.** If the event introduces new tag codes, register their factory functions with [`TagRegistry`](../../nostr-java-event/src/main/java/nostr/event/tag/TagRegistry.java). +5. **Write tests.** Add unit and integration tests covering serialization, deserialization, and NIP compliance. +6. **Follow contributing guidelines.** Run `mvn -q verify` before committing, ensure events comply with Nostr NIPs, and document your changes. + +## Testing & Contribution Requirements + +- Run `mvn -q verify` from the repository root and ensure all checks pass. +- Include comprehensive tests for new functionality and remove unused imports. +- Summaries of changes and test results are expected in pull requests. + +Refer to the repository's `AGENTS.md` for the full list of contribution expectations. diff --git a/docs/howto/custom-events.md b/docs/howto/custom-events.md new file mode 100644 index 000000000..3dcac4b67 --- /dev/null +++ b/docs/howto/custom-events.md @@ -0,0 +1,54 @@ +# Custom Nostr Events + +This guide shows how to construct and publish a Nostr event with a non-standard `kind` using **nostr-java**. + +## Background + +Every Nostr event must include the fields defined in [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md): + +- `id` +- `pubkey` +- `created_at` +- `kind` +- `tags` +- `content` +- `sig` + +Kinds that are not defined by existing NIPs may still be used. [NIP-16](https://github.com/nostr-protocol/nips/blob/master/16.md) describes how kind numbers are grouped (regular, replaceable, ephemeral and parameterized replaceable). Choose a value that does not collide with other applications. + +## Example + +```java +import java.util.List; + +import nostr.client.springwebsocket.StandardWebSocketClient; +import nostr.event.BaseTag; +import nostr.event.impl.GenericEvent; +import nostr.event.message.EventMessage; +import nostr.id.Identity; + +public class CustomEventExample { + public static void main(String[] args) throws Exception { + Identity identity = Identity.generateRandomIdentity(); + + int CUSTOM_KIND = 9000; // Non-standard kind + GenericEvent event = new GenericEvent(identity.getPublicKey(), CUSTOM_KIND, List.of(), + "Hello from a custom kind!"); + + // Required fields `id` and `sig` are populated when signing + identity.sign(event); + + try (StandardWebSocketClient client = new StandardWebSocketClient("wss://relay.example.com")) { + client.send(new EventMessage(event)); + } + } +} +``` + +## Steps Explained + +1. **Construct the event** – Provide the public key, custom kind, any tags, and content. The constructor fills in `created_at` automatically and initializes the list of `tags`. +2. **Sign** – `Identity.sign(event)` computes the event `id` and `sig` using the private key. Relays verify these fields against the serialized event bytes as defined in NIP‑01. +3. **Submit to a relay** – Send the event using an `EVENT` message. The example uses `StandardWebSocketClient`, but any Nostr-compatible relay transport will work. + +For more information about event structure and relay communication, consult [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) and [NIP-16](https://github.com/nostr-protocol/nips/blob/master/16.md). diff --git a/docs/howto/use-nostr-java-api.md b/docs/howto/use-nostr-java-api.md new file mode 100644 index 000000000..d08be83aa --- /dev/null +++ b/docs/howto/use-nostr-java-api.md @@ -0,0 +1,44 @@ +# Using the nostr-java API + +This guide shows how to set up the library and publish a basic [Nostr](https://github.com/nostr-protocol/nips) event. + +## Minimal setup + +Add the API module to your project: + +```xml + + xyz.tcheeric + nostr-java-api + [VERSION] + +``` + +Replace `[VERSION]` with the latest release number. + +## Create, sign, and publish an event + +```java +import nostr.api.NIP01; +import nostr.id.Identity; + +import java.util.Map; + +public class QuickStart { + public static void main(String[] args) { + Identity identity = Identity.generateRandomIdentity(); + Map relays = Map.of("local", "wss://nostr.example"); + + new NIP01(identity) + .createTextNoteEvent("Hello nostr") + .sign() + .send(relays); + } +} +``` + +### Reference +- [`Identity.generateRandomIdentity`](../../nostr-java-id/src/main/java/nostr/id/Identity.java) +- [`NIP01.createTextNoteEvent`](../../nostr-java-api/src/main/java/nostr/api/NIP01.java) +- [`EventNostr.sign`](../../nostr-java-api/src/main/java/nostr/api/EventNostr.java) +- [`EventNostr.send`](../../nostr-java-api/src/main/java/nostr/api/EventNostr.java) diff --git a/docs/reference/nostr-java-api.md b/docs/reference/nostr-java-api.md new file mode 100644 index 000000000..15a0dba3e --- /dev/null +++ b/docs/reference/nostr-java-api.md @@ -0,0 +1,184 @@ +# Nostr Java API Reference + +This document provides an overview of the public API exposed by the `nostr-java` modules. It lists the major classes, configuration objects and their key method signatures, and shows brief examples of how to use them. Where applicable, links to related [Nostr Improvement Proposals (NIPs)](https://github.com/nostr-protocol/nips) are provided. + +## Identity (`nostr-java-id`) + +### `Identity` +Represents a Nostr identity backed by a private key. It can derive a public key and sign `ISignable` objects. + +```java +public static Identity create(PrivateKey privateKey) +public static Identity create(String privateKey) +public static Identity generateRandomIdentity() +public PublicKey getPublicKey() +public Signature sign(ISignable signable) +``` + +**Usage:** +```java +Identity id = Identity.generateRandomIdentity(); +PublicKey pub = id.getPublicKey(); +Signature sig = id.sign(event); +``` + +## Event Model (`nostr-java-event`) + +### Core Types +- `BaseMessage` – base class for all relay messages. +- `BaseEvent` – root class for Nostr events. +- `BaseTag` – helper for tag encoding and decoding. + +### Predefined Events +The `nostr.event` package provides event implementations for many NIPs: + +| Class | NIP | +|-------|-----| +| `NIP01Event` | [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) – standard text notes. | +| `NIP04Event` | [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md) – encrypted direct messages. | +| `NIP05Event` | [NIP-05](https://github.com/nostr-protocol/nips/blob/master/05.md) – DNS identifiers. | +| `NIP09Event` | [NIP-09](https://github.com/nostr-protocol/nips/blob/master/09.md) – event deletion. | +| `NIP25Event` | [NIP-25](https://github.com/nostr-protocol/nips/blob/master/25.md) – reactions. | +| `NIP52Event` | [NIP-52](https://github.com/nostr-protocol/nips/blob/master/52.md) – calendar events. | +| `NIP99Event` | [NIP-99](https://github.com/nostr-protocol/nips/blob/master/99.md) – classified listings. | + +### Filters +`Filters` and related `Filterable` implementations help build subscription requests. + +```java +new Filters(Filterable... filterables) +List getFilterByType(String type) +void setLimit(Integer limit) +``` + +**Usage:** +```java +Filters filters = new Filters(new AuthorFilter(pubKey)); +filters.setLimit(100); +``` + +## WebSocket Clients (`nostr-java-client`, `nostr-java-api`) + +### `WebSocketClientIF` +Abstraction over a WebSocket connection to a relay. + +```java + List send(T eventMessage) throws IOException +List send(String json) throws IOException +void close() throws IOException +``` + +### `StandardWebSocketClient` +Spring `TextWebSocketHandler` based implementation of `WebSocketClientIF`. + +```java +public StandardWebSocketClient(String relayUri) +public List send(T eventMessage) throws IOException +public List send(String json) throws IOException +public void close() throws IOException +``` + +### `SpringWebSocketClient` +Wrapper that adds retry logic around a `WebSocketClientIF`. + +```java +public List send(BaseMessage eventMessage) throws IOException +public List send(String json) throws IOException +public List recover(IOException ex, String json) throws IOException +public void close() throws IOException +``` + +### `NostrSpringWebSocketClient` +High level client coordinating multiple relay connections and signing. + +```java +public NostrIF setRelays(Map relays) +public List sendEvent(IEvent event) +public List sendRequest(List filters, String subscriptionId) +public NostrIF sign(Identity identity, ISignable signable) +public boolean verify(GenericEvent event) +public Map getRelays() +public void close() +``` + +### Configuration +- `RetryConfig` – enables Spring Retry support. +- `RelaysProperties` – maps relay names to URLs via configuration properties. +- `RelayConfig` – loads `relays.properties` and exposes a `Map` bean. + +## Encryption and Cryptography + +### `MessageCipher` +Strategy interface for message encryption. + +```java +String encrypt(String message) +String decrypt(String message) +``` + +Implementations: +- `MessageCipher04` – NIP-04 direct message encryption. +- `MessageCipher44` – NIP-44 payload encryption. + +### `Schnorr` +Utility for Schnorr signatures (BIP-340). + +```java +static byte[] sign(byte[] msg, byte[] secKey, byte[] auxRand) +static boolean verify(byte[] msg, byte[] pubKey, byte[] sig) +static byte[] generatePrivateKey() +static byte[] genPubKey(byte[] secKey) +``` + +### `Bech32` +Utility for Bech32/Bech32m encoding used by [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md). + +```java +static String toBech32(Bech32Prefix hrp, byte[] hexKey) +static String fromBech32(String str) +``` + +## Utilities (`nostr-java-util`) + +### `NostrUtil` +General helper functions. + +```java +static String bytesToHex(byte[] bytes) +static byte[] hexToBytes(String hex) +static byte[] sha256(byte[] data) +static byte[] createRandomByteArray(int len) +``` + +### `NostrException` +Base checked exception for utility methods. + +## Examples + +### Send a Text Note (NIP-01) +```java +Identity id = Identity.generateRandomIdentity(); +NIP01 nip01 = new NIP01(id).createTextNoteEvent("Hello Nostr"); +NostrIF client = NostrSpringWebSocketClient.getInstance(id) + .setRelays(Map.of("relay","wss://relay.example")); +client.sendEvent(nip01.getEvent()); +``` + +### Encrypted Direct Message (NIP-04) +```java +Identity alice = Identity.generateRandomIdentity(); +Identity bob = Identity.generateRandomIdentity(); +NIP04 dm = new NIP04(alice, bob.getPublicKey()) + .createDirectMessageEvent("secret"); +String plaintext = NIP04.decrypt(bob, dm.getEvent()); +``` + +### Subscription with Filters +```java +Filters filters = new Filters(new AuthorFilter(pubKey)); +NostrIF client = NostrSpringWebSocketClient.getInstance(id); +List events = client.sendRequest(filters, "sub-id"); +``` + +--- +This reference is a starting point; consult the source for complete details and additional NIP helpers. diff --git a/pom.xml b/pom.xml index 86d90fe7c..29a5eabe7 100644 --- a/pom.xml +++ b/pom.xml @@ -85,24 +85,24 @@ 1.81 4.3.0 - 1.78 - 3.17.0 + 1.81 + 3.18.0 2.19.2 - 1.18.36 + 1.18.38 - 5.10.2 - 33.4.0-jre + 33.4.8-jre + 5.13.4 1.21.3 - 0.6.0 + 0.8.0 0.8.13 1.7.2 - 3.13.0 + 3.14.0 3.5.3 1.5 - 2.9.1 + 3.11.3 3.3.1 3.5.3