Compare commits
1 Commits
master
...
tyiu/navba
| Author | SHA1 | Date | |
|---|---|---|---|
|
5bf5402919
|
52
.github/ISSUE_TEMPLATE/app_release.md
vendored
52
.github/ISSUE_TEMPLATE/app_release.md
vendored
@@ -1,52 +0,0 @@
|
||||
---
|
||||
name: App release process
|
||||
about: Begin preparing for a new app release
|
||||
title: 'Release: '
|
||||
labels: release-tasks
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
A new version release. Please attempt to follow the release process steps below in the order they are shown.
|
||||
|
||||
## TestFlight release candidates
|
||||
|
||||
### Release candidate 1
|
||||
|
||||
**Version:** _[Enter full build information for the release candidate, including major and minor version number, build number, and commit hash]_
|
||||
|
||||
1. [ ] Merge in all needed changes to `master`
|
||||
2. [ ] Check CI, make sure it is passing
|
||||
3. [ ] Prepare preliminary changelog as a draft PR: _[Enter PR link to changelog here]_
|
||||
4. [ ] Make a _release_ build and submit to the internal TestFlight group via our new Release candidate workflow in Xcode Cloud.
|
||||
5. [ ] Prepare short screencast style video with main changes for the announcement
|
||||
6. [ ] Publish release build to these TestFlight groups:
|
||||
- [ ] Alpha testers group
|
||||
- [ ] Translators group
|
||||
- [ ] Purple group
|
||||
7. [ ] Publish announcement on Nostr
|
||||
|
||||
|
||||
_[Duplicate this release candidate section if there is more than one release candidate]_
|
||||
|
||||
|
||||
## App Store release
|
||||
|
||||
1. [ ] Release candidate checks:
|
||||
- [ ] Release candidate has been on Purple TestFlight for at least one week
|
||||
- [ ] No blocker issues came from feedback from Purple users (double-check)
|
||||
- [ ] Check with stakeholders
|
||||
- [ ] Check with developers & product for any release showstoppers (e.g., critical newfound bugs)
|
||||
2. [ ] Thorough check on release notes
|
||||
3. [ ] Submit to App Store review (with manual publishing setting enabled)
|
||||
4. [ ] Get App Store approval from Apple
|
||||
5. [ ] Prepare announcement
|
||||
7. [ ] Publish on the App Store and make announcement
|
||||
8. [ ] Publish changelog and tag commit hash corresponding to the release
|
||||
9. [ ] Perform a version bump on the repository, in preparation for the next release
|
||||
|
||||
|
||||
## Notes/others
|
||||
|
||||
_Enter any relevant notes here_
|
||||
|
||||
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: 'Bug: '
|
||||
labels: bug, Needs recreation
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**What happens**
|
||||
When I perform action ___, _____ happens.
|
||||
|
||||
**What I expect to happen**
|
||||
I expect _______ to happen.
|
||||
|
||||
**Link to noteID, npub**
|
||||
Provide link to relevant noteID, npub etc.
|
||||
|
||||
**Screenshots/video recording**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
|
||||
** Versions **
|
||||
Damus version: [e.g. 1.7.2 (1()]
|
||||
Operating system version: [e.g. iOS 17.2.1]
|
||||
Device: e.g. iPhone 13 Pro
|
||||
|
||||
**Steps To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Open Damus
|
||||
2. Tap on ___
|
||||
3. Action ____
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
27
.github/ISSUE_TEMPLATE/feature_request.md
vendored
27
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,27 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: 'Feature Request:'
|
||||
labels: feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Have a go at filling out the User Story template below
|
||||
|
||||
As a Damus user who is _____________, I would like to _________________, so that I achieve ___________.
|
||||
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
** When does this problem happen? **
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
66
.github/pull_request_template.md
vendored
66
.github/pull_request_template.md
vendored
@@ -1,66 +0,0 @@
|
||||
## Summary
|
||||
|
||||
_[Please provide a summary of the changes in this PR.]_
|
||||
|
||||
## Checklist
|
||||
|
||||
<!--
|
||||
CHOOSE YOUR CHECKLIST:
|
||||
- If this is an EXPERIMENTAL DAMUS LABS FEATURE, follow the "Experimental Feature Checklist" below and DELETE the "Standard PR Checklist"
|
||||
- If this is a STANDARD PR, follow the "Standard PR Checklist" below and DELETE the "Experimental Feature Checklist"
|
||||
-->
|
||||
|
||||
### Experimental Feature Checklist
|
||||
|
||||
<!-- DELETE THIS SECTION if this is a standard PR -->
|
||||
|
||||
> [!TIP]
|
||||
> This Pull Request is an experimental feature for Damus Labs, and follows a fast-track review process.
|
||||
> The overall requirements are lowered and the review process is not as strict as usual. However, the feature will only be available for Purple users who opt-in.
|
||||
|
||||
- [ ] I have read (or I am familiar with) the [Contribution Guidelines](../docs/CONTRIBUTING.md).
|
||||
- [ ] I have done some testing on the changes in this PR to ensure it is at least functional.
|
||||
- [ ] I made sure that this new feature is only available when the user opts-in from the Damus Labs screen, and does not affect the rest of the app when turned off.
|
||||
- [ ] My PR is either small, or I have split it into smaller logical commits that are easier to review.
|
||||
- [ ] I have added the signoff line to all my commits. See [Signing off your work](../docs/CONTRIBUTING.md#sign-your-work---the-developers-certificate-of-origin).
|
||||
- [ ] I have added an appropriate changelog entry to my commit in this PR. See [Adding changelog entries](../docs/CONTRIBUTING.md#add-changelog-changed-changelog-fixed-etc).
|
||||
- Example changelog entry: `Changelog-Added: Added experimental feature <X> to Damus Labs`
|
||||
|
||||
### Standard PR Checklist
|
||||
|
||||
<!-- DELETE THIS SECTION if this is an experimental Damus Labs feature -->
|
||||
|
||||
- [ ] I have read (or I am familiar with) the [Contribution Guidelines](../docs/CONTRIBUTING.md)
|
||||
- [ ] I have tested the changes in this PR
|
||||
- [ ] I have profiled the changes to ensure there are no performance regressions, or I do not need to profile the changes.
|
||||
- Utilize Xcode profiler to measure performance impact of code changes. See https://developer.apple.com/videos/play/wwdc2025/306
|
||||
- If not needed, provide reason:
|
||||
- [ ] I have opened or referred to an existing github issue related to this change.
|
||||
- [ ] My PR is either small, or I have split it into smaller logical commits that are easier to review
|
||||
- [ ] I have added the signoff line to all my commits. See [Signing off your work](../docs/CONTRIBUTING.md#sign-your-work---the-developers-certificate-of-origin)
|
||||
- [ ] I have added appropriate changelog entries for the changes in this PR. See [Adding changelog entries](../docs/CONTRIBUTING.md#add-changelog-changed-changelog-fixed-etc)
|
||||
- [ ] I do not need to add a changelog entry. Reason: _[Please provide a reason]_
|
||||
- [ ] I have added appropriate `Closes:` or `Fixes:` tags in the commit messages wherever applicable, or made sure those are not needed. See [Submitting patches](https://github.com/damus-io/damus/blob/master/docs/CONTRIBUTING.md#submitting-patches)
|
||||
|
||||
## Test report
|
||||
|
||||
_Please provide a test report for the changes in this PR. You can use the template below, but feel free to modify it as needed._
|
||||
|
||||
**Device:** _[Please specify the device you used for testing]_
|
||||
|
||||
**iOS:** _[Please specify the iOS version you used for testing]_
|
||||
|
||||
**Damus:** _[Please specify the Damus version or commit hash you used for testing]_
|
||||
|
||||
**Setup:** _[Please provide a brief description of the setup you used for testing, if applicable]_
|
||||
|
||||
**Steps:** _[Please provide a list of steps you took to test the changes in this PR]_
|
||||
|
||||
**Results:**
|
||||
- [ ] PASS
|
||||
- [ ] Partial PASS
|
||||
- Details: _[Please provide details of the partial pass]_
|
||||
|
||||
## Other notes
|
||||
|
||||
_[Please provide any other information that you think is relevant to this PR.]_
|
||||
31
.github/workflows/run-tests.yaml
vendored
Normal file
31
.github/workflows/run-tests.yaml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Run Test Suite
|
||||
run-name: Testing ${{ github.ref }} by @${{ github.actor }}
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
pull_request:
|
||||
branches:
|
||||
- "*"
|
||||
|
||||
|
||||
jobs:
|
||||
run_tests:
|
||||
runs-on: macos-12
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- xcode: "14.2"
|
||||
ios: "16.2"
|
||||
|
||||
name: Test iOS (${{ matrix.ios }})
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
- name: Select Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: ${{ matrix.xcode }}
|
||||
- name: Run Tests
|
||||
run: xcodebuild test -scheme damus -project damus.xcodeproj -destination 'platform=iOS Simulator,name=iPhone 14,OS=${{ matrix.ios }}' | xcpretty && exit ${PIPESTATUS[0]}
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,9 +1,5 @@
|
||||
xcuserdata
|
||||
/.direnv
|
||||
damus/TestingPrivate.swift
|
||||
damus.xcodeproj/xcshareddata/xcbaselines
|
||||
.DS_Store
|
||||
TODO.bak
|
||||
tags
|
||||
build-git-hash.txt
|
||||
.build
|
||||
|
||||
7
.mailmap
7
.mailmap
@@ -1,7 +0,0 @@
|
||||
Terry Yiu <git@tyiu.xyz> <963907+tyiu@users.noreply.github.com>
|
||||
Ben Weeks <ben.weeks@knowall.ai> <ben.weeks@outlook.com>
|
||||
Suhail Saqan <suhail.saqan@gmail.com> <43693074+suhailsaqan@users.noreply.github.com>
|
||||
cr0bar <cr0bar@cr0.bar> <cr0bar@users.noreply.github.com>
|
||||
Swift <scoder1747@gmail.com> <120697811+scoder1747@users.noreply.github.com>
|
||||
Daniel D'Aquino <daniel@daquino.me> <patches@damus.io>
|
||||
Transifex <transifex@transifex.com> <43880903+transifex-integration[bot]@users.noreply.github.com>
|
||||
@@ -1,5 +0,0 @@
|
||||
### Acknowledgements and licenses
|
||||
|
||||
1. This product contains code derived from [Nostr SDK iOS](https://github.com/nostr-sdk/nostr-sdk-ios). [License](https://github.com/nostr-sdk/nostr-sdk-ios/blob/40df800c6749d7ce0b6fd7328e76cbc0dc71c87b/LICENSE)
|
||||
2. This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/). [License](https://github.com/krzyzanowskim/CryptoSwift/blob/e74bbbfbef939224b242ae7c342a90e60b88b5ce/LICENSE)
|
||||
|
||||
47
AGENTS.md
47
AGENTS.md
@@ -1,47 +0,0 @@
|
||||
# Agents
|
||||
|
||||
## Damus Overview
|
||||
|
||||
Damus is an iOS client built around a local relay model ([damus-io/damus#3204](https://github.com/damus-io/damus/pull/3204)) to keep interactions snappy and resilient. The app operates on `nostrdb` ([source](https://github.com/damus-io/damus/tree/master/nostrdb)), and agents working on Damus should maximize usage of `nostrdb` facilities whenever possible.
|
||||
|
||||
## Codebase Layout
|
||||
|
||||
- `damus/` contains the SwiftUI app. Key subdirectories: `Core` (protocol, storage, networking, nostr primitives), `Features` (feature-specific flows like Timeline, Wallet, Purple), `Shared` (reusable UI components and utilities), `Models`, and localized resources (`*.lproj`, `en-US.xcloc`).
|
||||
- `nostrdb/` hosts the embedded database. Swift bindings (`Ndb.swift`, iterators) wrap a C/LMDB core; prefer these abstractions when working with persistence or queries.
|
||||
- `damus-c/` bridges C helpers (e.g., WASM runner) into Swift; check `damus-Bridging-Header.h` before adding new bridges.
|
||||
- `nostrscript/` contains AssemblyScript sources compiled to WASM via the top-level `Makefile`.
|
||||
- Tests live in `damusTests/` (unit/snapshot coverage) and `damusUITests/` (UI smoke tests). Keep them running before submitting changes.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
- Use `just build` / `just test` for simulator builds and the primary test suite (requires `xcbeautify`). Update or add `just` recipes if new repeatable workflows emerge.
|
||||
- Xcode project is `damus.xcodeproj`; the main scheme is `damus`. Ensure new targets or resources integrate cleanly with this scheme.
|
||||
- Rebuild WASM helpers with `make` when touching `nostrscript/` sources.
|
||||
- Follow `docs/DEV_TIPS.md` for debugging (enabling Info logging, staging push notification settings) and keep tips updated when discovering new workflows.
|
||||
|
||||
## Testing Expectations
|
||||
|
||||
- Provide a concrete test report in each PR (see `.github/pull_request_template.md`). Document devices, OS versions, and scenarios exercised.
|
||||
- Add or update unit tests in `damusTests/` alongside feature changes, especially when touching parsing, storage, or replay logic.
|
||||
- UI regressions should include `damusUITests/` coverage or rationale when automation is impractical.
|
||||
- Snapshot fixtures under `damusTests/__Snapshots__` must be regenerated deliberately; explain updates in commit messages.
|
||||
|
||||
## Contribution Standards
|
||||
|
||||
- Sign all commits (`git commit -s`) and include appropriate `Changelog-*`, `Closes:`, or `Fixes:` tags as described in `docs/CONTRIBUTING.md`.
|
||||
- Keep patches scoped: one logical change per commit, ensuring the app builds and runs after each step.
|
||||
- Favor Swift-first solutions that lean on `nostrdb` types (`Ndb`, `NdbNote`, iterators) before introducing new storage mechanisms.
|
||||
- Update documentation when workflows change, especially this file, `README.md`, or developer notes.
|
||||
|
||||
## Agent Requirements
|
||||
|
||||
1. Code should tend toward simplicity.
|
||||
2. Commits should be logically distinct.
|
||||
3. Commits should be standalone.
|
||||
4. Code should be human readable.
|
||||
5. Code should be human reviewable.
|
||||
6. Ensure docstring coverage for any code added, or modified.
|
||||
7. Review and follow `pull_request_template.md` when creating PRs for iOS Damus.
|
||||
8. Ensure nevernesting: favor early returns and guard clauses over deeply nested conditionals; simplify control flow by exiting early instead of wrapping logic in multiple layers of `if` statements.
|
||||
9. Before proposing changes, please **review and analyze if a change or upgrade to nostrdb** is beneficial to the change at hand.
|
||||
10. **Never block the main thread**: All network requests, database queries, and expensive computations must run on background threads/queues. Use `Task { }`, `DispatchQueue.global()`, or Swift concurrency (`async/await`) appropriately. UI updates must dispatch back to `@MainActor`. Test for hangs and freezes before submitting.
|
||||
906
CHANGELOG.md
906
CHANGELOG.md
@@ -1,908 +1,3 @@
|
||||
## [1.15] - 2025-07-11
|
||||
|
||||
**Note:** This version was only released on TestFlight, and never officially released on the App Store.
|
||||
|
||||
### Added
|
||||
|
||||
- Added new onboarding suggestions based on user-selected interests (Daniel D’Aquino)
|
||||
- Added adjustable max budget setting for Coinos one-click wallets (Daniel D’Aquino)
|
||||
- Added send feature to the wallet view (Daniel D’Aquino)
|
||||
- Added popover tips to DMs and Notifications toolbars on Trusted Network button (Terry Yiu)
|
||||
- Added tip in threads to inform users what trusted network means (Terry Yiu)
|
||||
- Added web of trust reply sorting in threads to mitigate spam (Terry Yiu)
|
||||
- Added follow list kind 39089 (ericholguin)
|
||||
- Added follow pack preview (ericholguin)
|
||||
- Added follow pack timeline to Universe View (ericholguin)
|
||||
- Added NIP-05 favicon to profile names and NIP-05 web of trust feed (Terry Yiu)
|
||||
- Display uploading indicator in post view (Swift Coder)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved the image sizing behavior on the image carousel for a smoother experience (Daniel D’Aquino)
|
||||
- Handle npub correctly in draft notes (Askia Linder)
|
||||
- Move users-section to be last in muted view (Askia Linder)
|
||||
- Removed media from regular link previews if media is already being shown (Terry Yiu)
|
||||
- Renamed Friends of Friends to Trusted Network (Terry Yiu)
|
||||
- Added privacy-based redaction to nsec in key settings view (Terry Yiu)
|
||||
- Added privacy-based redaction to wallet view (Terry Yiu)
|
||||
- Renamed Bitcoin Beach wallet to Blink (Terry Yiu)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed #nsfw tag filtering to be case insensitive (Terry Yiu)
|
||||
- Fixed stretchy banner header in Edit profile (Swift)
|
||||
- Fixed note rendering to include regular link previews with media removed when media previews are disabled (Terry Yiu)
|
||||
- Improve error handling on wallet send feature (Daniel D’Aquino)
|
||||
- Fixed issue where the text "??" would appear on the balance while loading (Daniel D’Aquino)
|
||||
- Hide end previewables when hashtags are present (Terry Yiu)
|
||||
- Fixed wallet transactions to always show profile display name unless there is no pubkey (Terry Yiu)
|
||||
- Fixed quotes view header alignment (Terry Yiu)
|
||||
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed hashtags in Universe View (ericholguin)
|
||||
|
||||
|
||||
[1.15]: https://github.com/damus-io/damus/releases/tag/v1.15
|
||||
|
||||
|
||||
## [1.14] - 2025-05-25
|
||||
|
||||
### Added
|
||||
|
||||
- Added safety reminder to wallets with higher balance (Daniel D’Aquino)
|
||||
- Added one-click Coinos wallet setup (Daniel D’Aquino)
|
||||
- Add notification setting to hide hellthreads (Terry Yiu)
|
||||
- Added separated first aid option for relay lists that does not need a contact list reset (Daniel D’Aquino)
|
||||
- Added NIP-65 relay list support (Daniel D’Aquino)
|
||||
- Added Unicode 16 emoji reactions for iOS 18.4+ by upgrading EmojiPicker (Terry Yiu)
|
||||
- Added a search interface to the settings screen (SanjaySiddharth)
|
||||
- Added view introducing users to Zaps (ericholguin)
|
||||
- Added new wallet view with balance and transactions list (ericholguin)
|
||||
- Added copy technical info button to user visible errors, so that users can more easily share errors with developers (Daniel D’Aquino)
|
||||
- Add dismiss button to wallet high balance reminders (Daniel D’Aquino)
|
||||
- Zap receiver information now included for outgoing zaps (Daniel D’Aquino)
|
||||
- Added inline note rendering of invoices to pull up wallet selector sheet (Terry Yiu)
|
||||
- Added route to profile page from wallet tx list (ericholguin)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Added additional information on top of blurred images (SanjaySiddharth)
|
||||
- Improved robustness of relay list handling (Daniel D’Aquino)
|
||||
- Updated image cache for better stability (Daniel D’Aquino)
|
||||
- Improved integration with Nostr Wallet Connect wallets (ericholguin)
|
||||
- Added relay connectivity information to NWC settings (Daniel D’Aquino)
|
||||
- Improved handling around NWC responses (Daniel D’Aquino)
|
||||
- Added more human visible errors on NWC wallets to aid with troubleshooting (Daniel D’Aquino)
|
||||
- Re-enabled note zaps as permitted by the new App Store guidelines (Daniel D’Aquino)
|
||||
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Hide future notes from timeline (Terry Yiu)
|
||||
- Fixed issue where profiles with a NIP-65 relay list would not display on Damus (Daniel D’Aquino)
|
||||
- Fix quote notes to include missing q tag (Terry Yiu)
|
||||
- Fixed issue where the side menu would close when copying the npub (SanjaySiddharth)
|
||||
- Fixed issue where cached images would be backed up to iCloud (Daniel D’Aquino)
|
||||
- Optimized classify_url function (Terry Yiu)
|
||||
- Fixed note rendering for those that contain previewable items or leading and trailing whitespaces (Terry Yiu)
|
||||
- Fixed issue where some videos would become unplayable after some time using the app (Daniel D’Aquino)
|
||||
|
||||
|
||||
[1.14]: https://github.com/damus-io/damus/releases/tag/v1.14
|
||||
|
||||
|
||||
## [1.13.1] - 2025-03-21
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed an issue where threads would not load properly (Daniel D’Aquino)
|
||||
|
||||
|
||||
[1.13.1]: https://github.com/damus-io/damus/releases/tag/v1.13.1
|
||||
|
||||
|
||||
## [1.13] - 2025-03-14
|
||||
|
||||
### Added
|
||||
|
||||
- Added local persistence of note drafts (Daniel D’Aquino)
|
||||
- Added user-friendly error view for errors around the app that would not fit in other places (Daniel D’Aquino)
|
||||
- Coinos connection button in Wallet view (ericholguin)
|
||||
- Added Alby Go to mobile wallets selection menu (Tomek ⚡ K)
|
||||
- Minor accessibility improvements around picture editing and onboarding (Daniel D’Aquino)
|
||||
- Profile image cropping tools (Daniel D’Aquino)
|
||||
- Added Conversations tab to profiles (Terry Yiu)
|
||||
- Added profile pictures to push notifications (William Casarin)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Don't show reposts for the same note more than once in your home feed (William Casarin)
|
||||
- Improved profile image bandwidth optimization (Daniel D’Aquino)
|
||||
- Improved reliability of picture selector (Daniel D’Aquino)
|
||||
- Changed spaces to newlines in new posts to provide cleaner separation between text, uploaded media, and quoted notes (Terry Yiu)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue where some push notifications would not open in the app and leave users confused (Daniel D’Aquino)
|
||||
- Fixed issue where app would need a restart for new NWC wallets to work (Daniel D’Aquino)
|
||||
- Fixed overly sensitive horizontal swipe on thread chat view (Daniel D’Aquino)
|
||||
- Trim whitespaces from Lightning addresses (Terry Yiu)
|
||||
- Fixed translation export script by upgrading nostr-sdk-swift dependency to support Mac Catalyst (Terry Yiu)
|
||||
- Fixed issue where users continue to receive push notifications after logout (Daniel D’Aquino)
|
||||
- Fixed an issue where events on a thread view would occasionally disappear (Daniel D’Aquino)
|
||||
- Improved robustness of the URL handler (Daniel D’Aquino)
|
||||
- Translate notes even if they are in a preferred language but not the current language as that is what users expect (Terry Yiu)
|
||||
- Cancel ongoing uploading operations after the user cancels the post (Swift Coder)
|
||||
- Fixed link and photo sharing support on macOS (Swift Coder)
|
||||
- Fix bug where profile view was showing more than just the notes and replies on the notes / notes & replies tabs (Terry Yiu)
|
||||
- Fixed reposts banner to be localizable (Terry Yiu)
|
||||
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed language filtering from Universe feed because language detection can be inaccurate (Terry Yiu)
|
||||
- Removed mystery tabs meant to fix tab switching bug that no longer exists (Terry Yiu)
|
||||
|
||||
|
||||
|
||||
[1.13](https://github.com/damus-io/damus/releases/tag/v1.13): https://github.com/damus-io/damus/releases/tag/v1.13
|
||||
|
||||
|
||||
## [1.12.3] - 2025-02-06
|
||||
|
||||
### Added
|
||||
|
||||
- Purple members who have been active for more than a year now get a special badge (Daniel D’Aquino)
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved clarity of the mute button to indicate it can be used for blocking a user (Daniel D’Aquino)
|
||||
- Made the microphone access request message more clear to users (Daniel D’Aquino)
|
||||
|
||||
[v1.12.3]: https://github.com/damus-io/damus/releases/tag/v1.12.3
|
||||
|
||||
|
||||
## [1.12](https://github.com/damus-io/damus/releases/tag/v1.12) - 2024-12-20
|
||||
|
||||
### Added
|
||||
|
||||
- Render Gif and video files while composing posts (Swift Coder)
|
||||
- Add profile info text in stretchable banner with follow button (Swift Coder)
|
||||
- Paste Gif image similar to jpeg and png files (Swift Coder)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved UX around the label for searching words (Daniel D’Aquino)
|
||||
- Improved accessibility support on some elements (Daniel D’Aquino)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue where the "next" button would appear hidden and hard to click on the create account view (Daniel D’Aquino)
|
||||
- Fix non scrollable wallet screen (Swift Coder)
|
||||
- Fixed suggested users category titles to be localizable (Terry Yiu)
|
||||
- Fixed GradientFollowButton to have consistent width and autoscale text limited to 1 line (Terry Yiu)
|
||||
- Fixed right-to-left localization issues (Terry Yiu)
|
||||
- Fixed AddMuteItemView to trim leading and trailing whitespaces from mute text and disallow adding text with only whitespaces (Terry Yiu)
|
||||
- Fixed SideMenuView text to autoscale and limit to 1 line (Terry Yiu)
|
||||
- Fixed an issue where a profile would need to be input twice in the search to be found (Daniel D’Aquino)
|
||||
- Fixed non-breaking spaces in localized strings (Terry Yiu)
|
||||
- Fixed localization issue on Add mute item button (Terry Yiu)
|
||||
- Replace non-breaking spaces with regular spaces as Apple's NSLocalizedString macro does not seem to work with it (Terry Yiu)
|
||||
- Fixed localization issues in RelayConfigView (Terry Yiu)
|
||||
- Fix duplicate uploads (Swift Coder)
|
||||
- Remove duplicate pubkey from Follow Suggestion list (Swift Coder)
|
||||
- Fix Page control indicator (Swift Coder)
|
||||
- Fix damus sharing issues (Swift Coder)
|
||||
- Fixed issue where banner edit button is unclickable (Daniel D’Aquino)
|
||||
- Handle empty notification pages by displaying suitable text (Swift Coder)
|
||||
|
||||
[v1.12](https://github.com/damus-io/damus/releases/tag/v1.12): [https://github.com/damus-io/damus/releases/tag/v1.12]
|
||||
|
||||
|
||||
## [v1.11(10)](https://github.com/damus-io/damus/releases/tag/v1.11-10) - 2024-11-18
|
||||
|
||||
### Added
|
||||
|
||||
- Add Damus Share Feature (Swift)
|
||||
- Added new easy to use video controls for full screen video (Daniel D’Aquino)
|
||||
- Add Edit, Share, and Tap-gesture in Profile pic image viewer (Swift Coder)
|
||||
- Disappearing header, tabbar, and post button on scroll (ericholguin)
|
||||
- Add Apple translation popovers for notes for iOS 17.4+ and macOS 14.4+ (Terry Yiu)
|
||||
- Added NDB search functionality to the universe view (ericholguin)
|
||||
- Added mute button to ProfileActionSheet (chungwwei)
|
||||
- Added mute action to selected text menu (ericholguin)
|
||||
- Added support for pasting images from the clipboard to the post composer (Swift Coder)
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved image carousel image fill behavior (Daniel D’Aquino)
|
||||
- Improved video syncing and bandwidth usage when switching between timeline video and full screen mode (Daniel D’Aquino)
|
||||
- Swipe to dismiss on full screen carousel now shows an opacity effect for improved UX (Daniel D’Aquino)
|
||||
- Removed event contents from full screen media carousel for cleaner view (Daniel D’Aquino)
|
||||
- Add share button for images on full screen image carousel view (Swift)
|
||||
- Changed boldness of font in side menu labels. (ericholguin)
|
||||
- Changed search notes button with searched keyword (ericholguin)
|
||||
- Changed opacity of tabbar and post button (ericholguin)
|
||||
- Allow multiple images to be uploaded at the same time (swiftcoder) (William Casarin)
|
||||
- Changed side menu design (ericholguin)
|
||||
- Truncate fulltext search results (William Casarin)
|
||||
- Expanded profile search results to 128 (William Casarin)
|
||||
- Expand nostrdb text search results to 128 items (William Casarin)
|
||||
- Use LazyVStack in text search results (William Casarin)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed missing tab bar on navigation (Swift Coder)
|
||||
- Fixed some issues where QR code would not work, and improved UX (Daniel D’Aquino)
|
||||
- Fixed iOS 18 gesture issues that would take user to the thread view when clicking on a video or unmuting it (Daniel D’Aquino)
|
||||
- Fixed several issues that would cause video to automatically play or pause incorrectly (Daniel D’Aquino)
|
||||
- Fixed issue where full screen video would disappear when going to landscape mode (Daniel D’Aquino)
|
||||
- Fixed portrait video size on full screen carousel (Daniel D’Aquino)
|
||||
- Fix avatar image on qrcode view (Swift Coder)
|
||||
- Fix banner image upload (Swift Coder)
|
||||
- Fix dismiss button visibility (Swift Coder)
|
||||
- Fix quote repost counting (William Casarin)
|
||||
- Fixed overlapping text in Universe View (ericholguin)
|
||||
- Fixed localization issues and exported strings (Terry Yiu)
|
||||
- Fix sensitive long-press gesture on event chat bubble in iOS 18 (Daniel D’Aquino)
|
||||
- Fixed bottom padding for tabbar (ericholguin)
|
||||
- Fixed localization build failures (Terry Yiu)
|
||||
- Fixed back nav button placement in profile edit view (ericholguin)
|
||||
- Friend profiles will now more likely show up in profile search (William Casarin)
|
||||
- Fix broken QR code scanner and fix landscape mode (Terry Yiu)
|
||||
|
||||
[1.11(10)](https://github.com/damus-io/damus/releases/tag/v1.11-10): https://github.com/damus-io/damus/releases/tag/v1.11-10
|
||||
|
||||
## [1.10.1] - 2024-09-22
|
||||
|
||||
### Added
|
||||
|
||||
- Push notification support (Daniel D’Aquino)
|
||||
- Added profile edit safe guards (Eric Holguin)
|
||||
- Tor relay icon (ericholguin)
|
||||
- Add highlighter for web pages (Daniel D’Aquino)
|
||||
- Add support for adding comments when creating a highlight (Daniel D’Aquino)
|
||||
- Add support for rendering highlights with comments (Daniel D’Aquino)
|
||||
- Ability to create highlights (ericholguin)
|
||||
- Highlights (NIP-84) (ericholguin)
|
||||
- Revamp emoji picker to be less error-prone and add search, frequently used, and multiple skin tone support capabilities (Terry Yiu)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Improve notification view filtering UX (Daniel D’Aquino)
|
||||
- Improve visibility of friends filter button (Daniel D’Aquino)
|
||||
- Changed the default banner from ostriches to damoose (Eric Holguin)
|
||||
- Changed image and banner url text fields to new sheet view (Eric Holguin)
|
||||
- Onboarding design (ericholguin)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix items that became unclickable on iOS 18 (Daniel D’Aquino)
|
||||
- Fix many reconnection issues (William Casarin)
|
||||
- Fixed issue where theme would be changed to black and can't be switched back on iOS 18 (cr0bar)
|
||||
- Fixed some scenarios where the contact list would never be saved locally and cause issues when switching relays. (Daniel D’Aquino)
|
||||
- Fix albyhub zaps not appearing (William Casarin)
|
||||
- Fix inadvertent escape from mention suggestion menu when typing a space character (Daniel D’Aquino)
|
||||
- Fix profile view toolbar alignment bug in iOS 18 (Terry Yiu)
|
||||
- Create Account model now uses correct metadata (ericholguin)
|
||||
- Restore localization for custom tabs (William Casarin)
|
||||
- Fix iOS 18 reflection runtime error for custom picker (William Casarin)
|
||||
|
||||
|
||||
[1.10.1]: https://github.com/damus-io/damus/releases/tag/v1.10.1
|
||||
|
||||
|
||||
## [1.9.1 (4)] - 2024-08-13
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix crash when viewing notes with invalid image dimension metadata (Daniel D’Aquino)
|
||||
|
||||
[1.9.1 (4)]: https://github.com/damus-io/damus/releases/tag/v1.9.1-4
|
||||
|
||||
|
||||
## [1.9 (14)] - 2024-07-14
|
||||
|
||||
### Added
|
||||
|
||||
- Completely new threads experience that is easier and more pleasant to use (Daniel D’Aquino)
|
||||
- Add emoji search to emoji picker (Terry Yiu)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Added first aid contact damus support email (alltheseas)
|
||||
- Disable mutiny wallet button (William Casarin)
|
||||
- Make friends show up first when searching for profiles (Terry Yiu)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix crash on profile page when there are profile updates (William Casarin)
|
||||
- Fix crash when adding duplicate mute items (William Casarin)
|
||||
- Fix pretty bad crash when building flatbuffer profiles (William Casarin)
|
||||
- Fix reactions view to not show reactions from replies on parent note (Terry Yiu)
|
||||
- Fix missing Mute button in profile view menu (Terry Yiu)
|
||||
- Fixed wallet not disconnecting when a user logs out (ericholguin)
|
||||
- Fix stale feed issue when follow list is too big (Daniel D’Aquino)
|
||||
|
||||
[1.9 (14)]: https://github.com/damus-io/damus/releases/tag/v1.9-14
|
||||
|
||||
## [1.8] - 2024-05-11
|
||||
|
||||
### Added
|
||||
|
||||
- Added nip10 marker replies (William Casarin)
|
||||
- Add marker nip10 support when reading notes (William Casarin)
|
||||
- Added title image and tags to longform events (ericholguin)
|
||||
- Add First Aid solution for users who do not have a contact list created for their account (Daniel D’Aquino)
|
||||
- Relay fees metadata (ericholguin)
|
||||
- Added callbackuri for a better ux when connecting mutiny wallet nwc (ericholguin)
|
||||
- Add event content preview to the full screen carousel (Daniel D’Aquino)
|
||||
- Show list of quoted reposts in threads (William Casarin)
|
||||
- Proxy Tags are now viewable on Selected Events (ericholguin)
|
||||
- Connect to Mutiny Wallet Button (ericholguin)
|
||||
- Add ability to mute words, add new mutelist interface (Charlie) (William Casarin)
|
||||
- Add ability to mute hashtag from SearchView (Charlie Fish)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Change reactions to use a native looking emoji picker (Terry Yiu)
|
||||
- Relay detail design (ericholguin)
|
||||
- Updated Zeus logo (ericholguin)
|
||||
- Improve UX around video playback (Daniel D’Aquino)
|
||||
- Moved paste nwc button to main wallet view (ericholguin)
|
||||
- Errors with an NWC will show as an alert (ericholguin)
|
||||
- Relay config view user interface (ericholguin)
|
||||
- Always strip GPS data from images (kernelkind)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix thread bug where a quote isn't picked up as a reply (William Casarin)
|
||||
- Fixed threads not loading sometimes (William Casarin)
|
||||
- Fixed issue where some replies were including the q tag (William Casarin)
|
||||
- Fixed issue where timeline was scrolling when it isn't supposed to (William Casarin)
|
||||
- Fix issue where bootstrap relays would inadvertently be added to the user's list on connectivity issues (Daniel D’Aquino)
|
||||
- Fix broken GIF uploads (Daniel D’Aquino)
|
||||
- Fix ghost notifications caused by Purple impending expiration notifications (Daniel D’Aquino)
|
||||
- Improve reliability of contact list creation during onboarding (Daniel D’Aquino)
|
||||
- Fix emoji reactions being cut off (ericholguin)
|
||||
- Fix image indicators to limit number of dots to not spill screen beyond visible margins (ericholguin)
|
||||
- Fix bug that would cause connection issues with relays defined with a trailing slash URL, and an inability to delete them. (Daniel D’Aquino)
|
||||
- Issue where NWC Scanner view would not dismiss after a failed scan/paste (ericholguin)
|
||||
|
||||
|
||||
|
||||
[1.8]: https://github.com/damus-io/damus/releases/tag/v1.8
|
||||
|
||||
## [1.7-rc2] - 2024-02-28
|
||||
|
||||
### Added
|
||||
|
||||
- Add support for Apple In-App purchases (Daniel D’Aquino)
|
||||
- Notification reminders for Damus Purple impending expiration (Daniel D’Aquino)
|
||||
- Damus Purple membership! (William Casarin)
|
||||
- Fixed minor spacing and padding issues in onboarding views (ericholguin)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Disable inline text suggestions on 17.0 as they interfere with mention generation (William Casarin)
|
||||
- EULA is not shown by default (ericholguin)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix welcome screen not showing if the user enters the app directly after a successful checkout without going through the link (Daniel D’Aquino)
|
||||
- Fix profile not updating bug (William Casarin)
|
||||
- Fix nostrscripts not loading (William Casarin)
|
||||
- Fix crash when accessing cached purple accounts (William Casarin)
|
||||
- Hide member signup date on reposts (kernelkind)
|
||||
- Fixed previews not rendering (ericholguin)
|
||||
- Fix load media formatting on small screens (kernelkind)
|
||||
- Fix shared nevents that are too long (kernelkind)
|
||||
- Fix many nostrdb transaction related crashes (William Casarin)
|
||||
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed copying public key action (ericholguin)
|
||||
|
||||
|
||||
|
||||
[1.7-rc2]: https://github.com/damus-io/damus/releases/tag/v1.7-rc2
|
||||
|
||||
## [1.7-2] - 2024-01-24
|
||||
|
||||
### Added
|
||||
|
||||
- New fulltext search engine (William Casarin)
|
||||
|
||||
- Add "Always show onboarding suggestions" developer setting (Daniel D’Aquino)
|
||||
- Add NIP-42 relay auth support (Charlie Fish)
|
||||
- Add ability to hide suggested hashtags (ericholguin)
|
||||
- Add ability to mute hashtag from SearchView (Charlie Fish)
|
||||
- Add ability to preview media taken with camera (Suhail Saqan)
|
||||
- Add ability to search for naddr, nprofiles, nevents (kernelkind)
|
||||
- Add experimental push notification support (Daniel D’Aquino)
|
||||
- Add naddr link support (kernelkind)
|
||||
- Add regional relay recommendations to Relay configuration view (currently for Japanese users only) (Daniel D’Aquino)
|
||||
- Add regional relays for Germany (Daniel D’Aquino)
|
||||
- Add regional relays for Thailand (Daniel D’Aquino)
|
||||
- Added a custom camera view (Suhail Saqan)
|
||||
- Always convert damus.io links to inline mentions (William Casarin)
|
||||
- Unfurl profile name on remote push notifications (Daniel D’Aquino)
|
||||
- Zap notification support for push notifications (Daniel D’Aquino)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Generate nprofile/nevent links in share menus (kernelkind)
|
||||
- Improve push notification support to match local notification support (Daniel D’Aquino)
|
||||
- Move mute thread in menu so it's not clicked by accident (alltheseas)
|
||||
- Prioritize friends when autocompleting (Charlie Fish)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Add workaround to fix note language recognition and reduce wasteful translation requests (Terry Yiu)
|
||||
- Allow mentioning users with punctuation characters in their names (kernelkind)
|
||||
- Fix broken mentions when there is text is directly after (kernelkind)
|
||||
- Fix crash on very large notes (Daniel D’Aquino)
|
||||
- Fix crash when logging out and switching accounts (William Casarin)
|
||||
- Fix duplicate notes getting written to nostrdb (William Casarin)
|
||||
- Fix issue where adding relays might not work on corrupted contact lists (Charlie Fish)
|
||||
- Fix onboarding post view not being dismissed under certain conditions (Daniel D’Aquino)
|
||||
- Fix performance issue with gifs (William Casarin)
|
||||
- Fix persistent local notifications even after logout (William Casarin)
|
||||
- Fixed bug where sometimes notes from other profiles appear on profile pages (Charlie Fish)
|
||||
- Remove extra space at the end of DM messages (kernelkind)
|
||||
- Save current viewed image index when switching to fullscreen (kernelkind)
|
||||
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed old nsec key warning, nsec automatically convert to npub when posting (kernelkind)
|
||||
|
||||
|
||||
|
||||
[1.7-2]: https://github.com/damus-io/damus/releases/tag/v1.7-2
|
||||
## [1.6-25] - 2023-10-31
|
||||
|
||||
### Added
|
||||
|
||||
- Tap to dismiss keyboard on user status view (ericholguin)
|
||||
- Add setting that allows users to optionally disable the new profile action sheet feature (Daniel D’Aquino)
|
||||
- Add follow button to profile action sheet (Daniel D’Aquino)
|
||||
- Added reaction counters to nostrdb (William Casarin)
|
||||
- Record when profile is last fetched in nostrdb (William Casarin)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Automatically load extra regional Japanese relays during account creation if user's region is set to Japan. (Daniel D’Aquino)
|
||||
- Updated customize zap view (ericholguin)
|
||||
- Users are now notified when you quote repost them (William Casarin)
|
||||
- Save bandwidth by only fetching new profiles after a certain amount of time (William Casarin)
|
||||
- Zap button on profile action sheet now zaps with a single click, while a long press brings custom zap view (Daniel D’Aquino)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Use white font color in qrcode view (ericholguin)
|
||||
- Fixed an issue where zapping would silently fail on default settings if the user does not have a lightning wallet preinstalled on their device. (Daniel D’Aquino)
|
||||
|
||||
|
||||
[1.6-25]: https://github.com/damus-io/damus/releases/tag/v1.6-25
|
||||
## [1.6-24] - 2023-10-22 - AppStore Rejection Cope
|
||||
|
||||
### Added
|
||||
|
||||
- Improve discoverability of profile zaps with zappability badges and profile action sheets (Daniel D’Aquino)
|
||||
- Add suggested hashtags to universe view (Daniel D’Aquino)
|
||||
- Suggest first post during onboarding (Daniel D’Aquino)
|
||||
- Add expiry date for images in cache to be auto-deleted after a preset time to save space on storage (Daniel D’Aquino)
|
||||
- Add QR scan nsec logins. (Jericho Hasselbush)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved status view design (ericholguin)
|
||||
- Improve clear cache functionality (Daniel D’Aquino)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Reduce size of event menu hitbox (William Casarin)
|
||||
- Do not show DMs from muted users (Daniel D’Aquino)
|
||||
- Add more spacing between display name and username, and prefix username with `@` character (Daniel D’Aquino)
|
||||
- Broadcast quoted notes when posting a note with quotes (Daniel D’Aquino)
|
||||
|
||||
|
||||
[1.6-24]: https://github.com/damus-io/damus/releases/tag/v1.6-24
|
||||
|
||||
## [1.6-23] - 2023-10-06 - Appstore Release
|
||||
|
||||
### Added
|
||||
|
||||
- Added merch store button to sidebar menu (Daniel D’Aquino)
|
||||
|
||||
### Changed
|
||||
|
||||
- Damus icon now opens sidebar (Daniel D’Aquino)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Stop tab buttons from causing the root view to scroll to the top unless user is coming from another tab or already at the root view (Daniel D’Aquino)
|
||||
- Fix profiles not updating (William Casarin)
|
||||
- Fix issue where relays with trailing slashes cannot be removed (#1531) (Daniel D’Aquino)
|
||||
|
||||
|
||||
[1.6-23]: https://github.com/damus-io/damus/releases/tag/v1.6-23
|
||||
|
||||
## [1.6-20] - 2023-10-04
|
||||
|
||||
### Changed
|
||||
|
||||
- Improve UX around clearing cache (Daniel D’Aquino)
|
||||
- Show muted thread replies at the bottom of the thread view (#1522) (Daniel D’Aquino)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix situations where the note composer cursor gets stuck in one place after tagging a user (Daniel D’Aquino)
|
||||
- Fix some note composer issues, such as when copying/pasting larger text, and make the post composer more robust. (Daniel D’Aquino)
|
||||
- Apply filters to hashtag search timeline view (Daniel D’Aquino)
|
||||
- Hide quoted or reposted notes from people whom the user has muted. (#1216) (Daniel D’Aquino)
|
||||
- Fix profile not updating (William Casarin)
|
||||
- Fix small graphical toolbar bug when scrolling profiles (Daniel D’Aquino)
|
||||
- Fix localization issues and export strings for translation (Terry Yiu)
|
||||
|
||||
|
||||
[1.6-20]: https://github.com/damus-io/damus/releases/tag/v1.6-20
|
||||
|
||||
## [1.6-18] - 2023-09-21
|
||||
|
||||
### Added
|
||||
|
||||
- Add followed hashtags to your following list (Daniel D’Aquino)
|
||||
- Add "Do not show #nsfw tagged posts" setting (Daniel D’Aquino)
|
||||
- Hold tap to preview status URL (Jericho Hasselbush)
|
||||
- Finnish translations (etrikaj)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Switch to nostrdb for @'s and user search (William Casarin)
|
||||
- Use nostrdb for profiles (William Casarin)
|
||||
- Updated relay view (ericholguin)
|
||||
- Increase size of the hitbox on note ellipsis button (Daniel D’Aquino)
|
||||
- Make carousel tab dots tappable (Bryan Montz)
|
||||
- Move the "Follow you" badge into the profile header (Grimless)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix text composer wrapping issue when mentioning npub (Daniel D’Aquino)
|
||||
- Make blurred videos viewable by allowing blur to disappear once tapped (Daniel D’Aquino)
|
||||
- Fix parsing issue with NIP-47 compliant NWC urls without double-slashes (Daniel D’Aquino)
|
||||
- Fix padding of username next to pfp on some views (William Casarin)
|
||||
- Fixes issue where username with multiple emojis would place cursor in strange position. (Jericho Hasselbush)
|
||||
- Fixed audio in video playing twice (Bryan Montz)
|
||||
- Fix crash when long pressing custom reactions (William Casarin)
|
||||
- Fix random crashom due to old profile database (William Casarin)
|
||||
|
||||
[1.6-18]: https://github.com/damus-io/damus/releases/tag/v1.6-18
|
||||
|
||||
## [1.6-17] - 2023-08-23
|
||||
|
||||
### Added
|
||||
|
||||
- Add support for status URLs (William Casarin)
|
||||
- Click music statuses to display in spotify (William Casarin)
|
||||
- Add settings for disabling user statuses (William Casarin)
|
||||
|
||||
### Changed
|
||||
|
||||
- clear statuses if they only contain whitespace (William Casarin)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix long status lines (William Casarin)
|
||||
- Fix status events not expiring locally (William Casarin)
|
||||
|
||||
[1.6-17]: https://github.com/damus-io/damus/releases/tag/v1.6-17
|
||||
|
||||
## [1.6-16] - 2023-08-23
|
||||
|
||||
### Added
|
||||
|
||||
- Added live music statuses (William Casarin)
|
||||
- Added generic user statuses (William Casarin)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Avoid notification for zaps from muted profiles (tappu75e@duck.com)
|
||||
- Fix text editing issues on characters added right after mention link (Daniel D’Aquino)
|
||||
- Mute hellthreads everywhere (William Casarin)
|
||||
|
||||
|
||||
[1.6-16]: https://github.com/damus-io/damus/releases/tag/v1.6-16
|
||||
|
||||
## [1.6-13] - 2023-08-18
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix bug where it would sometimes show -1 in replies (tappu75e@duck.com)
|
||||
- Fix images and links occasionally appearing with escaped slashes (Daniel D‘Aquino)
|
||||
- Fixed nostrscript not working on smaller phones (William Casarin)
|
||||
- Fix zaps sometimes not appearing (William Casarin)
|
||||
- Fixed issue where reposts would sometimes repost the wrong thing (William Casarin)
|
||||
- Fixed issue where sometimes there would be empty entries on your profile (William Casarin)
|
||||
|
||||
[1.6-13]: https://github.com/damus-io/damus/releases/tag/v1.6-13
|
||||
|
||||
|
||||
## [1.6-11]: "Bugfix Sunday" - 2023-08-07
|
||||
|
||||
### Added
|
||||
|
||||
- Add close button to custom reactions (Suhail Saqan)
|
||||
- Add ability to change order of custom reactions (Suhail Saqan)
|
||||
- Adjustable font size (William Casarin)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Show renotes in Notes timeline (William Casarin)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Ensure the person you're replying to is the first entry in the reply description (William Casarin)
|
||||
- Don't cutoff text in notifications (William Casarin)
|
||||
- Fix wikipedia url detection with parenthesis (William Casarin)
|
||||
- Fixed old notifications always appearing on first start (William Casarin)
|
||||
- Fix issue with slashes on relay urls causing relay connection problems (William Casarin)
|
||||
- Fix rare crash triggered by local notifications (William Casarin)
|
||||
- Fix crash when long-pressing reactions (William Casarin)
|
||||
- Fixed nostr reporting decoding (William Casarin)
|
||||
- Dismiss qr screen on scan (Suhail Saqan)
|
||||
- Show QRCameraView regardless of same user (Suhail Saqan)
|
||||
- Fix wiggle when long press reactions (Suhail Saqan)
|
||||
- Fix reaction button breaking scrolling (Suhail Saqan)
|
||||
- Fix crash when muting threads (Bryan Montz)
|
||||
|
||||
|
||||
[1.6-11]: https://github.com/damus-io/damus/releases/tag/v1.6-11
|
||||
|
||||
## [1.6-8]: "nostrdb prep" 2023-08-03
|
||||
|
||||
### Added
|
||||
|
||||
- Suggested Users to Follow (Joel Klabo)
|
||||
- Add support for multiple reactions (Suhail Saqan)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved memory usage and performance when processing events (William Casarin)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed disappearing text on iOS17 (cr0bar)
|
||||
- Fix UTF support for hashtags (Daniel D‘Aquino)
|
||||
- Fix compilation error on test target in UserSearchCacheTests (Daniel D‘Aquino)
|
||||
- Fix nav crashing and buggyness (William Casarin)
|
||||
- Allow relay logs to be opened in dev mode even if relay (Daniel D'Aquino)
|
||||
- endless connection attempt loop after user removes relay (Bryan Montz)
|
||||
|
||||
|
||||
[1.6-8]: https://github.com/damus-io/damus/releases/tag/v1.6-8
|
||||
|
||||
## 1.6 (7): "Less bad" - 2023-07-16
|
||||
|
||||
### Added
|
||||
|
||||
- Show nostr address username and support abbreviated _ usernames (William Casarin)
|
||||
- Re-add nip05 badges to profiles (William Casarin)
|
||||
- Add space when tagging users in posts if needed (William Casarin)
|
||||
- Added padding under word count on longform account (William Casarin)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Don't spam lnurls when validating zaps (William Casarin)
|
||||
- Eliminate nostr address validation bandwidth on startup (William Casarin)
|
||||
- Allow user to login to deleted profile (William Casarin)
|
||||
- Fix issue where typing cc@bob would produce brokenb ccnostr:bob mention (William Casarin)
|
||||
|
||||
|
||||
|
||||
[1.6-7]: https://github.com/damus-io/damus/releases/tag/v1.6-7
|
||||
|
||||
## [1.6-6] - 2023-07-16
|
||||
|
||||
### Added
|
||||
|
||||
- New markdown renderer (William Casarin)
|
||||
- Added feedback when user adds a relay that is already on the list (Daniel D'Aquino)
|
||||
|
||||
### Changed
|
||||
|
||||
- Hide nsec when logging in (cr0bar)
|
||||
- Remove nip05 on events (William Casarin)
|
||||
- Rename NIP05 to "nostr address" (William Casarin)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue where hashtags were leaking in DMs (William Casarin)
|
||||
- Fix issue with emojis next to hashtags and urls (William Casarin)
|
||||
- relay detail view is not immediately available after adding new relay (Bryan Montz)
|
||||
- Fix nostr:nostr:... bugs (William Casarin)
|
||||
|
||||
|
||||
[1.6-6]: https://github.com/damus-io/damus/releases/tag/v1.6-6
|
||||
|
||||
## [1.6-4] - 2023-07-13
|
||||
|
||||
### Added
|
||||
|
||||
- Add the ability to follow hashtags (William Casarin)
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove note size restriction for longform events (William Casarin)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Hide users and hashtags from home timeline when you unfollow (William Casarin)
|
||||
- Fixed a bug where following a user might not work due to poor connectivity (William Casarin)
|
||||
- Icon color for developer mode setting is incorrect in low-light mode (Bryan Montz)
|
||||
- Fixed nav bar color on login, eula, and account creation (ericholguin)
|
||||
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove following Damus Will by default (William Casarin)
|
||||
|
||||
[1.6-4]: https://github.com/damus-io/damus/releases/tag/v1.6-4
|
||||
|
||||
## [1.6-3] - 2023-07-11
|
||||
|
||||
### Changed
|
||||
|
||||
- Start at top when reading longform events (William Casarin)
|
||||
- Allow reposting and quote reposting multiple times (William Casarin)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Show longform previews in notifications instead of the entire post (William Casarin)
|
||||
- Fix padding on longform events (William Casarin)
|
||||
- Fix action bar appearing on quoted longform previews (William Casarin)
|
||||
|
||||
|
||||
[1.6-3]: https://github.com/damus-io/damus/releases/tag/v1.6-3
|
||||
## [1.6-2] - 2023-07-11
|
||||
|
||||
### Added
|
||||
|
||||
- Add support for multilingual hashtags (cr0bar)
|
||||
- Add r tag when mentioning a url (William Casarin)
|
||||
- Add initial longform note support (William Casarin)
|
||||
- Enable banner image editing (Joel Klabo)
|
||||
- Add relay log in developer mode (Bryan Montz)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix lag when creating large posts (William Casarin)
|
||||
- Fix npub mentions failing to parse in some cases (William Casarin)
|
||||
- Fix PostView initial string to skip mentioning self when on own profile (Terry Yiu)
|
||||
- Fix freezing bug when tapping Developer settings menu (Terry Yiu)
|
||||
- Fix potential fake profile zap attacks (William Casarin)
|
||||
- Fix issue where malicious zappers can send fake zaps to another user's posts (William Casarin)
|
||||
- Fix profile post button mentions (cr0bar)
|
||||
- Fix icons on settings view (cr0bar)
|
||||
- Fix Invalid Zap bug in reposts (William Casarin)
|
||||
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove old @ and & hex key mentions (William Casarin)
|
||||
|
||||
|
||||
[1.6-2]: https://github.com/damus-io/damus/releases/tag/v1.6-2
|
||||
## [1.6] - 2023-07-04
|
||||
|
||||
### Added
|
||||
|
||||
- Speed up user search (Terry Yiu)
|
||||
- Add post button to profile pages (William Casarin)
|
||||
- Add post button when logged in with private key and on own profile view (Terry Yiu)
|
||||
|
||||
### Changed
|
||||
|
||||
- Drop iOS15 support (Scott Penrose)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Load more content on profile view (William Casarin)
|
||||
- Fix reports to conform to NIP-56 (Terry Yiu)
|
||||
- Fix profile navigation bugs from muted users list and relay list views (Terry Yiu)
|
||||
- Fix navigation to translation settings view (Terry Yiu)
|
||||
- Fixed all navigation issues (Scott Penrose)
|
||||
- Disable post button when media upload in progress (Terry Yiu)
|
||||
- Fix taps on mentions in note drafts to not redirect to other Nostr clients (Terry Yiu)
|
||||
- Fix missing profile zap notification text (Terry Yiu)
|
||||
|
||||
|
||||
[1.6]: https://github.com/damus-io/damus/releases/tag/v1.6
|
||||
|
||||
## [1.5-5] - 2023-06-24
|
||||
|
||||
### Fixed
|
||||
|
||||
- Remove note zaps to fit apples appstore guidelines
|
||||
- Fix zap sheet popping (William Casarin)
|
||||
- Fix CustomizeZapView from randomly disappearing (William Casarin)
|
||||
- Fix "zapped your profile" strings to say "zapped you" (Terry Yiu)
|
||||
- Fix reconnect loop issues on iOS17 (William Casarin)
|
||||
- Fix some more thread jankiness (William Casarin)
|
||||
- Fix spelling of Nostr to use Titlecase instead of lowercase (Terry Yiu)
|
||||
- Rename all usages of the term Post as a noun to Note to conform to the Nostr spec (Terry Yiu)
|
||||
- Fix text cutoff on login with npub (gladiusKatana)
|
||||
- Fix hangs due to video player (William Casarin)
|
||||
|
||||
|
||||
[1.5-5]: https://github.com/damus-io/damus/releases/tag/v1.5-5
|
||||
|
||||
## [1.5-2] - 2023-05-30
|
||||
|
||||
### Added
|
||||
|
||||
- Add new full-bleed video player (William Casarin)
|
||||
- Add ability to show multiple posts per user in Universe (Ben Weeks)
|
||||
- Custom iconography added for other areas of the app. (Ben Weeks)
|
||||
- Custom iconography for the left navigation. (Ben Weeks)
|
||||
- Custom iconography for the tab buttons. (Ben Weeks)
|
||||
- Added dots under image carousel (Ben Weeks)
|
||||
- Add profile caching (Bryan Montz)
|
||||
- Add mention parsing and fine-grained text selection on description in ProfileView (Terry Yiu)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Redesign phase 1 (text, icons)
|
||||
- Updated UI to use custom font (Ben Weeks)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix side menu bug in landscape (OlegAba)
|
||||
- Use "Follow me on nostr" text when looking at someone else's QR code (Ben Weeks)
|
||||
- Fix issue where cursor dissapears when typing long message (gladiusKatana)
|
||||
- Attempt fix for randomly broken animated gifs (William Casarin)
|
||||
- Fix cursor jumping when pressing return (gladius)
|
||||
- Fix side menu label size so that translations in longer languages fit without wrapping (Terry Yiu)
|
||||
- Fix reaction notification title to be consistent with ReactionView (Terry Yiu)
|
||||
- Fix nostr URL scheme to open properly even if there's already a different view open (Terry Yiu)
|
||||
- Fix crash related to preloading events (Bryan Montz)
|
||||
|
||||
|
||||
## v1.4.3 - 2023-05-08
|
||||
|
||||
### Added
|
||||
@@ -2134,3 +1229,4 @@
|
||||
|
||||
|
||||
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.usernotifications.communication</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.kernel.extended-virtual-addressing</key>
|
||||
<true/>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.damus</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)com.jb55.damus2</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.usernotifications.service</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,46 +0,0 @@
|
||||
//
|
||||
// NotificationExtensionState.swift
|
||||
// DamusNotificationService
|
||||
//
|
||||
// Created by Daniel D’Aquino on 2023-11-27.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
struct NotificationExtensionState: HeadlessDamusState {
|
||||
let ndb: Ndb
|
||||
let settings: UserSettingsStore
|
||||
let contacts: Contacts
|
||||
let mutelist_manager: MutelistManager
|
||||
let keypair: Keypair
|
||||
let profiles: Profiles
|
||||
let zaps: Zaps
|
||||
let lnurls: LNUrls
|
||||
|
||||
init?() {
|
||||
guard let ndb = Ndb(owns_db_file: false) else { return nil }
|
||||
self.ndb = ndb
|
||||
|
||||
guard let keypair = get_saved_keypair() else { return nil }
|
||||
|
||||
// dumb stuff needed for property wrappers
|
||||
UserSettingsStore.pubkey = keypair.pubkey
|
||||
self.settings = UserSettingsStore()
|
||||
|
||||
self.contacts = Contacts(our_pubkey: keypair.pubkey)
|
||||
self.mutelist_manager = MutelistManager(user_keypair: keypair)
|
||||
self.keypair = keypair
|
||||
self.profiles = Profiles(ndb: ndb)
|
||||
self.zaps = Zaps(our_pubkey: keypair.pubkey)
|
||||
self.lnurls = LNUrls()
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func add_zap(zap: Zapping) -> Bool {
|
||||
// store generic zap mapping
|
||||
self.zaps.add_zap(zap: zap)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
//
|
||||
// NotificationFormatter.swift
|
||||
// DamusNotificationService
|
||||
//
|
||||
// Created by Daniel D’Aquino on 2023-11-13.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UserNotifications
|
||||
|
||||
struct NotificationFormatter {
|
||||
static var shared = NotificationFormatter()
|
||||
|
||||
// MARK: - Formatting with NdbNote
|
||||
|
||||
func format_message(event: NdbNote) -> UNMutableNotificationContent? {
|
||||
let content = UNMutableNotificationContent()
|
||||
if let event_json_data = try? JSONEncoder().encode(event), // Must be encoded, as the notification completion handler requires this object to conform to `NSSecureCoding`
|
||||
let event_json_string = String(data: event_json_data, encoding: .utf8) {
|
||||
content.userInfo = [
|
||||
NDB_NOTE_JSON_USER_INFO_KEY: event_json_string
|
||||
]
|
||||
}
|
||||
switch event.known_kind {
|
||||
case .text:
|
||||
content.title = NSLocalizedString("Someone posted a note", comment: "Title label for push notification where someone posted a note")
|
||||
content.body = event.content
|
||||
break
|
||||
case .dm:
|
||||
content.title = NSLocalizedString("New message", comment: "Title label for push notifications where a direct message was sent to the user")
|
||||
content.body = NSLocalizedString("(Contents are encrypted)", comment: "Label on push notification indicating that the contents of the message are encrypted")
|
||||
break
|
||||
case .like:
|
||||
guard let reactionEmoji = to_reaction_emoji(ev: event) else {
|
||||
content.title = NSLocalizedString("Someone reacted to your note", comment: "Generic title label for push notifications where someone reacted to the user's post")
|
||||
break
|
||||
}
|
||||
content.title = NSLocalizedString("New note reaction", comment: "Title label for push notifications where someone reacted to the user's post with a specific emoji")
|
||||
content.body = String(format: NSLocalizedString("Someone reacted to your note with %@", comment: "Body label for push notifications where someone reacted to the user's post with a specific emoji"), reactionEmoji)
|
||||
break
|
||||
case .zap:
|
||||
content.title = NSLocalizedString("Someone zapped you ⚡️", comment: "Title label for a push notification where someone zapped the user")
|
||||
break
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
// MARK: - Formatting with LocalNotification
|
||||
|
||||
func format_message(displayName: String, notify: LocalNotification) -> (content: UNMutableNotificationContent, identifier: String)? {
|
||||
let content = UNMutableNotificationContent()
|
||||
var title = ""
|
||||
var identifier = ""
|
||||
|
||||
switch notify.type {
|
||||
case .tagged:
|
||||
title = String(format: NSLocalizedString("Tagged by %@", comment: "Tagged by heading in local notification"), displayName)
|
||||
identifier = "myMentionNotification"
|
||||
case .mention:
|
||||
title = String(format: NSLocalizedString("Mentioned by %@", comment: "Mentioned by heading in local notification"), displayName)
|
||||
identifier = "myMentionNotification"
|
||||
case .repost:
|
||||
title = String(format: NSLocalizedString("Reposted by %@", comment: "Reposted by heading in local notification"), displayName)
|
||||
identifier = "myBoostNotification"
|
||||
case .like:
|
||||
title = String(format: NSLocalizedString("%@ reacted with %@", comment: "Reacted by heading in local notification"), displayName, to_reaction_emoji(ev: notify.event) ?? "")
|
||||
identifier = "myLikeNotification"
|
||||
case .dm:
|
||||
title = displayName
|
||||
identifier = "myDMNotification"
|
||||
case .zap, .profile_zap:
|
||||
// not handled here. Try `format_message(displayName: String, notify: LocalNotification, state: HeadlessDamusState) async -> (content: UNMutableNotificationContent, identifier: String)?`
|
||||
return nil
|
||||
case .reply:
|
||||
title = String(format: NSLocalizedString("%@ replied to your note", comment: "Heading for local notification indicating a new reply"), displayName)
|
||||
identifier = "myReplyNotification"
|
||||
}
|
||||
content.title = title
|
||||
content.body = notify.content
|
||||
content.sound = UNNotificationSound.default
|
||||
content.userInfo = notify.to_lossy().to_user_info()
|
||||
|
||||
return (content, identifier)
|
||||
}
|
||||
|
||||
func format_message(displayName: String, notify: LocalNotification, state: HeadlessDamusState) async -> (content: UNMutableNotificationContent, identifier: String)? {
|
||||
// Try sync method first and return if it works
|
||||
if let sync_formatted_message = self.format_message(displayName: displayName, notify: notify) {
|
||||
return sync_formatted_message
|
||||
}
|
||||
|
||||
// If it does not work, try async formatting methods
|
||||
let content = UNMutableNotificationContent()
|
||||
|
||||
switch notify.type {
|
||||
case .zap, .profile_zap:
|
||||
guard let zap = await get_zap(from: notify.event, state: state) else {
|
||||
Log.debug("format_message: async get_zap failed", for: .push_notifications)
|
||||
return nil
|
||||
}
|
||||
content.title = Self.zap_notification_title(zap)
|
||||
content.body = Self.zap_notification_body(profiles: state.profiles, zap: zap)
|
||||
content.sound = UNNotificationSound.default
|
||||
content.userInfo = LossyLocalNotification(type: .zap, mention: .init(nip19: .note(notify.event.id))).to_user_info()
|
||||
return (content, "myZapNotification")
|
||||
default:
|
||||
// The sync method should have taken care of this.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Formatting zap utility notifications
|
||||
|
||||
static func zap_notification_title(_ zap: Zap) -> String {
|
||||
if zap.private_request != nil {
|
||||
return NSLocalizedString("Private Zap", comment: "Title of notification when a private zap is received.")
|
||||
} else {
|
||||
return NSLocalizedString("Zap", comment: "Title of notification when a non-private zap is received.")
|
||||
}
|
||||
}
|
||||
|
||||
static func zap_notification_body(profiles: Profiles, zap: Zap, locale: Locale = Locale.current) -> String {
|
||||
let src = zap.request.ev
|
||||
let pk = zap.is_anon ? ANON_PUBKEY : src.pubkey
|
||||
|
||||
let profile = try? profiles.lookup(id: pk)
|
||||
let name = Profile.displayName(profile: profile, pubkey: pk).displayName.truncate(maxLength: 50)
|
||||
|
||||
let sats = NSNumber(value: (Double(zap.invoice.amount) / 1000.0))
|
||||
let formattedSats = format_msats_abbrev(zap.invoice.amount)
|
||||
|
||||
if src.content.isEmpty {
|
||||
let format = localizedStringFormat(key: "zap_notification_no_message", locale: locale)
|
||||
return String(format: format, locale: locale, sats.decimalValue as NSDecimalNumber, formattedSats, name)
|
||||
} else {
|
||||
let format = localizedStringFormat(key: "zap_notification_with_message", locale: locale)
|
||||
return String(format: format, locale: locale, sats.decimalValue as NSDecimalNumber, formattedSats, name, src.content)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
//
|
||||
// NotificationService.swift
|
||||
// DamusNotificationService
|
||||
//
|
||||
// Created by Daniel D’Aquino on 2023-11-10.
|
||||
//
|
||||
|
||||
import Kingfisher
|
||||
import ImageIO
|
||||
import UserNotifications
|
||||
import Foundation
|
||||
import UniformTypeIdentifiers
|
||||
import Intents
|
||||
|
||||
class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
var contentHandler: ((UNNotificationContent) -> Void)?
|
||||
var bestAttemptContent: UNMutableNotificationContent?
|
||||
|
||||
private func configureKingfisherCache() {
|
||||
guard let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.DAMUS_APP_GROUP_IDENTIFIER) else {
|
||||
return
|
||||
}
|
||||
|
||||
let cachePath = groupURL.appendingPathComponent(Constants.IMAGE_CACHE_DIRNAME)
|
||||
if let cache = try? ImageCache(name: "sharedCache", cacheDirectoryURL: cachePath) {
|
||||
KingfisherManager.shared.cache = cache
|
||||
}
|
||||
}
|
||||
|
||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||
configureKingfisherCache()
|
||||
|
||||
self.contentHandler = contentHandler
|
||||
|
||||
guard let nostr_event_json = request.content.userInfo["nostr_event"] as? String,
|
||||
let nostr_event = NdbNote.owned_from_json(json: nostr_event_json)
|
||||
else {
|
||||
// No nostr event detected. Just display the original notification
|
||||
contentHandler(request.content)
|
||||
return;
|
||||
}
|
||||
|
||||
// Log that we got a push notification
|
||||
Log.debug("Got nostr event push notification from pubkey %s", for: .push_notifications, nostr_event.pubkey.hex())
|
||||
|
||||
Task {
|
||||
guard let state = await NotificationExtensionState() else {
|
||||
Log.debug("Failed to open nostrdb", for: .push_notifications)
|
||||
|
||||
// Something failed to initialize so let's go for the next best thing
|
||||
guard let improved_content = NotificationFormatter.shared.format_message(event: nostr_event) else {
|
||||
// We cannot format this nostr event. Suppress notification.
|
||||
contentHandler(UNNotificationContent())
|
||||
return
|
||||
}
|
||||
contentHandler(improved_content)
|
||||
return
|
||||
}
|
||||
|
||||
let sender_profile = {
|
||||
let profile = try? state.profiles.lookup(id: nostr_event.pubkey)
|
||||
let picture = ((profile?.picture.map { URL(string: $0) }) ?? URL(string: robohash(nostr_event.pubkey)))!
|
||||
return ProfileBuf(picture: picture,
|
||||
name: profile?.name,
|
||||
display_name: profile?.display_name,
|
||||
nip05: profile?.nip05)
|
||||
}()
|
||||
let sender_pubkey = nostr_event.pubkey
|
||||
|
||||
// Don't show notification details that match mute list.
|
||||
// TODO: Remove this code block once we get notification suppression entitlement from Apple. It will be covered by the `guard should_display_notification` block
|
||||
if await state.mutelist_manager.is_event_muted(nostr_event) {
|
||||
// We cannot really suppress muted notifications until we have the notification supression entitlement.
|
||||
// The best we can do if we ever get those muted notifications (which we generally won't due to server-side processing) is to obscure the details
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = NSLocalizedString("Muted event", comment: "Title for a push notification which has been muted")
|
||||
content.body = NSLocalizedString("This is an event that has been muted according to your mute list rules. We cannot suppress this notification, but we obscured the details to respect your preferences", comment: "Description for a push notification which has been muted, and explanation that we cannot suppress it")
|
||||
content.sound = UNNotificationSound.default
|
||||
contentHandler(content)
|
||||
return
|
||||
}
|
||||
|
||||
guard await should_display_notification(state: state, event: nostr_event, mode: .push) else {
|
||||
Log.debug("should_display_notification failed", for: .push_notifications)
|
||||
// We should not display notification for this event. Suppress notification.
|
||||
// contentHandler(UNNotificationContent())
|
||||
// TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification
|
||||
contentHandler(request.content)
|
||||
return
|
||||
}
|
||||
|
||||
guard let notification_object = generate_local_notification_object(ndb: state.ndb, from: nostr_event, state: state) else {
|
||||
Log.debug("generate_local_notification_object failed", for: .push_notifications)
|
||||
// We could not process this notification. Probably an unsupported nostr event kind. Suppress.
|
||||
// contentHandler(UNNotificationContent())
|
||||
// TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification
|
||||
contentHandler(request.content)
|
||||
return
|
||||
}
|
||||
|
||||
let sender_dn = DisplayName(name: sender_profile.name, display_name: sender_profile.display_name, pubkey: sender_pubkey)
|
||||
guard let (improvedContent, _) = await NotificationFormatter.shared.format_message(displayName: sender_dn.displayName, notify: notification_object, state: state) else {
|
||||
|
||||
Log.debug("NotificationFormatter.format_message failed", for: .push_notifications)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
var options: [AnyHashable: Any] = [:]
|
||||
if let imageSource = CGImageSourceCreateWithURL(sender_profile.picture as CFURL, nil),
|
||||
let uti = CGImageSourceGetType(imageSource) {
|
||||
options[UNNotificationAttachmentOptionsTypeHintKey] = uti
|
||||
}
|
||||
|
||||
let attachment = try UNNotificationAttachment(identifier: sender_profile.picture.absoluteString, url: sender_profile.picture, options: options)
|
||||
improvedContent.attachments = [attachment]
|
||||
} catch {
|
||||
Log.error("failed to get notification attachment: %s", for: .push_notifications, error.localizedDescription)
|
||||
}
|
||||
|
||||
let kind = nostr_event.known_kind
|
||||
|
||||
// these aren't supported yet
|
||||
if !(kind == .text || kind == .dm) {
|
||||
contentHandler(improvedContent)
|
||||
return
|
||||
}
|
||||
|
||||
// rich communication notifications for kind1, dms, etc
|
||||
|
||||
let message_intent = await message_intent_from_note(ndb: state.ndb,
|
||||
sender_profile: sender_profile,
|
||||
content: improvedContent.body,
|
||||
note: nostr_event,
|
||||
our_pubkey: state.keypair.pubkey)
|
||||
|
||||
improvedContent.threadIdentifier = nostr_event.thread_id().hex()
|
||||
improvedContent.categoryIdentifier = "COMMUNICATION"
|
||||
|
||||
let interaction = INInteraction(intent: message_intent, response: nil)
|
||||
interaction.direction = .incoming
|
||||
do {
|
||||
try await interaction.donate()
|
||||
let updated = try improvedContent.updating(from: message_intent)
|
||||
contentHandler(updated)
|
||||
} catch {
|
||||
Log.error("failed to donate interaction: %s", for: .push_notifications, error.localizedDescription)
|
||||
contentHandler(improvedContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func serviceExtensionTimeWillExpire() {
|
||||
// Called just before the extension will be terminated by the system.
|
||||
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
||||
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct ProfileBuf {
|
||||
let picture: URL
|
||||
let name: String?
|
||||
let display_name: String?
|
||||
let nip05: String?
|
||||
}
|
||||
|
||||
func message_intent_from_note(ndb: Ndb, sender_profile: ProfileBuf, content: String, note: NdbNote, our_pubkey: Pubkey) async -> INSendMessageIntent {
|
||||
let sender_pk = note.pubkey
|
||||
let sender = await profile_to_inperson(name: sender_profile.name,
|
||||
display_name: sender_profile.display_name,
|
||||
picture: sender_profile.picture.absoluteString,
|
||||
nip05: sender_profile.nip05,
|
||||
pubkey: sender_pk,
|
||||
our_pubkey: our_pubkey)
|
||||
|
||||
let conversationIdentifier = note.thread_id().hex()
|
||||
var recipients: [INPerson] = []
|
||||
var pks: [Pubkey] = []
|
||||
let meta = INSendMessageIntentDonationMetadata()
|
||||
|
||||
// gather recipients
|
||||
if let recipient_note_id = note.direct_replies() {
|
||||
let replying_to_pk = try? ndb.lookup_note(recipient_note_id, borrow: { replying_to_note -> Pubkey? in
|
||||
switch replying_to_note {
|
||||
case .none: return nil
|
||||
case .some(let note): return note.pubkey
|
||||
}
|
||||
})
|
||||
if let replying_to_pk {
|
||||
meta.isReplyToCurrentUser = replying_to_pk == our_pubkey
|
||||
|
||||
if replying_to_pk != sender_pk {
|
||||
// we push the actual person being replied to first
|
||||
pks.append(replying_to_pk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let pubkeys = Array(note.referenced_pubkeys)
|
||||
meta.recipientCount = pubkeys.count
|
||||
if pubkeys.contains(sender_pk) {
|
||||
meta.recipientCount -= 1
|
||||
}
|
||||
|
||||
for pk in pubkeys.prefix(3) {
|
||||
if pk == sender_pk || pks.contains(pk) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !meta.isReplyToCurrentUser && pk == our_pubkey {
|
||||
meta.mentionsCurrentUser = true
|
||||
}
|
||||
|
||||
pks.append(pk)
|
||||
}
|
||||
|
||||
for pk in pks {
|
||||
let recipient = await pubkey_to_inperson(ndb: ndb, pubkey: pk, our_pubkey: our_pubkey)
|
||||
recipients.append(recipient)
|
||||
}
|
||||
|
||||
// we enable default formatting this way
|
||||
var groupName = INSpeakableString(spokenPhrase: "")
|
||||
|
||||
// otherwise we just say its a DM
|
||||
if note.known_kind == .dm {
|
||||
groupName = INSpeakableString(spokenPhrase: "DM")
|
||||
}
|
||||
|
||||
let intent = INSendMessageIntent(recipients: recipients,
|
||||
outgoingMessageType: .outgoingMessageText,
|
||||
content: content,
|
||||
speakableGroupName: groupName,
|
||||
conversationIdentifier: conversationIdentifier,
|
||||
serviceName: "kind\(note.kind)",
|
||||
sender: sender,
|
||||
attachments: nil)
|
||||
intent.donationMetadata = meta
|
||||
|
||||
// this is needed for recipients > 0
|
||||
if let img = sender.image {
|
||||
intent.setImage(img, forParameterNamed: \.speakableGroupName)
|
||||
}
|
||||
|
||||
return intent
|
||||
}
|
||||
|
||||
func pubkey_to_inperson(ndb: Ndb, pubkey: Pubkey, our_pubkey: Pubkey) async -> INPerson {
|
||||
let profile = try? ndb.lookup_profile(pubkey, borrow: { profileRecord in
|
||||
switch profileRecord {
|
||||
case .some(let pr): return pr.profile
|
||||
case .none: return nil
|
||||
}
|
||||
})
|
||||
let name = profile?.name
|
||||
let display_name = profile?.display_name
|
||||
let nip05 = profile?.nip05
|
||||
let picture = profile?.picture
|
||||
|
||||
return await profile_to_inperson(name: name,
|
||||
display_name: display_name,
|
||||
picture: picture,
|
||||
nip05: nip05,
|
||||
pubkey: pubkey,
|
||||
our_pubkey: our_pubkey)
|
||||
}
|
||||
|
||||
func fetch_pfp(picture: URL) async throws -> RetrieveImageResult {
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
KingfisherManager.shared.retrieveImage(with: Kingfisher.ImageResource(downloadURL: picture)) { result in
|
||||
switch result {
|
||||
case .success(let img):
|
||||
continuation.resume(returning: img)
|
||||
case .failure(let error):
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func profile_to_inperson(name: String?, display_name: String?, picture: String?, nip05: String?, pubkey: Pubkey, our_pubkey: Pubkey) async -> INPerson {
|
||||
let npub = pubkey.npub
|
||||
let handle = INPersonHandle(value: npub, type: .unknown)
|
||||
var aliases: [INPersonHandle] = []
|
||||
|
||||
if let nip05 {
|
||||
aliases.append(INPersonHandle(value: nip05, type: .emailAddress))
|
||||
}
|
||||
|
||||
let nostrName = DisplayName(name: name, display_name: display_name, pubkey: pubkey)
|
||||
let nameComponents = nostrName.nameComponents()
|
||||
let displayName = nostrName.displayName
|
||||
let contactIdentifier = npub
|
||||
let customIdentifier = npub
|
||||
let suggestionType = INPersonSuggestionType.socialProfile
|
||||
|
||||
var image: INImage? = nil
|
||||
|
||||
if let picture,
|
||||
let url = URL(string: picture),
|
||||
let img = try? await fetch_pfp(picture: url),
|
||||
let imgdata = img.data()
|
||||
{
|
||||
image = INImage(imageData: imgdata)
|
||||
} else {
|
||||
Log.error("Failed to fetch pfp (%s) for %s", for: .push_notifications, picture ?? "nil", displayName)
|
||||
}
|
||||
|
||||
let person = INPerson(personHandle: handle,
|
||||
nameComponents: nameComponents,
|
||||
displayName: displayName,
|
||||
image: image,
|
||||
contactIdentifier: contactIdentifier,
|
||||
customIdentifier: customIdentifier,
|
||||
isMe: pubkey == our_pubkey,
|
||||
suggestionType: suggestionType
|
||||
)
|
||||
|
||||
return person
|
||||
}
|
||||
|
||||
func robohash(_ pk: Pubkey) -> String {
|
||||
return "https://robohash.org/" + pk.hex()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>1C8F.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
13
Makefile
13
Makefile
@@ -1,13 +0,0 @@
|
||||
|
||||
all: nostrscript/primal.wasm
|
||||
|
||||
nostrscript/%.wasm: nostrscript/%.ts nostrscript/nostr.ts Makefile
|
||||
asc $< --runtime stub --outFile $@ --optimize
|
||||
|
||||
tags:
|
||||
find damus-c -name '*.c' -or -name '*.h' | xargs ctags
|
||||
|
||||
clean:
|
||||
rm nostrscript/*.wasm
|
||||
|
||||
.PHONY: tags
|
||||
@@ -1,32 +1,3 @@
|
||||
// swift-tools-version: 6.0
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "damus",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
.macOS(.v12)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "damus",
|
||||
targets: ["damus"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/jb55/secp256k1.swift.git", branch: "main")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "damus",
|
||||
dependencies: [
|
||||
.product(name: "secp256k1", package: "secp256k1.swift")
|
||||
],
|
||||
path: "damus"),
|
||||
.testTarget(
|
||||
name: "damusTests",
|
||||
dependencies: ["damus"],
|
||||
path: "damusTests"),
|
||||
]
|
||||
)
|
||||
dependencies: [
|
||||
.Package(url: "https://github.com/jb55/secp256k1.swift.git", branch: "main")
|
||||
]
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>1C8F.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
125
Purple.storekit
125
Purple.storekit
@@ -1,125 +0,0 @@
|
||||
{
|
||||
"identifier" : "64C21A2D",
|
||||
"nonRenewingSubscriptions" : [
|
||||
|
||||
],
|
||||
"products" : [
|
||||
|
||||
],
|
||||
"settings" : {
|
||||
"_applicationInternalID" : "1628663131",
|
||||
"_developerTeamID" : "XK7H4JAB3D",
|
||||
"_failTransactionsEnabled" : false,
|
||||
"_lastSynchronizedDate" : 704848066.26849198,
|
||||
"_locale" : "en_US",
|
||||
"_storefront" : "USA",
|
||||
"_storeKitErrors" : [
|
||||
{
|
||||
"current" : null,
|
||||
"enabled" : false,
|
||||
"name" : "Load Products"
|
||||
},
|
||||
{
|
||||
"current" : null,
|
||||
"enabled" : false,
|
||||
"name" : "Purchase"
|
||||
},
|
||||
{
|
||||
"current" : null,
|
||||
"enabled" : false,
|
||||
"name" : "Verification"
|
||||
},
|
||||
{
|
||||
"current" : null,
|
||||
"enabled" : false,
|
||||
"name" : "App Store Sync"
|
||||
},
|
||||
{
|
||||
"current" : null,
|
||||
"enabled" : false,
|
||||
"name" : "Subscription Status"
|
||||
},
|
||||
{
|
||||
"current" : null,
|
||||
"enabled" : false,
|
||||
"name" : "App Transaction"
|
||||
},
|
||||
{
|
||||
"current" : null,
|
||||
"enabled" : false,
|
||||
"name" : "Manage Subscriptions Sheet"
|
||||
},
|
||||
{
|
||||
"current" : null,
|
||||
"enabled" : false,
|
||||
"name" : "Refund Request Sheet"
|
||||
},
|
||||
{
|
||||
"current" : null,
|
||||
"enabled" : false,
|
||||
"name" : "Offer Code Redeem Sheet"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subscriptionGroups" : [
|
||||
{
|
||||
"id" : "21283177",
|
||||
"localizations" : [
|
||||
|
||||
],
|
||||
"name" : "Purple",
|
||||
"subscriptions" : [
|
||||
{
|
||||
"adHocOffers" : [
|
||||
|
||||
],
|
||||
"codeOffers" : [
|
||||
|
||||
],
|
||||
"displayPrice" : "6.99",
|
||||
"familyShareable" : false,
|
||||
"groupNumber" : 1,
|
||||
"internalID" : "6446591615",
|
||||
"introductoryOffer" : null,
|
||||
"localizations" : [
|
||||
{
|
||||
"description" : "Support damus development with Damus Purple!",
|
||||
"displayName" : "Damus Purple",
|
||||
"locale" : "en_CA"
|
||||
}
|
||||
],
|
||||
"productID" : "purple",
|
||||
"recurringSubscriptionPeriod" : "P1M",
|
||||
"referenceName" : "Purple",
|
||||
"subscriptionGroupID" : "21283177",
|
||||
"type" : "RecurringSubscription"
|
||||
},
|
||||
{
|
||||
"adHocOffers" : [
|
||||
|
||||
],
|
||||
"codeOffers" : [
|
||||
|
||||
],
|
||||
"displayPrice" : "69.99",
|
||||
"familyShareable" : false,
|
||||
"groupNumber" : 2,
|
||||
"internalID" : "6448764101",
|
||||
"introductoryOffer" : null,
|
||||
"localizations" : [
|
||||
|
||||
],
|
||||
"productID" : "purpleyearly",
|
||||
"recurringSubscriptionPeriod" : "P1Y",
|
||||
"referenceName" : "Purple Yearly",
|
||||
"subscriptionGroupID" : "21283177",
|
||||
"type" : "RecurringSubscription"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"version" : {
|
||||
"major" : 3,
|
||||
"minor" : 0
|
||||
}
|
||||
}
|
||||
92
README.md
92
README.md
@@ -1,73 +1,27 @@
|
||||
<div align="center">
|
||||
[](https://github.com/damus-io/damus/actions/workflows/run-tests.yaml)
|
||||
|
||||
<img src="./damus/Assets.xcassets/damus-home.imageset/damus-home@2x.png" alt="Damus Logo" title="Damus logo" width=""/>
|
||||
# damus
|
||||
|
||||
# Damus
|
||||
A twitter-like [nostr][nostr] client for iPhone, iPad and MacOS.
|
||||
|
||||
The social network you control
|
||||
|
||||
A twitter-like [nostr][nostr] client for iPhone, iPad and MacOS.
|
||||
|
||||
[](/LICENSE)
|
||||
|
||||
## Download and Install
|
||||
|
||||
[](https://apps.apple.com/us/app/damus/id1628663131)
|
||||
|
||||
## Supported Platforms
|
||||
|
||||
iOS 16.0+ • macOS 13.0+
|
||||
|
||||
<img src="./demo1.png" width="70%" height="50%" />
|
||||
|
||||
</div>
|
||||
<img src="./ss.png" width="50%" height="50%" />
|
||||
|
||||
[nostr]: https://github.com/fiatjaf/nostr
|
||||
|
||||
## How is Damus better than X/Twitter?
|
||||
There are no toxic algorithms.\
|
||||
You can send or receive zaps (satoshis) without asking for permission.\
|
||||
[There is no central database](https://fiatjaf.com/nostr.html). Therefore, Damus is censorship resistant.\
|
||||
There are no ads.\
|
||||
You don't have to reveal sensitive personal information to sign up.\
|
||||
No email is required. \
|
||||
No phone number is required. \
|
||||
Damus is free and open source software. \
|
||||
There is no Big Tech moat. Therefore, seamless interoperability with thousands or millions of other nostr apps is possible, and is how [Damus and nostr win](https://www.youtube.com/watch?v=qTixqS-W1yo).
|
||||
|
||||
## If there are no ads, how is Damus funded?
|
||||
Damus offers a paid subscription 🟣 purple 🟣 https://damus.io/purple/. \
|
||||
Initial benefits include a unique subscriber number, subscriber badge, and auto-translate powered by DeepL.
|
||||
|
||||
Damus has also graciously received donations or grants from hundreds of Damus users, [Opensats](https://opensats.org/), and the [Human Rights Foundation](https://hrf.org/).
|
||||
|
||||
## Spec Compliance
|
||||
|
||||
damus implements the following [Nostr Implementation Possibilities][nips]
|
||||
|
||||
- [NIP-01: Basic protocol flow][nip01]
|
||||
- [NIP-04: Encrypted direct message][nip04]
|
||||
- [NIP-08: Mentions][nip08]
|
||||
- [NIP-10: Reply conventions][nip10]
|
||||
- [NIP-12: Generic tag queries (hashtags)][nip12]
|
||||
- [NIP-19: bech32-encoded entities][NIP19]
|
||||
- [NIP-21: nostr: URI scheme][NIP21]
|
||||
- [NIP-25: Reactions][NIP25]
|
||||
- [NIP-42: Authentication of clients to relays][nip42]
|
||||
- [NIP-56: Reporting][nip56]
|
||||
|
||||
[nips]: https://github.com/nostr-protocol/nips
|
||||
[nip01]: https://github.com/nostr-protocol/nips/blob/master/01.md
|
||||
[nip04]: https://github.com/nostr-protocol/nips/blob/master/04.md
|
||||
[nip08]: https://github.com/nostr-protocol/nips/blob/master/08.md
|
||||
[nip10]: https://github.com/nostr-protocol/nips/blob/master/10.md
|
||||
[nip12]: https://github.com/nostr-protocol/nips/blob/master/12.md
|
||||
[nip19]: https://github.com/nostr-protocol/nips/blob/master/19.md
|
||||
[nip21]: https://github.com/nostr-protocol/nips/blob/master/21.md
|
||||
[nip25]: https://github.com/nostr-protocol/nips/blob/master/25.md
|
||||
[nip42]: https://github.com/nostr-protocol/nips/blob/master/42.md
|
||||
[nip56]: https://github.com/nostr-protocol/nips/blob/master/56.md
|
||||
|
||||
|
||||
## Getting Started on Damus
|
||||
|
||||
@@ -78,7 +32,7 @@ damus implements the following [Nostr Implementation Possibilities][nips]
|
||||
- Relays: You can add more relays to send your notes to by tapping the "+".
|
||||
- Find more relays to add: https://nostr.info/relays/
|
||||
- Public Key (pubkey): Your public, personal address and how people can find and tag you
|
||||
- Secret Key: Your *private* key unique to you. Never share your private key publicly and share with other clients at your own risk!
|
||||
- Secret Key: Your *private* key unique to you. Never share your private key publically and share with other clients at your own risk!
|
||||
- Save your keys somewhere safe
|
||||
- Log out
|
||||
|
||||
@@ -92,15 +46,19 @@ damus implements the following [Nostr Implementation Possibilities][nips]
|
||||
1. Search their username in the search bar at the top of the 🔍 Global Feed and click their profile
|
||||
2. Tap the 🔑 icon which will copy their pubkey to your clipboard
|
||||
3. Go back to your 🏠 Personal Feed and tap the blue + button to compose your Note
|
||||
4. Add @ directly followed by the pubkey (e.g., `@npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s`)
|
||||
- You can also tap the ellipsis menu of a Note (three dots in top right of note) to grab their User ID aka pubkey or Note ID to link directly to a Note.
|
||||
4. Add @ direcly followed by the pubkey (e.g., `@npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s`)
|
||||
- You can also long-press a Note to grab their User ID aka pubkey or Note ID to link directly to a Note.
|
||||
- Currently you can't delete your Notes in the iOS app
|
||||
- Share images by pasting the image url which you can grab from nostr.build, imgbb, imgur, etc. (i.e., `https://i.ibb.co/2SHZbwm/alpha60.jpg`). Currently images only load for people you follow in the 🏠 Personal Feed. Images are not automatically loaded in 🔍 Global Feed
|
||||
- Share images by pasting the image url which you can grab from imgbb, imgur, etc. (i.e., `https://i.ibb.co/2SHZbwm/alpha60.jpg`). Currently images only load for people you follow in the 🏠 Personal Feed. Images are not automatically loaded in 🔍 Global Feed
|
||||
- Engaging with Notes
|
||||
- 💬 Replying to a Note: Tap the chat icon underneath the note. This will show up in the users’ notifications and in your 🏠 Personal and 🔍 Global Feeds
|
||||
- ♺ Reposts: Tap the repost icon which will show up in your 🏠 Personal and 🔍 Global Feeds
|
||||
- ♡ Likes: Tap the heart icon. Users will not get a notification, and cannot see who liked their note (currently, web clients can see your pfp only)
|
||||
|
||||
- Formatting Notes (may not format as intended in other web clients)
|
||||
- Italics: 1 asterisk `*italic*`
|
||||
- Bold: 2 asterisk `**bold**`
|
||||
- Strikethrough: 1 tildes `~strikethrough~`
|
||||
- Code: 1 back-tick `` `code` ``
|
||||
|
||||
#### 💬 Encrypted DMs (chat app, bottom navigation)
|
||||
- Tap the chat icon and you'll notice there's nothing to see at first. Go to a user profile and tap the 💬 chat icon next to the follow button to begin a DM
|
||||
@@ -118,9 +76,7 @@ damus implements the following [Nostr Implementation Possibilities][nips]
|
||||
4. For PFP, insert a URL containing your image (support video: https://cdn.jb55.com/vid/pfp-editor.mp4)
|
||||
5. Save
|
||||
|
||||
|
||||
#### ⚡️ Request Sats
|
||||
Paste an invoice from your favorite LN wallet.
|
||||
(Sats or Satoshis are the smallest denomination of bitcoin)
|
||||
|
||||
**Alby (browser extension)**
|
||||
@@ -138,31 +94,17 @@ Paste an invoice from your favorite LN wallet.
|
||||
|
||||
Contributors welcome! Start by examining known issues: https://github.com/damus-io/damus/issues.
|
||||
|
||||
### Mailing lists
|
||||
### Code
|
||||
|
||||
We have a few mailing lists that anyone can join to get involved in damus development:
|
||||
[Email patches][git-send-email] to jb55@jb55.com are preferred, but I accept PRs on GitHub as well.
|
||||
|
||||
- [dev][dev-list] - development discussions
|
||||
- [patches][patches-list] - code submission and review
|
||||
- [product][product-list] - product discussions
|
||||
- [design][design-list] - design discussions
|
||||
|
||||
[dev-list]: https://damus.io/list/dev
|
||||
[patches-list]: https://damus.io/list/patches
|
||||
[product-list]: https://damus.io/list/product
|
||||
[design-list]: https://damus.io/list/design
|
||||
|
||||
### Contributing
|
||||
|
||||
Before starting to work on any contributions, please read [docs/CONTRIBUTING.md](./docs/CONTRIBUTING.md).
|
||||
[git-send-email]: http://git-send-email.io
|
||||
|
||||
### Privacy
|
||||
Your internet protocol (IP) address is exposed to the relays you connect to, and third party media hosters (e.g. nostr.build, imgur.com, giphy.com, youtube.com etc.) that render on Damus. If you want to improve your privacy, consider utilizing a service that masks your IP address (e.g. a VPN) from trackers online.
|
||||
|
||||
The relay also learns which public keys you are requesting, meaning your public key will be tied to your IP address.
|
||||
|
||||
It is public information which other profiles (npubs) you are exchanging DMs with. The content of the DMs is encrypted.
|
||||
|
||||
### Translations
|
||||
|
||||
Translators welcome! Join the [Transifex][transifex] project.
|
||||
@@ -173,10 +115,8 @@ All user-facing strings must have a comment in order to provide context to trans
|
||||
|
||||
### Awards
|
||||
|
||||
Damus lead dev and founder Will awards developers with satoshis!
|
||||
There may be nostr badges awarded for contributors in the future... :)
|
||||
|
||||
|
||||
First contributors:
|
||||
|
||||
1. @randymcmillan
|
||||
|
||||
56
damus-c/block.h
Normal file
56
damus-c/block.h
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// block.h
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-04-09.
|
||||
//
|
||||
|
||||
#ifndef block_h
|
||||
#define block_h
|
||||
|
||||
#include "nostr_bech32.h"
|
||||
#include "str_block.h"
|
||||
|
||||
#define MAX_BLOCKS 1024
|
||||
|
||||
enum block_type {
|
||||
BLOCK_HASHTAG = 1,
|
||||
BLOCK_TEXT = 2,
|
||||
BLOCK_MENTION_INDEX = 3,
|
||||
BLOCK_MENTION_BECH32 = 4,
|
||||
BLOCK_URL = 5,
|
||||
BLOCK_INVOICE = 6,
|
||||
};
|
||||
|
||||
|
||||
typedef struct invoice_block {
|
||||
struct str_block invstr;
|
||||
union {
|
||||
struct bolt11 *bolt11;
|
||||
};
|
||||
} invoice_block_t;
|
||||
|
||||
typedef struct mention_bech32_block {
|
||||
struct str_block str;
|
||||
struct nostr_bech32 bech32;
|
||||
} mention_bech32_block_t;
|
||||
|
||||
typedef struct block {
|
||||
enum block_type type;
|
||||
union {
|
||||
struct str_block str;
|
||||
struct invoice_block invoice;
|
||||
struct mention_bech32_block mention_bech32;
|
||||
int mention_index;
|
||||
} block;
|
||||
} block_t;
|
||||
|
||||
typedef struct blocks {
|
||||
int num_blocks;
|
||||
struct block *blocks;
|
||||
} blocks_t;
|
||||
|
||||
void blocks_init(struct blocks *blocks);
|
||||
void blocks_free(struct blocks *blocks);
|
||||
|
||||
#endif /* block_h */
|
||||
@@ -14,7 +14,7 @@
|
||||
* Example:
|
||||
* static void COLD moan(const char *reason)
|
||||
* {
|
||||
* fprintf(stderr, "Error: %s (%s)\n", reason, strerror(errno));
|
||||
* fprintf(stderr, "Error: %s (%s)\n", reason, strerror(errno));
|
||||
* }
|
||||
*/
|
||||
#define COLD __attribute__((__cold__))
|
||||
@@ -33,8 +33,8 @@
|
||||
* Example:
|
||||
* static void NORETURN fail(const char *reason)
|
||||
* {
|
||||
* fprintf(stderr, "Error: %s (%s)\n", reason, strerror(errno));
|
||||
* exit(1);
|
||||
* fprintf(stderr, "Error: %s (%s)\n", reason, strerror(errno));
|
||||
* exit(1);
|
||||
* }
|
||||
*/
|
||||
#define NORETURN __attribute__((__noreturn__))
|
||||
@@ -56,7 +56,7 @@
|
||||
* void PRINTF_FMT(2,3) my_printf(const char *prefix, const char *fmt, ...);
|
||||
*/
|
||||
#define PRINTF_FMT(nfmt, narg) \
|
||||
__attribute__((format(__printf__, nfmt, narg)))
|
||||
__attribute__((format(__printf__, nfmt, narg)))
|
||||
#else
|
||||
#define PRINTF_FMT(nfmt, narg)
|
||||
#endif
|
||||
@@ -106,7 +106,7 @@
|
||||
* // With some preprocessor options, this is unnecessary.
|
||||
* static UNNEEDED void add_to_counter(int add)
|
||||
* {
|
||||
* counter += add;
|
||||
* counter += add;
|
||||
* }
|
||||
*/
|
||||
#define UNNEEDED __attribute__((__unused__))
|
||||
@@ -121,12 +121,12 @@
|
||||
* the compiler that it must exist even if it (seems) unused.
|
||||
*
|
||||
* Example:
|
||||
* // Even if this is unused, these are vital for debugging.
|
||||
* static NEEDED int counter;
|
||||
* static NEEDED void dump_counter(void)
|
||||
* {
|
||||
* printf("Counter is %i\n", counter);
|
||||
* }
|
||||
* // Even if this is unused, these are vital for debugging.
|
||||
* static NEEDED int counter;
|
||||
* static NEEDED void dump_counter(void)
|
||||
* {
|
||||
* printf("Counter is %i\n", counter);
|
||||
* }
|
||||
*/
|
||||
#define NEEDED __attribute__((__used__))
|
||||
#else
|
||||
@@ -144,11 +144,11 @@
|
||||
* to the reader that it's deliberate.
|
||||
*
|
||||
* Example:
|
||||
* // This is used as a callback, so needs to have this prototype.
|
||||
* static int some_callback(void *unused UNUSED)
|
||||
* {
|
||||
* return 0;
|
||||
* }
|
||||
* // This is used as a callback, so needs to have this prototype.
|
||||
* static int some_callback(void *unused UNUSED)
|
||||
* {
|
||||
* return 0;
|
||||
* }
|
||||
*/
|
||||
#define UNUSED __attribute__((__unused__))
|
||||
#endif
|
||||
@@ -178,28 +178,28 @@
|
||||
* This can be done using the IS_COMPILE_CONSTANT() macro.
|
||||
*
|
||||
* Example:
|
||||
* enum greek { ALPHA, BETA, GAMMA, DELTA, EPSILON };
|
||||
* enum greek { ALPHA, BETA, GAMMA, DELTA, EPSILON };
|
||||
*
|
||||
* // Out-of-line version.
|
||||
* const char *greek_name(enum greek greek);
|
||||
* // Out-of-line version.
|
||||
* const char *greek_name(enum greek greek);
|
||||
*
|
||||
* // Inline version.
|
||||
* static inline const char *_greek_name(enum greek greek)
|
||||
* {
|
||||
* switch (greek) {
|
||||
* case ALPHA: return "alpha";
|
||||
* case BETA: return "beta";
|
||||
* case GAMMA: return "gamma";
|
||||
* case DELTA: return "delta";
|
||||
* case EPSILON: return "epsilon";
|
||||
* default: return "**INVALID**";
|
||||
* }
|
||||
* }
|
||||
* // Inline version.
|
||||
* static inline const char *_greek_name(enum greek greek)
|
||||
* {
|
||||
* switch (greek) {
|
||||
* case ALPHA: return "alpha";
|
||||
* case BETA: return "beta";
|
||||
* case GAMMA: return "gamma";
|
||||
* case DELTA: return "delta";
|
||||
* case EPSILON: return "epsilon";
|
||||
* default: return "**INVALID**";
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // Use inline if compiler knows answer. Otherwise call function
|
||||
* // to avoid copies of the same code everywhere.
|
||||
* #define greek_name(g) \
|
||||
* (IS_COMPILE_CONSTANT(greek) ? _greek_name(g) : greek_name(g))
|
||||
* // Use inline if compiler knows answer. Otherwise call function
|
||||
* // to avoid copies of the same code everywhere.
|
||||
* #define greek_name(g) \
|
||||
* (IS_COMPILE_CONSTANT(greek) ? _greek_name(g) : greek_name(g))
|
||||
*/
|
||||
#define IS_COMPILE_CONSTANT(expr) __builtin_constant_p(expr)
|
||||
#else
|
||||
@@ -220,7 +220,7 @@
|
||||
* // buf param may be freed by this; need return value!
|
||||
* static char *WARN_UNUSED_RESULT enlarge(char *buf, unsigned *size)
|
||||
* {
|
||||
* return realloc(buf, (*size) *= 2);
|
||||
* return realloc(buf, (*size) *= 2);
|
||||
* }
|
||||
*/
|
||||
#define WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
|
||||
@@ -307,7 +307,7 @@
|
||||
*
|
||||
* Example:
|
||||
* if (cpu_supports("mmx"))
|
||||
* printf("MMX support engaged!\n");
|
||||
* printf("MMX support engaged!\n");
|
||||
*/
|
||||
#define cpu_supports(x) __builtin_cpu_supports(x)
|
||||
#else
|
||||
156
damus-c/cursor.h
Normal file
156
damus-c/cursor.h
Normal file
@@ -0,0 +1,156 @@
|
||||
//
|
||||
// cursor.h
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-04-09.
|
||||
//
|
||||
|
||||
#ifndef cursor_h
|
||||
#define cursor_h
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef unsigned char u8;
|
||||
|
||||
struct cursor {
|
||||
const u8 *p;
|
||||
const u8 *start;
|
||||
const u8 *end;
|
||||
};
|
||||
|
||||
static inline int is_whitespace(char c) {
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
|
||||
}
|
||||
|
||||
static inline int is_boundary(char c) {
|
||||
return !isalnum(c);
|
||||
}
|
||||
|
||||
static inline int is_invalid_url_ending(char c) {
|
||||
return c == '!' || c == '?' || c == ')' || c == '.' || c == ',' || c == ';';
|
||||
}
|
||||
|
||||
static inline int is_alphanumeric(char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
static inline void make_cursor(struct cursor *c, const u8 *content, size_t len)
|
||||
{
|
||||
c->start = content;
|
||||
c->end = content + len;
|
||||
c->p = content;
|
||||
}
|
||||
|
||||
static inline int consume_until_boundary(struct cursor *cur) {
|
||||
char c;
|
||||
|
||||
while (cur->p < cur->end) {
|
||||
c = *cur->p;
|
||||
|
||||
if (is_boundary(c))
|
||||
return 1;
|
||||
|
||||
cur->p++;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline int consume_until_whitespace(struct cursor *cur, int or_end) {
|
||||
char c;
|
||||
int consumedAtLeastOne = 0;
|
||||
|
||||
while (cur->p < cur->end) {
|
||||
c = *cur->p;
|
||||
|
||||
if (is_whitespace(c))
|
||||
return consumedAtLeastOne;
|
||||
|
||||
cur->p++;
|
||||
consumedAtLeastOne = 1;
|
||||
}
|
||||
|
||||
return or_end;
|
||||
}
|
||||
|
||||
static inline int consume_until_non_alphanumeric(struct cursor *cur, int or_end) {
|
||||
char c;
|
||||
int consumedAtLeastOne = 0;
|
||||
|
||||
while (cur->p < cur->end) {
|
||||
c = *cur->p;
|
||||
|
||||
if (!is_alphanumeric(c))
|
||||
return consumedAtLeastOne;
|
||||
|
||||
cur->p++;
|
||||
consumedAtLeastOne = 1;
|
||||
}
|
||||
|
||||
return or_end;
|
||||
}
|
||||
|
||||
static inline int parse_char(struct cursor *cur, char c) {
|
||||
if (cur->p >= cur->end)
|
||||
return 0;
|
||||
|
||||
if (*cur->p == c) {
|
||||
cur->p++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int peek_char(struct cursor *cur, int ind) {
|
||||
if ((cur->p + ind < cur->start) || (cur->p + ind >= cur->end))
|
||||
return -1;
|
||||
|
||||
return *(cur->p + ind);
|
||||
}
|
||||
|
||||
|
||||
static inline int pull_byte(struct cursor *cur, u8 *byte) {
|
||||
if (cur->p >= cur->end)
|
||||
return 0;
|
||||
|
||||
*byte = *cur->p;
|
||||
cur->p++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline int pull_bytes(struct cursor *cur, int count, const u8 **bytes) {
|
||||
if (cur->p + count > cur->end)
|
||||
return 0;
|
||||
|
||||
*bytes = cur->p;
|
||||
cur->p += count;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline int parse_str(struct cursor *cur, const char *str) {
|
||||
int i;
|
||||
char c, cs;
|
||||
unsigned long len;
|
||||
|
||||
len = strlen(str);
|
||||
|
||||
if (cur->p + len >= cur->end)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
c = tolower(cur->p[i]);
|
||||
cs = tolower(str[i]);
|
||||
|
||||
if (c != cs)
|
||||
return 0;
|
||||
}
|
||||
|
||||
cur->p += len;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
#endif /* cursor_h */
|
||||
@@ -2,11 +2,6 @@
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
#include "damus.h"
|
||||
#include "bolt11.h"
|
||||
#include "amount.h"
|
||||
#include "nostr_bech32.h"
|
||||
#include "wasm.h"
|
||||
#include "nostrscript.h"
|
||||
#include "nostrdb.h"
|
||||
#include "lmdb.h"
|
||||
|
||||
|
||||
278
damus-c/damus.c
Normal file
278
damus-c/damus.c
Normal file
@@ -0,0 +1,278 @@
|
||||
//
|
||||
// damus.c
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2022-10-17.
|
||||
//
|
||||
|
||||
#include "damus.h"
|
||||
#include "cursor.h"
|
||||
#include "bolt11.h"
|
||||
#include "bech32.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int parse_digit(struct cursor *cur, int *digit) {
|
||||
int c;
|
||||
if ((c = peek_char(cur, 0)) == -1)
|
||||
return 0;
|
||||
|
||||
c -= '0';
|
||||
|
||||
if (c >= 0 && c <= 9) {
|
||||
*digit = c;
|
||||
cur->p++;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int parse_mention_index(struct cursor *cur, struct block *block) {
|
||||
int d1, d2, d3, ind;
|
||||
const u8 *start = cur->p;
|
||||
|
||||
if (!parse_str(cur, "#["))
|
||||
return 0;
|
||||
|
||||
if (!parse_digit(cur, &d1)) {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ind = d1;
|
||||
|
||||
if (parse_digit(cur, &d2))
|
||||
ind = (d1 * 10) + d2;
|
||||
|
||||
if (parse_digit(cur, &d3))
|
||||
ind = (d1 * 100) + (d2 * 10) + d3;
|
||||
|
||||
if (!parse_char(cur, ']')) {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
|
||||
block->type = BLOCK_MENTION_INDEX;
|
||||
block->block.mention_index = ind;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int parse_hashtag(struct cursor *cur, struct block *block) {
|
||||
int c;
|
||||
const u8 *start = cur->p;
|
||||
|
||||
if (!parse_char(cur, '#'))
|
||||
return 0;
|
||||
|
||||
c = peek_char(cur, 0);
|
||||
if (c == -1 || is_whitespace(c) || c == '#') {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
|
||||
consume_until_boundary(cur);
|
||||
|
||||
block->type = BLOCK_HASHTAG;
|
||||
block->block.str.start = (const char*)(start + 1);
|
||||
block->block.str.end = (const char*)cur->p;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int add_block(struct blocks *blocks, struct block block)
|
||||
{
|
||||
if (blocks->num_blocks + 1 >= MAX_BLOCKS)
|
||||
return 0;
|
||||
|
||||
blocks->blocks[blocks->num_blocks++] = block;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int add_text_block(struct blocks *blocks, const u8 *start, const u8 *end)
|
||||
{
|
||||
struct block b;
|
||||
|
||||
if (start == end)
|
||||
return 1;
|
||||
|
||||
b.type = BLOCK_TEXT;
|
||||
b.block.str.start = (const char*)start;
|
||||
b.block.str.end = (const char*)end;
|
||||
|
||||
return add_block(blocks, b);
|
||||
}
|
||||
|
||||
static int parse_url(struct cursor *cur, struct block *block) {
|
||||
const u8 *start = cur->p;
|
||||
|
||||
if (!parse_str(cur, "http"))
|
||||
return 0;
|
||||
|
||||
if (parse_char(cur, 's') || parse_char(cur, 'S')) {
|
||||
if (!parse_str(cur, "://")) {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (!parse_str(cur, "://")) {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!consume_until_whitespace(cur, 1)) {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// strip any unwanted characters
|
||||
while(is_invalid_url_ending(peek_char(cur, -1))) cur->p--;
|
||||
|
||||
block->type = BLOCK_URL;
|
||||
block->block.str.start = (const char *)start;
|
||||
block->block.str.end = (const char *)cur->p;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int parse_invoice(struct cursor *cur, struct block *block) {
|
||||
const u8 *start, *end;
|
||||
char *fail;
|
||||
struct bolt11 *bolt11;
|
||||
// optional
|
||||
parse_str(cur, "lightning:");
|
||||
|
||||
start = cur->p;
|
||||
|
||||
if (!parse_str(cur, "lnbc"))
|
||||
return 0;
|
||||
|
||||
if (!consume_until_whitespace(cur, 1)) {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
|
||||
end = cur->p;
|
||||
|
||||
char str[end - start + 1];
|
||||
str[end - start] = 0;
|
||||
memcpy(str, start, end - start);
|
||||
|
||||
if (!(bolt11 = bolt11_decode(NULL, str, &fail))) {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
|
||||
block->type = BLOCK_INVOICE;
|
||||
|
||||
block->block.invoice.invstr.start = (const char*)start;
|
||||
block->block.invoice.invstr.end = (const char*)end;
|
||||
block->block.invoice.bolt11 = bolt11;
|
||||
|
||||
cur->p = end;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int parse_mention_bech32(struct cursor *cur, struct block *block) {
|
||||
const u8 *start = cur->p;
|
||||
|
||||
if (!parse_str(cur, "nostr:"))
|
||||
return 0;
|
||||
|
||||
block->block.str.start = (const char *)cur->p;
|
||||
|
||||
if (!parse_nostr_bech32(cur, &block->block.mention_bech32.bech32)) {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
|
||||
block->block.str.end = (const char *)cur->p;
|
||||
|
||||
block->type = BLOCK_MENTION_BECH32;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int add_text_then_block(struct cursor *cur, struct blocks *blocks, struct block block, const u8 **start, const u8 *pre_mention)
|
||||
{
|
||||
if (!add_text_block(blocks, *start, pre_mention))
|
||||
return 0;
|
||||
|
||||
*start = (u8*)cur->p;
|
||||
|
||||
if (!add_block(blocks, block))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int damus_parse_content(struct blocks *blocks, const char *content) {
|
||||
int cp, c;
|
||||
struct cursor cur;
|
||||
struct block block;
|
||||
const u8 *start, *pre_mention;
|
||||
|
||||
blocks->num_blocks = 0;
|
||||
make_cursor(&cur, (const u8*)content, strlen(content));
|
||||
|
||||
start = cur.p;
|
||||
while (cur.p < cur.end && blocks->num_blocks < MAX_BLOCKS) {
|
||||
cp = peek_char(&cur, -1);
|
||||
c = peek_char(&cur, 0);
|
||||
|
||||
pre_mention = cur.p;
|
||||
if (cp == -1 || is_whitespace(cp) || c == '#') {
|
||||
if (c == '#' && (parse_mention_index(&cur, &block) || parse_hashtag(&cur, &block))) {
|
||||
if (!add_text_then_block(&cur, blocks, block, &start, pre_mention))
|
||||
return 0;
|
||||
continue;
|
||||
} else if ((c == 'h' || c == 'H') && parse_url(&cur, &block)) {
|
||||
if (!add_text_then_block(&cur, blocks, block, &start, pre_mention))
|
||||
return 0;
|
||||
continue;
|
||||
} else if ((c == 'l' || c == 'L') && parse_invoice(&cur, &block)) {
|
||||
if (!add_text_then_block(&cur, blocks, block, &start, pre_mention))
|
||||
return 0;
|
||||
continue;
|
||||
} else if (c == 'n' && parse_mention_bech32(&cur, &block)) {
|
||||
if (!add_text_then_block(&cur, blocks, block, &start, pre_mention))
|
||||
return 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
cur.p++;
|
||||
}
|
||||
|
||||
if (cur.p - start > 0) {
|
||||
if (!add_text_block(blocks, start, cur.p))
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void blocks_init(struct blocks *blocks) {
|
||||
blocks->blocks = malloc(sizeof(struct block) * MAX_BLOCKS);
|
||||
blocks->num_blocks = 0;
|
||||
}
|
||||
|
||||
void blocks_free(struct blocks *blocks) {
|
||||
if (!blocks->blocks) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < blocks->num_blocks; ++i) {
|
||||
if (blocks->blocks[i].type == BLOCK_MENTION_BECH32) {
|
||||
free(blocks->blocks[i].block.mention_bech32.bech32.buffer);
|
||||
blocks->blocks[i].block.mention_bech32.bech32.buffer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
free(blocks->blocks);
|
||||
blocks->num_blocks = 0;
|
||||
}
|
||||
18
damus-c/damus.h
Normal file
18
damus-c/damus.h
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// damus.h
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2022-10-17.
|
||||
//
|
||||
|
||||
#ifndef damus_h
|
||||
#define damus_h
|
||||
|
||||
#include <stdio.h>
|
||||
#include "nostr_bech32.h"
|
||||
#include "block.h"
|
||||
typedef unsigned char u8;
|
||||
|
||||
int damus_parse_content(struct blocks *blocks, const char *content);
|
||||
|
||||
#endif /* damus_h */
|
||||
@@ -11,13 +11,13 @@
|
||||
* Designed to be usable in constant-requiring initializers.
|
||||
*
|
||||
* Example:
|
||||
* struct mystruct {
|
||||
* char buf[BSWAP_16(0x1234)];
|
||||
* };
|
||||
* struct mystruct {
|
||||
* char buf[BSWAP_16(0x1234)];
|
||||
* };
|
||||
*/
|
||||
#define BSWAP_16(val) \
|
||||
((((uint16_t)(val) & 0x00ff) << 8) \
|
||||
| (((uint16_t)(val) & 0xff00) >> 8))
|
||||
#define BSWAP_16(val) \
|
||||
((((uint16_t)(val) & 0x00ff) << 8) \
|
||||
| (((uint16_t)(val) & 0xff00) >> 8))
|
||||
|
||||
/**
|
||||
* BSWAP_32 - reverse bytes in a constant uint32_t value.
|
||||
@@ -26,15 +26,15 @@
|
||||
* Designed to be usable in constant-requiring initializers.
|
||||
*
|
||||
* Example:
|
||||
* struct mystruct {
|
||||
* char buf[BSWAP_32(0xff000000)];
|
||||
* };
|
||||
* struct mystruct {
|
||||
* char buf[BSWAP_32(0xff000000)];
|
||||
* };
|
||||
*/
|
||||
#define BSWAP_32(val) \
|
||||
((((uint32_t)(val) & 0x000000ff) << 24) \
|
||||
| (((uint32_t)(val) & 0x0000ff00) << 8) \
|
||||
| (((uint32_t)(val) & 0x00ff0000) >> 8) \
|
||||
| (((uint32_t)(val) & 0xff000000) >> 24))
|
||||
#define BSWAP_32(val) \
|
||||
((((uint32_t)(val) & 0x000000ff) << 24) \
|
||||
| (((uint32_t)(val) & 0x0000ff00) << 8) \
|
||||
| (((uint32_t)(val) & 0x00ff0000) >> 8) \
|
||||
| (((uint32_t)(val) & 0xff000000) >> 24))
|
||||
|
||||
/**
|
||||
* BSWAP_64 - reverse bytes in a constant uint64_t value.
|
||||
@@ -43,19 +43,19 @@
|
||||
* Designed to be usable in constant-requiring initializers.
|
||||
*
|
||||
* Example:
|
||||
* struct mystruct {
|
||||
* char buf[BSWAP_64(0xff00000000000000ULL)];
|
||||
* };
|
||||
* struct mystruct {
|
||||
* char buf[BSWAP_64(0xff00000000000000ULL)];
|
||||
* };
|
||||
*/
|
||||
#define BSWAP_64(val) \
|
||||
((((uint64_t)(val) & 0x00000000000000ffULL) << 56) \
|
||||
| (((uint64_t)(val) & 0x000000000000ff00ULL) << 40) \
|
||||
| (((uint64_t)(val) & 0x0000000000ff0000ULL) << 24) \
|
||||
| (((uint64_t)(val) & 0x00000000ff000000ULL) << 8) \
|
||||
| (((uint64_t)(val) & 0x000000ff00000000ULL) >> 8) \
|
||||
| (((uint64_t)(val) & 0x0000ff0000000000ULL) >> 24) \
|
||||
| (((uint64_t)(val) & 0x00ff000000000000ULL) >> 40) \
|
||||
| (((uint64_t)(val) & 0xff00000000000000ULL) >> 56))
|
||||
#define BSWAP_64(val) \
|
||||
((((uint64_t)(val) & 0x00000000000000ffULL) << 56) \
|
||||
| (((uint64_t)(val) & 0x000000000000ff00ULL) << 40) \
|
||||
| (((uint64_t)(val) & 0x0000000000ff0000ULL) << 24) \
|
||||
| (((uint64_t)(val) & 0x00000000ff000000ULL) << 8) \
|
||||
| (((uint64_t)(val) & 0x000000ff00000000ULL) >> 8) \
|
||||
| (((uint64_t)(val) & 0x0000ff0000000000ULL) >> 24) \
|
||||
| (((uint64_t)(val) & 0x00ff000000000000ULL) >> 40) \
|
||||
| (((uint64_t)(val) & 0xff00000000000000ULL) >> 56))
|
||||
|
||||
#if HAVE_BYTESWAP_H
|
||||
#include <byteswap.h>
|
||||
@@ -65,12 +65,12 @@
|
||||
* @val: value whose bytes to swap.
|
||||
*
|
||||
* Example:
|
||||
* // Output contains "1024 is 4 as two bytes reversed"
|
||||
* printf("1024 is %u as two bytes reversed\n", bswap_16(1024));
|
||||
* // Output contains "1024 is 4 as two bytes reversed"
|
||||
* printf("1024 is %u as two bytes reversed\n", bswap_16(1024));
|
||||
*/
|
||||
static inline uint16_t bswap_16(uint16_t val)
|
||||
{
|
||||
return BSWAP_16(val);
|
||||
return BSWAP_16(val);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,12 +78,12 @@ static inline uint16_t bswap_16(uint16_t val)
|
||||
* @val: value whose bytes to swap.
|
||||
*
|
||||
* Example:
|
||||
* // Output contains "1024 is 262144 as four bytes reversed"
|
||||
* printf("1024 is %u as four bytes reversed\n", bswap_32(1024));
|
||||
* // Output contains "1024 is 262144 as four bytes reversed"
|
||||
* printf("1024 is %u as four bytes reversed\n", bswap_32(1024));
|
||||
*/
|
||||
static inline uint32_t bswap_32(uint32_t val)
|
||||
{
|
||||
return BSWAP_32(val);
|
||||
return BSWAP_32(val);
|
||||
}
|
||||
#endif /* !HAVE_BYTESWAP_H */
|
||||
|
||||
@@ -93,19 +93,19 @@ static inline uint32_t bswap_32(uint32_t val)
|
||||
* @val: value whose bytes to swap.
|
||||
*
|
||||
* Example:
|
||||
* // Output contains "1024 is 1125899906842624 as eight bytes reversed"
|
||||
* printf("1024 is %llu as eight bytes reversed\n",
|
||||
* (unsigned long long)bswap_64(1024));
|
||||
* // Output contains "1024 is 1125899906842624 as eight bytes reversed"
|
||||
* printf("1024 is %llu as eight bytes reversed\n",
|
||||
* (unsigned long long)bswap_64(1024));
|
||||
*/
|
||||
static inline uint64_t bswap_64(uint64_t val)
|
||||
{
|
||||
return BSWAP_64(val);
|
||||
return BSWAP_64(val);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Needed for Glibc like endiness check */
|
||||
#define __LITTLE_ENDIAN 1234
|
||||
#define __BIG_ENDIAN 4321
|
||||
#define __LITTLE_ENDIAN 1234
|
||||
#define __BIG_ENDIAN 4321
|
||||
|
||||
/* Sanity check the defines. We don't handle weird endianness. */
|
||||
#if !HAVE_LITTLE_ENDIAN && !HAVE_BIG_ENDIAN
|
||||
@@ -114,13 +114,13 @@ static inline uint64_t bswap_64(uint64_t val)
|
||||
#error "Can't compile for both big and little endian."
|
||||
#elif HAVE_LITTLE_ENDIAN
|
||||
#ifndef __BYTE_ORDER
|
||||
#define __BYTE_ORDER __LITTLE_ENDIAN
|
||||
#define __BYTE_ORDER __LITTLE_ENDIAN
|
||||
#elif __BYTE_ORDER != __LITTLE_ENDIAN
|
||||
#error "__BYTE_ORDER already defined, but not equal to __LITTLE_ENDIAN"
|
||||
#endif
|
||||
#elif HAVE_BIG_ENDIAN
|
||||
#ifndef __BYTE_ORDER
|
||||
#define __BYTE_ORDER __BIG_ENDIAN
|
||||
#define __BYTE_ORDER __BIG_ENDIAN
|
||||
#elif __BYTE_ORDER != __BIG_ENDIAN
|
||||
#error "__BYTE_ORDER already defined, but not equal to __BIG_ENDIAN"
|
||||
#endif
|
||||
@@ -242,7 +242,7 @@ typedef uint16_t ENDIAN_TYPE beint16_t;
|
||||
*/
|
||||
static inline leint64_t cpu_to_le64(uint64_t native)
|
||||
{
|
||||
return CPU_TO_LE64(native);
|
||||
return CPU_TO_LE64(native);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,7 +251,7 @@ static inline leint64_t cpu_to_le64(uint64_t native)
|
||||
*/
|
||||
static inline leint32_t cpu_to_le32(uint32_t native)
|
||||
{
|
||||
return CPU_TO_LE32(native);
|
||||
return CPU_TO_LE32(native);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,7 +260,7 @@ static inline leint32_t cpu_to_le32(uint32_t native)
|
||||
*/
|
||||
static inline leint16_t cpu_to_le16(uint16_t native)
|
||||
{
|
||||
return CPU_TO_LE16(native);
|
||||
return CPU_TO_LE16(native);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,7 +269,7 @@ static inline leint16_t cpu_to_le16(uint16_t native)
|
||||
*/
|
||||
static inline uint64_t le64_to_cpu(leint64_t le_val)
|
||||
{
|
||||
return LE64_TO_CPU(le_val);
|
||||
return LE64_TO_CPU(le_val);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,7 +278,7 @@ static inline uint64_t le64_to_cpu(leint64_t le_val)
|
||||
*/
|
||||
static inline uint32_t le32_to_cpu(leint32_t le_val)
|
||||
{
|
||||
return LE32_TO_CPU(le_val);
|
||||
return LE32_TO_CPU(le_val);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,7 +287,7 @@ static inline uint32_t le32_to_cpu(leint32_t le_val)
|
||||
*/
|
||||
static inline uint16_t le16_to_cpu(leint16_t le_val)
|
||||
{
|
||||
return LE16_TO_CPU(le_val);
|
||||
return LE16_TO_CPU(le_val);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,7 +296,7 @@ static inline uint16_t le16_to_cpu(leint16_t le_val)
|
||||
*/
|
||||
static inline beint64_t cpu_to_be64(uint64_t native)
|
||||
{
|
||||
return CPU_TO_BE64(native);
|
||||
return CPU_TO_BE64(native);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -305,7 +305,7 @@ static inline beint64_t cpu_to_be64(uint64_t native)
|
||||
*/
|
||||
static inline beint32_t cpu_to_be32(uint32_t native)
|
||||
{
|
||||
return CPU_TO_BE32(native);
|
||||
return CPU_TO_BE32(native);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -314,7 +314,7 @@ static inline beint32_t cpu_to_be32(uint32_t native)
|
||||
*/
|
||||
static inline beint16_t cpu_to_be16(uint16_t native)
|
||||
{
|
||||
return CPU_TO_BE16(native);
|
||||
return CPU_TO_BE16(native);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,7 +323,7 @@ static inline beint16_t cpu_to_be16(uint16_t native)
|
||||
*/
|
||||
static inline uint64_t be64_to_cpu(beint64_t be_val)
|
||||
{
|
||||
return BE64_TO_CPU(be_val);
|
||||
return BE64_TO_CPU(be_val);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -332,7 +332,7 @@ static inline uint64_t be64_to_cpu(beint64_t be_val)
|
||||
*/
|
||||
static inline uint32_t be32_to_cpu(beint32_t be_val)
|
||||
{
|
||||
return BE32_TO_CPU(be_val);
|
||||
return BE32_TO_CPU(be_val);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -341,7 +341,7 @@ static inline uint32_t be32_to_cpu(beint32_t be_val)
|
||||
*/
|
||||
static inline uint16_t be16_to_cpu(beint16_t be_val)
|
||||
{
|
||||
return BE16_TO_CPU(be_val);
|
||||
return BE16_TO_CPU(be_val);
|
||||
}
|
||||
|
||||
/* Whichever they include first, they get these definitions. */
|
||||
@@ -39,6 +39,15 @@ bool hex_decode(const char *str, size_t slen, void *buf, size_t bufsize)
|
||||
return slen == 0 && bufsize == 0;
|
||||
}
|
||||
|
||||
static char hexchar(unsigned int val)
|
||||
{
|
||||
if (val < 10)
|
||||
return '0' + val;
|
||||
if (val < 16)
|
||||
return 'a' + val - 10;
|
||||
abort();
|
||||
}
|
||||
|
||||
bool hex_encode(const void *buf, size_t bufsize, char *dest, size_t destsize)
|
||||
{
|
||||
size_t i;
|
||||
73
damus-c/hex.h
Normal file
73
damus-c/hex.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/* CC0 (Public domain) - see LICENSE file for details */
|
||||
#ifndef CCAN_HEX_H
|
||||
#define CCAN_HEX_H
|
||||
#include "config.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* hex_decode - Unpack a hex string.
|
||||
* @str: the hexadecimal string
|
||||
* @slen: the length of @str
|
||||
* @buf: the buffer to write the data into
|
||||
* @bufsize: the length of
|
||||
*
|
||||
* Returns false if there are any characters which aren't 0-9, a-f or A-F,
|
||||
* of the string wasn't the right length for @bufsize.
|
||||
*
|
||||
* Example:
|
||||
* unsigned char data[20];
|
||||
*
|
||||
* if (!hex_decode(argv[1], strlen(argv[1]), data, 20))
|
||||
* printf("String is malformed!\n");
|
||||
*/
|
||||
bool hex_decode(const char *str, size_t slen, void *buf, size_t bufsize);
|
||||
|
||||
/**
|
||||
* hex_encode - Create a nul-terminated hex string
|
||||
* @buf: the buffer to read the data from
|
||||
* @bufsize: the length of buf
|
||||
* @dest: the string to fill
|
||||
* @destsize: the max size of the string
|
||||
*
|
||||
* Returns true if the string, including terminator, fit in @destsize;
|
||||
*
|
||||
* Example:
|
||||
* unsigned char buf[] = { 0x1F, 0x2F };
|
||||
* char str[5];
|
||||
*
|
||||
* if (!hex_encode(buf, sizeof(buf), str, sizeof(str)))
|
||||
* abort();
|
||||
*/
|
||||
bool hex_encode(const void *buf, size_t bufsize, char *dest, size_t destsize);
|
||||
|
||||
/**
|
||||
* hex_str_size - Calculate how big a nul-terminated hex string is
|
||||
* @bytes: bytes of data to represent
|
||||
*
|
||||
* Example:
|
||||
* unsigned char buf[] = { 0x1F, 0x2F };
|
||||
* char str[hex_str_size(sizeof(buf))];
|
||||
*
|
||||
* hex_encode(buf, sizeof(buf), str, sizeof(str));
|
||||
*/
|
||||
static inline size_t hex_str_size(size_t bytes)
|
||||
{
|
||||
return 2 * bytes + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* hex_data_size - Calculate how many bytes of data in a hex string
|
||||
* @strlen: the length of the string (with or without NUL)
|
||||
*
|
||||
* Example:
|
||||
* const char str[] = "1F2F";
|
||||
* unsigned char buf[hex_data_size(sizeof(str))];
|
||||
*
|
||||
* hex_decode(str, strlen(str), buf, sizeof(buf));
|
||||
*/
|
||||
static inline size_t hex_data_size(size_t strlen)
|
||||
{
|
||||
return strlen / 2;
|
||||
}
|
||||
#endif /* CCAN_HEX_H */
|
||||
@@ -52,13 +52,9 @@
|
||||
*/
|
||||
#define unlikely(cond) __builtin_expect(!!(cond), 0)
|
||||
#else
|
||||
#ifndef likely
|
||||
#define likely(cond) (!!(cond))
|
||||
#endif
|
||||
#ifndef unlikely
|
||||
#define unlikely(cond) (!!(cond))
|
||||
#endif
|
||||
#endif
|
||||
#else /* CCAN_LIKELY_DEBUG versions */
|
||||
#include <ccan/str/str.h>
|
||||
|
||||
295
damus-c/nostr_bech32.c
Normal file
295
damus-c/nostr_bech32.c
Normal file
@@ -0,0 +1,295 @@
|
||||
//
|
||||
// nostr_bech32.c
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-04-09.
|
||||
//
|
||||
|
||||
#include "nostr_bech32.h"
|
||||
#include <stdlib.h>
|
||||
#include "cursor.h"
|
||||
#include "bech32.h"
|
||||
|
||||
#define MAX_TLVS 16
|
||||
|
||||
#define TLV_SPECIAL 0
|
||||
#define TLV_RELAY 1
|
||||
#define TLV_AUTHOR 2
|
||||
#define TLV_KIND 3
|
||||
#define TLV_KNOWN_TLVS 4
|
||||
|
||||
struct nostr_tlv {
|
||||
u8 type;
|
||||
u8 len;
|
||||
const u8 *value;
|
||||
};
|
||||
|
||||
struct nostr_tlvs {
|
||||
struct nostr_tlv tlvs[MAX_TLVS];
|
||||
int num_tlvs;
|
||||
};
|
||||
|
||||
static int parse_nostr_tlv(struct cursor *cur, struct nostr_tlv *tlv) {
|
||||
// get the tlv tag
|
||||
if (!pull_byte(cur, &tlv->type))
|
||||
return 0;
|
||||
|
||||
// unknown, fail!
|
||||
if (tlv->type >= TLV_KNOWN_TLVS)
|
||||
return 0;
|
||||
|
||||
// get the length
|
||||
if (!pull_byte(cur, &tlv->len))
|
||||
return 0;
|
||||
|
||||
// is the reported length greater then our buffer? if so fail
|
||||
if (cur->p + tlv->len > cur->end)
|
||||
return 0;
|
||||
|
||||
tlv->value = cur->p;
|
||||
cur->p += tlv->len;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int parse_nostr_tlvs(struct cursor *cur, struct nostr_tlvs *tlvs) {
|
||||
int i;
|
||||
tlvs->num_tlvs = 0;
|
||||
|
||||
for (i = 0; i < MAX_TLVS; i++) {
|
||||
if (parse_nostr_tlv(cur, &tlvs->tlvs[i])) {
|
||||
tlvs->num_tlvs++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tlvs->num_tlvs == 0)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int find_tlv(struct nostr_tlvs *tlvs, u8 type, struct nostr_tlv **tlv) {
|
||||
*tlv = NULL;
|
||||
|
||||
for (int i = 0; i < tlvs->num_tlvs; i++) {
|
||||
if (tlvs->tlvs[i].type == type) {
|
||||
*tlv = &tlvs->tlvs[i];
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_nostr_bech32_type(const char *prefix, enum nostr_bech32_type *type) {
|
||||
// Parse type
|
||||
if (strcmp(prefix, "note") == 0) {
|
||||
*type = NOSTR_BECH32_NOTE;
|
||||
return 1;
|
||||
} else if (strcmp(prefix, "npub") == 0) {
|
||||
*type = NOSTR_BECH32_NPUB;
|
||||
return 1;
|
||||
} else if (strcmp(prefix, "nprofile") == 0) {
|
||||
*type = NOSTR_BECH32_NPROFILE;
|
||||
return 1;
|
||||
} else if (strcmp(prefix, "nevent") == 0) {
|
||||
*type = NOSTR_BECH32_NEVENT;
|
||||
return 1;
|
||||
} else if (strcmp(prefix, "nrelay") == 0) {
|
||||
*type = NOSTR_BECH32_NRELAY;
|
||||
return 1;
|
||||
} else if (strcmp(prefix, "naddr") == 0) {
|
||||
*type = NOSTR_BECH32_NADDR;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_nostr_bech32_note(struct cursor *cur, struct bech32_note *note) {
|
||||
return pull_bytes(cur, 32, ¬e->event_id);
|
||||
}
|
||||
|
||||
static int parse_nostr_bech32_npub(struct cursor *cur, struct bech32_npub *npub) {
|
||||
return pull_bytes(cur, 32, &npub->pubkey);
|
||||
}
|
||||
|
||||
static int tlvs_to_relays(struct nostr_tlvs *tlvs, struct relays *relays) {
|
||||
struct nostr_tlv *tlv;
|
||||
struct str_block *str;
|
||||
|
||||
relays->num_relays = 0;
|
||||
|
||||
for (int i = 0; i < tlvs->num_tlvs; i++) {
|
||||
tlv = &tlvs->tlvs[i];
|
||||
if (tlv->type != TLV_RELAY)
|
||||
continue;
|
||||
|
||||
if (relays->num_relays + 1 > MAX_RELAYS)
|
||||
break;
|
||||
|
||||
str = &relays->relays[relays->num_relays++];
|
||||
str->start = (const char*)tlv->value;
|
||||
str->end = (const char*)(tlv->value + tlv->len);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int parse_nostr_bech32_nevent(struct cursor *cur, struct bech32_nevent *nevent) {
|
||||
struct nostr_tlvs tlvs;
|
||||
struct nostr_tlv *tlv;
|
||||
|
||||
if (!parse_nostr_tlvs(cur, &tlvs))
|
||||
return 0;
|
||||
|
||||
if (!find_tlv(&tlvs, TLV_SPECIAL, &tlv))
|
||||
return 0;
|
||||
|
||||
if (tlv->len != 32)
|
||||
return 0;
|
||||
|
||||
nevent->event_id = tlv->value;
|
||||
|
||||
if (find_tlv(&tlvs, TLV_AUTHOR, &tlv)) {
|
||||
nevent->pubkey = tlv->value;
|
||||
} else {
|
||||
nevent->pubkey = NULL;
|
||||
}
|
||||
|
||||
return tlvs_to_relays(&tlvs, &nevent->relays);
|
||||
}
|
||||
|
||||
static int parse_nostr_bech32_naddr(struct cursor *cur, struct bech32_naddr *naddr) {
|
||||
struct nostr_tlvs tlvs;
|
||||
struct nostr_tlv *tlv;
|
||||
|
||||
if (!parse_nostr_tlvs(cur, &tlvs))
|
||||
return 0;
|
||||
|
||||
if (!find_tlv(&tlvs, TLV_SPECIAL, &tlv))
|
||||
return 0;
|
||||
|
||||
naddr->identifier.start = (const char*)tlv->value;
|
||||
naddr->identifier.end = (const char*)tlv->value + tlv->len;
|
||||
|
||||
if (!find_tlv(&tlvs, TLV_AUTHOR, &tlv))
|
||||
return 0;
|
||||
|
||||
naddr->pubkey = tlv->value;
|
||||
|
||||
return tlvs_to_relays(&tlvs, &naddr->relays);
|
||||
}
|
||||
|
||||
static int parse_nostr_bech32_nprofile(struct cursor *cur, struct bech32_nprofile *nprofile) {
|
||||
struct nostr_tlvs tlvs;
|
||||
struct nostr_tlv *tlv;
|
||||
|
||||
if (!parse_nostr_tlvs(cur, &tlvs))
|
||||
return 0;
|
||||
|
||||
if (!find_tlv(&tlvs, TLV_SPECIAL, &tlv))
|
||||
return 0;
|
||||
|
||||
if (tlv->len != 32)
|
||||
return 0;
|
||||
|
||||
nprofile->pubkey = tlv->value;
|
||||
|
||||
return tlvs_to_relays(&tlvs, &nprofile->relays);
|
||||
}
|
||||
|
||||
static int parse_nostr_bech32_nrelay(struct cursor *cur, struct bech32_nrelay *nrelay) {
|
||||
struct nostr_tlvs tlvs;
|
||||
struct nostr_tlv *tlv;
|
||||
|
||||
if (!parse_nostr_tlvs(cur, &tlvs))
|
||||
return 0;
|
||||
|
||||
if (!find_tlv(&tlvs, TLV_SPECIAL, &tlv))
|
||||
return 0;
|
||||
|
||||
nrelay->relay.start = (const char*)tlv->value;
|
||||
nrelay->relay.end = (const char*)tlv->value + tlv->len;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int parse_nostr_bech32(struct cursor *cur, struct nostr_bech32 *obj) {
|
||||
const u8 *start, *end;
|
||||
|
||||
start = cur->p;
|
||||
|
||||
if (!consume_until_non_alphanumeric(cur, 1)) {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
|
||||
end = cur->p;
|
||||
|
||||
size_t data_len;
|
||||
size_t input_len = end - start;
|
||||
if (input_len < 10 || input_len > 10000) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
obj->buffer = malloc(input_len * 2);
|
||||
if (!obj->buffer)
|
||||
return 0;
|
||||
|
||||
u8 data[input_len];
|
||||
char prefix[input_len];
|
||||
|
||||
if (bech32_decode_len(prefix, data, &data_len, (const char*)start, input_len) == BECH32_ENCODING_NONE) {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
|
||||
obj->buflen = 0;
|
||||
if (!bech32_convert_bits(obj->buffer, &obj->buflen, 8, data, data_len, 5, 0)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!parse_nostr_bech32_type(prefix, &obj->type)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
struct cursor bcur;
|
||||
make_cursor(&bcur, obj->buffer, obj->buflen);
|
||||
|
||||
switch (obj->type) {
|
||||
case NOSTR_BECH32_NOTE:
|
||||
if (!parse_nostr_bech32_note(&bcur, &obj->data.note))
|
||||
goto fail;
|
||||
break;
|
||||
case NOSTR_BECH32_NPUB:
|
||||
if (!parse_nostr_bech32_npub(&bcur, &obj->data.npub))
|
||||
goto fail;
|
||||
break;
|
||||
case NOSTR_BECH32_NEVENT:
|
||||
if (!parse_nostr_bech32_nevent(&bcur, &obj->data.nevent))
|
||||
goto fail;
|
||||
break;
|
||||
case NOSTR_BECH32_NADDR:
|
||||
if (!parse_nostr_bech32_naddr(&bcur, &obj->data.naddr))
|
||||
goto fail;
|
||||
break;
|
||||
case NOSTR_BECH32_NPROFILE:
|
||||
if (!parse_nostr_bech32_nprofile(&bcur, &obj->data.nprofile))
|
||||
goto fail;
|
||||
break;
|
||||
case NOSTR_BECH32_NRELAY:
|
||||
if (!parse_nostr_bech32_nrelay(&bcur, &obj->data.nrelay))
|
||||
goto fail;
|
||||
break;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
free(obj->buffer);
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
78
damus-c/nostr_bech32.h
Normal file
78
damus-c/nostr_bech32.h
Normal file
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// nostr_bech32.h
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-04-09.
|
||||
//
|
||||
|
||||
#ifndef nostr_bech32_h
|
||||
#define nostr_bech32_h
|
||||
|
||||
#include <stdio.h>
|
||||
#include "str_block.h"
|
||||
#include "cursor.h"
|
||||
typedef unsigned char u8;
|
||||
#define MAX_RELAYS 10
|
||||
|
||||
struct relays {
|
||||
struct str_block relays[MAX_RELAYS];
|
||||
int num_relays;
|
||||
};
|
||||
|
||||
enum nostr_bech32_type {
|
||||
NOSTR_BECH32_NOTE = 1,
|
||||
NOSTR_BECH32_NPUB = 2,
|
||||
NOSTR_BECH32_NPROFILE = 3,
|
||||
NOSTR_BECH32_NEVENT = 4,
|
||||
NOSTR_BECH32_NRELAY = 5,
|
||||
NOSTR_BECH32_NADDR = 6,
|
||||
};
|
||||
|
||||
struct bech32_note {
|
||||
const u8 *event_id;
|
||||
};
|
||||
|
||||
struct bech32_npub {
|
||||
const u8 *pubkey;
|
||||
};
|
||||
|
||||
struct bech32_nevent {
|
||||
struct relays relays;
|
||||
const u8 *event_id;
|
||||
const u8 *pubkey; // optional
|
||||
};
|
||||
|
||||
struct bech32_nprofile {
|
||||
struct relays relays;
|
||||
const u8 *pubkey;
|
||||
};
|
||||
|
||||
struct bech32_naddr {
|
||||
struct relays relays;
|
||||
struct str_block identifier;
|
||||
const u8 *pubkey;
|
||||
};
|
||||
|
||||
struct bech32_nrelay {
|
||||
struct str_block relay;
|
||||
};
|
||||
|
||||
typedef struct nostr_bech32 {
|
||||
enum nostr_bech32_type type;
|
||||
u8 *buffer; // holds strings and tlv stuff
|
||||
size_t buflen;
|
||||
|
||||
union {
|
||||
struct bech32_note note;
|
||||
struct bech32_npub npub;
|
||||
struct bech32_nevent nevent;
|
||||
struct bech32_nprofile nprofile;
|
||||
struct bech32_naddr naddr;
|
||||
struct bech32_nrelay nrelay;
|
||||
} data;
|
||||
} nostr_bech32_t;
|
||||
|
||||
|
||||
int parse_nostr_bech32(struct cursor *cur, struct nostr_bech32 *obj);
|
||||
|
||||
#endif /* nostr_bech32_h */
|
||||
308
damus-c/sha256.c
Normal file
308
damus-c/sha256.c
Normal file
@@ -0,0 +1,308 @@
|
||||
/* MIT (BSD) license - see LICENSE file for details */
|
||||
/* SHA256 core code translated from the Bitcoin project's C++:
|
||||
*
|
||||
* src/crypto/sha256.cpp commit 417532c8acb93c36c2b6fd052b7c11b6a2906aa2
|
||||
* Copyright (c) 2014 The Bitcoin Core developers
|
||||
* Distributed under the MIT software license, see the accompanying
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
*/
|
||||
#include "sha256.h"
|
||||
#include "compiler.h"
|
||||
#include "endian.h"
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
static void invalidate_sha256(struct sha256_ctx *ctx)
|
||||
{
|
||||
#ifdef CCAN_CRYPTO_SHA256_USE_OPENSSL
|
||||
ctx->c.md_len = 0;
|
||||
#else
|
||||
ctx->bytes = (size_t)-1;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void check_sha256(struct sha256_ctx *ctx UNUSED)
|
||||
{
|
||||
#ifdef CCAN_CRYPTO_SHA256_USE_OPENSSL
|
||||
assert(ctx->c.md_len != 0);
|
||||
#else
|
||||
assert(ctx->bytes != (size_t)-1);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef CCAN_CRYPTO_SHA256_USE_OPENSSL
|
||||
void sha256_init(struct sha256_ctx *ctx)
|
||||
{
|
||||
SHA256_Init(&ctx->c);
|
||||
}
|
||||
|
||||
void sha256_update(struct sha256_ctx *ctx, const void *p, size_t size)
|
||||
{
|
||||
check_sha256(ctx);
|
||||
SHA256_Update(&ctx->c, p, size);
|
||||
}
|
||||
|
||||
void sha256_done(struct sha256_ctx *ctx, struct sha256 *res)
|
||||
{
|
||||
SHA256_Final(res->u.u8, &ctx->c);
|
||||
invalidate_sha256(ctx);
|
||||
}
|
||||
#else
|
||||
static uint32_t Ch(uint32_t x, uint32_t y, uint32_t z)
|
||||
{
|
||||
return z ^ (x & (y ^ z));
|
||||
}
|
||||
static uint32_t Maj(uint32_t x, uint32_t y, uint32_t z)
|
||||
{
|
||||
return (x & y) | (z & (x | y));
|
||||
}
|
||||
static uint32_t Sigma0(uint32_t x)
|
||||
{
|
||||
return (x >> 2 | x << 30) ^ (x >> 13 | x << 19) ^ (x >> 22 | x << 10);
|
||||
}
|
||||
static uint32_t Sigma1(uint32_t x)
|
||||
{
|
||||
return (x >> 6 | x << 26) ^ (x >> 11 | x << 21) ^ (x >> 25 | x << 7);
|
||||
}
|
||||
static uint32_t sigma0(uint32_t x)
|
||||
{
|
||||
return (x >> 7 | x << 25) ^ (x >> 18 | x << 14) ^ (x >> 3);
|
||||
}
|
||||
static uint32_t sigma1(uint32_t x)
|
||||
{
|
||||
return (x >> 17 | x << 15) ^ (x >> 19 | x << 13) ^ (x >> 10);
|
||||
}
|
||||
|
||||
/** One round of SHA-256. */
|
||||
static void Round(uint32_t a, uint32_t b, uint32_t c, uint32_t *d, uint32_t e, uint32_t f, uint32_t g, uint32_t *h, uint32_t k, uint32_t w)
|
||||
{
|
||||
uint32_t t1 = *h + Sigma1(e) + Ch(e, f, g) + k + w;
|
||||
uint32_t t2 = Sigma0(a) + Maj(a, b, c);
|
||||
*d += t1;
|
||||
*h = t1 + t2;
|
||||
}
|
||||
|
||||
/** Perform one SHA-256 transformation, processing a 64-byte chunk. */
|
||||
static void Transform(uint32_t *s, const uint32_t *chunk)
|
||||
{
|
||||
uint32_t a = s[0], b = s[1], c = s[2], d = s[3], e = s[4], f = s[5], g = s[6], h = s[7];
|
||||
uint32_t w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15;
|
||||
|
||||
Round(a, b, c, &d, e, f, g, &h, 0x428a2f98, w0 = be32_to_cpu(chunk[0]));
|
||||
Round(h, a, b, &c, d, e, f, &g, 0x71374491, w1 = be32_to_cpu(chunk[1]));
|
||||
Round(g, h, a, &b, c, d, e, &f, 0xb5c0fbcf, w2 = be32_to_cpu(chunk[2]));
|
||||
Round(f, g, h, &a, b, c, d, &e, 0xe9b5dba5, w3 = be32_to_cpu(chunk[3]));
|
||||
Round(e, f, g, &h, a, b, c, &d, 0x3956c25b, w4 = be32_to_cpu(chunk[4]));
|
||||
Round(d, e, f, &g, h, a, b, &c, 0x59f111f1, w5 = be32_to_cpu(chunk[5]));
|
||||
Round(c, d, e, &f, g, h, a, &b, 0x923f82a4, w6 = be32_to_cpu(chunk[6]));
|
||||
Round(b, c, d, &e, f, g, h, &a, 0xab1c5ed5, w7 = be32_to_cpu(chunk[7]));
|
||||
Round(a, b, c, &d, e, f, g, &h, 0xd807aa98, w8 = be32_to_cpu(chunk[8]));
|
||||
Round(h, a, b, &c, d, e, f, &g, 0x12835b01, w9 = be32_to_cpu(chunk[9]));
|
||||
Round(g, h, a, &b, c, d, e, &f, 0x243185be, w10 = be32_to_cpu(chunk[10]));
|
||||
Round(f, g, h, &a, b, c, d, &e, 0x550c7dc3, w11 = be32_to_cpu(chunk[11]));
|
||||
Round(e, f, g, &h, a, b, c, &d, 0x72be5d74, w12 = be32_to_cpu(chunk[12]));
|
||||
Round(d, e, f, &g, h, a, b, &c, 0x80deb1fe, w13 = be32_to_cpu(chunk[13]));
|
||||
Round(c, d, e, &f, g, h, a, &b, 0x9bdc06a7, w14 = be32_to_cpu(chunk[14]));
|
||||
Round(b, c, d, &e, f, g, h, &a, 0xc19bf174, w15 = be32_to_cpu(chunk[15]));
|
||||
|
||||
Round(a, b, c, &d, e, f, g, &h, 0xe49b69c1, w0 += sigma1(w14) + w9 + sigma0(w1));
|
||||
Round(h, a, b, &c, d, e, f, &g, 0xefbe4786, w1 += sigma1(w15) + w10 + sigma0(w2));
|
||||
Round(g, h, a, &b, c, d, e, &f, 0x0fc19dc6, w2 += sigma1(w0) + w11 + sigma0(w3));
|
||||
Round(f, g, h, &a, b, c, d, &e, 0x240ca1cc, w3 += sigma1(w1) + w12 + sigma0(w4));
|
||||
Round(e, f, g, &h, a, b, c, &d, 0x2de92c6f, w4 += sigma1(w2) + w13 + sigma0(w5));
|
||||
Round(d, e, f, &g, h, a, b, &c, 0x4a7484aa, w5 += sigma1(w3) + w14 + sigma0(w6));
|
||||
Round(c, d, e, &f, g, h, a, &b, 0x5cb0a9dc, w6 += sigma1(w4) + w15 + sigma0(w7));
|
||||
Round(b, c, d, &e, f, g, h, &a, 0x76f988da, w7 += sigma1(w5) + w0 + sigma0(w8));
|
||||
Round(a, b, c, &d, e, f, g, &h, 0x983e5152, w8 += sigma1(w6) + w1 + sigma0(w9));
|
||||
Round(h, a, b, &c, d, e, f, &g, 0xa831c66d, w9 += sigma1(w7) + w2 + sigma0(w10));
|
||||
Round(g, h, a, &b, c, d, e, &f, 0xb00327c8, w10 += sigma1(w8) + w3 + sigma0(w11));
|
||||
Round(f, g, h, &a, b, c, d, &e, 0xbf597fc7, w11 += sigma1(w9) + w4 + sigma0(w12));
|
||||
Round(e, f, g, &h, a, b, c, &d, 0xc6e00bf3, w12 += sigma1(w10) + w5 + sigma0(w13));
|
||||
Round(d, e, f, &g, h, a, b, &c, 0xd5a79147, w13 += sigma1(w11) + w6 + sigma0(w14));
|
||||
Round(c, d, e, &f, g, h, a, &b, 0x06ca6351, w14 += sigma1(w12) + w7 + sigma0(w15));
|
||||
Round(b, c, d, &e, f, g, h, &a, 0x14292967, w15 += sigma1(w13) + w8 + sigma0(w0));
|
||||
|
||||
Round(a, b, c, &d, e, f, g, &h, 0x27b70a85, w0 += sigma1(w14) + w9 + sigma0(w1));
|
||||
Round(h, a, b, &c, d, e, f, &g, 0x2e1b2138, w1 += sigma1(w15) + w10 + sigma0(w2));
|
||||
Round(g, h, a, &b, c, d, e, &f, 0x4d2c6dfc, w2 += sigma1(w0) + w11 + sigma0(w3));
|
||||
Round(f, g, h, &a, b, c, d, &e, 0x53380d13, w3 += sigma1(w1) + w12 + sigma0(w4));
|
||||
Round(e, f, g, &h, a, b, c, &d, 0x650a7354, w4 += sigma1(w2) + w13 + sigma0(w5));
|
||||
Round(d, e, f, &g, h, a, b, &c, 0x766a0abb, w5 += sigma1(w3) + w14 + sigma0(w6));
|
||||
Round(c, d, e, &f, g, h, a, &b, 0x81c2c92e, w6 += sigma1(w4) + w15 + sigma0(w7));
|
||||
Round(b, c, d, &e, f, g, h, &a, 0x92722c85, w7 += sigma1(w5) + w0 + sigma0(w8));
|
||||
Round(a, b, c, &d, e, f, g, &h, 0xa2bfe8a1, w8 += sigma1(w6) + w1 + sigma0(w9));
|
||||
Round(h, a, b, &c, d, e, f, &g, 0xa81a664b, w9 += sigma1(w7) + w2 + sigma0(w10));
|
||||
Round(g, h, a, &b, c, d, e, &f, 0xc24b8b70, w10 += sigma1(w8) + w3 + sigma0(w11));
|
||||
Round(f, g, h, &a, b, c, d, &e, 0xc76c51a3, w11 += sigma1(w9) + w4 + sigma0(w12));
|
||||
Round(e, f, g, &h, a, b, c, &d, 0xd192e819, w12 += sigma1(w10) + w5 + sigma0(w13));
|
||||
Round(d, e, f, &g, h, a, b, &c, 0xd6990624, w13 += sigma1(w11) + w6 + sigma0(w14));
|
||||
Round(c, d, e, &f, g, h, a, &b, 0xf40e3585, w14 += sigma1(w12) + w7 + sigma0(w15));
|
||||
Round(b, c, d, &e, f, g, h, &a, 0x106aa070, w15 += sigma1(w13) + w8 + sigma0(w0));
|
||||
|
||||
Round(a, b, c, &d, e, f, g, &h, 0x19a4c116, w0 += sigma1(w14) + w9 + sigma0(w1));
|
||||
Round(h, a, b, &c, d, e, f, &g, 0x1e376c08, w1 += sigma1(w15) + w10 + sigma0(w2));
|
||||
Round(g, h, a, &b, c, d, e, &f, 0x2748774c, w2 += sigma1(w0) + w11 + sigma0(w3));
|
||||
Round(f, g, h, &a, b, c, d, &e, 0x34b0bcb5, w3 += sigma1(w1) + w12 + sigma0(w4));
|
||||
Round(e, f, g, &h, a, b, c, &d, 0x391c0cb3, w4 += sigma1(w2) + w13 + sigma0(w5));
|
||||
Round(d, e, f, &g, h, a, b, &c, 0x4ed8aa4a, w5 += sigma1(w3) + w14 + sigma0(w6));
|
||||
Round(c, d, e, &f, g, h, a, &b, 0x5b9cca4f, w6 += sigma1(w4) + w15 + sigma0(w7));
|
||||
Round(b, c, d, &e, f, g, h, &a, 0x682e6ff3, w7 += sigma1(w5) + w0 + sigma0(w8));
|
||||
Round(a, b, c, &d, e, f, g, &h, 0x748f82ee, w8 += sigma1(w6) + w1 + sigma0(w9));
|
||||
Round(h, a, b, &c, d, e, f, &g, 0x78a5636f, w9 += sigma1(w7) + w2 + sigma0(w10));
|
||||
Round(g, h, a, &b, c, d, e, &f, 0x84c87814, w10 += sigma1(w8) + w3 + sigma0(w11));
|
||||
Round(f, g, h, &a, b, c, d, &e, 0x8cc70208, w11 += sigma1(w9) + w4 + sigma0(w12));
|
||||
Round(e, f, g, &h, a, b, c, &d, 0x90befffa, w12 += sigma1(w10) + w5 + sigma0(w13));
|
||||
Round(d, e, f, &g, h, a, b, &c, 0xa4506ceb, w13 += sigma1(w11) + w6 + sigma0(w14));
|
||||
Round(c, d, e, &f, g, h, a, &b, 0xbef9a3f7, w14 + sigma1(w12) + w7 + sigma0(w15));
|
||||
Round(b, c, d, &e, f, g, h, &a, 0xc67178f2, w15 + sigma1(w13) + w8 + sigma0(w0));
|
||||
|
||||
s[0] += a;
|
||||
s[1] += b;
|
||||
s[2] += c;
|
||||
s[3] += d;
|
||||
s[4] += e;
|
||||
s[5] += f;
|
||||
s[6] += g;
|
||||
s[7] += h;
|
||||
}
|
||||
|
||||
static bool alignment_ok(const void *p UNUSED, size_t n UNUSED)
|
||||
{
|
||||
#if HAVE_UNALIGNED_ACCESS
|
||||
return true;
|
||||
#else
|
||||
return ((size_t)p % n == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void add(struct sha256_ctx *ctx, const void *p, size_t len)
|
||||
{
|
||||
const unsigned char *data = p;
|
||||
size_t bufsize = ctx->bytes % 64;
|
||||
|
||||
if (bufsize + len >= 64) {
|
||||
/* Fill the buffer, and process it. */
|
||||
memcpy(ctx->buf.u8 + bufsize, data, 64 - bufsize);
|
||||
ctx->bytes += 64 - bufsize;
|
||||
data += 64 - bufsize;
|
||||
len -= 64 - bufsize;
|
||||
Transform(ctx->s, ctx->buf.u32);
|
||||
bufsize = 0;
|
||||
}
|
||||
|
||||
while (len >= 64) {
|
||||
/* Process full chunks directly from the source. */
|
||||
if (alignment_ok(data, sizeof(uint32_t)))
|
||||
Transform(ctx->s, (const uint32_t *)data);
|
||||
else {
|
||||
memcpy(ctx->buf.u8, data, sizeof(ctx->buf));
|
||||
Transform(ctx->s, ctx->buf.u32);
|
||||
}
|
||||
ctx->bytes += 64;
|
||||
data += 64;
|
||||
len -= 64;
|
||||
}
|
||||
|
||||
if (len) {
|
||||
/* Fill the buffer with what remains. */
|
||||
memcpy(ctx->buf.u8 + bufsize, data, len);
|
||||
ctx->bytes += len;
|
||||
}
|
||||
}
|
||||
|
||||
void sha256_init(struct sha256_ctx *ctx)
|
||||
{
|
||||
struct sha256_ctx init = SHA256_INIT;
|
||||
*ctx = init;
|
||||
}
|
||||
|
||||
void sha256_update(struct sha256_ctx *ctx, const void *p, size_t size)
|
||||
{
|
||||
check_sha256(ctx);
|
||||
add(ctx, p, size);
|
||||
}
|
||||
|
||||
void sha256_done(struct sha256_ctx *ctx, struct sha256 *res)
|
||||
{
|
||||
static const unsigned char pad[64] = {0x80};
|
||||
uint64_t sizedesc;
|
||||
size_t i;
|
||||
|
||||
sizedesc = cpu_to_be64((uint64_t)ctx->bytes << 3);
|
||||
/* Add '1' bit to terminate, then all 0 bits, up to next block - 8. */
|
||||
add(ctx, pad, 1 + ((128 - 8 - (ctx->bytes % 64) - 1) % 64));
|
||||
/* Add number of bits of data (big endian) */
|
||||
add(ctx, &sizedesc, 8);
|
||||
for (i = 0; i < sizeof(ctx->s) / sizeof(ctx->s[0]); i++)
|
||||
res->u.u32[i] = cpu_to_be32(ctx->s[i]);
|
||||
invalidate_sha256(ctx);
|
||||
}
|
||||
#endif
|
||||
|
||||
void sha256(struct sha256 *sha, const void *p, size_t size)
|
||||
{
|
||||
struct sha256_ctx ctx;
|
||||
|
||||
sha256_init(&ctx);
|
||||
sha256_update(&ctx, p, size);
|
||||
sha256_done(&ctx, sha);
|
||||
}
|
||||
|
||||
void sha256_u8(struct sha256_ctx *ctx, uint8_t v)
|
||||
{
|
||||
sha256_update(ctx, &v, sizeof(v));
|
||||
}
|
||||
|
||||
void sha256_u16(struct sha256_ctx *ctx, uint16_t v)
|
||||
{
|
||||
sha256_update(ctx, &v, sizeof(v));
|
||||
}
|
||||
|
||||
void sha256_u32(struct sha256_ctx *ctx, uint32_t v)
|
||||
{
|
||||
sha256_update(ctx, &v, sizeof(v));
|
||||
}
|
||||
|
||||
void sha256_u64(struct sha256_ctx *ctx, uint64_t v)
|
||||
{
|
||||
sha256_update(ctx, &v, sizeof(v));
|
||||
}
|
||||
|
||||
/* Add as little-endian */
|
||||
void sha256_le16(struct sha256_ctx *ctx, uint16_t v)
|
||||
{
|
||||
leint16_t lev = cpu_to_le16(v);
|
||||
sha256_update(ctx, &lev, sizeof(lev));
|
||||
}
|
||||
|
||||
void sha256_le32(struct sha256_ctx *ctx, uint32_t v)
|
||||
{
|
||||
leint32_t lev = cpu_to_le32(v);
|
||||
sha256_update(ctx, &lev, sizeof(lev));
|
||||
}
|
||||
|
||||
void sha256_le64(struct sha256_ctx *ctx, uint64_t v)
|
||||
{
|
||||
leint64_t lev = cpu_to_le64(v);
|
||||
sha256_update(ctx, &lev, sizeof(lev));
|
||||
}
|
||||
|
||||
/* Add as big-endian */
|
||||
void sha256_be16(struct sha256_ctx *ctx, uint16_t v)
|
||||
{
|
||||
beint16_t bev = cpu_to_be16(v);
|
||||
sha256_update(ctx, &bev, sizeof(bev));
|
||||
}
|
||||
|
||||
void sha256_be32(struct sha256_ctx *ctx, uint32_t v)
|
||||
{
|
||||
beint32_t bev = cpu_to_be32(v);
|
||||
sha256_update(ctx, &bev, sizeof(bev));
|
||||
}
|
||||
|
||||
void sha256_be64(struct sha256_ctx *ctx, uint64_t v)
|
||||
{
|
||||
beint64_t bev = cpu_to_be64(v);
|
||||
sha256_update(ctx, &bev, sizeof(bev));
|
||||
}
|
||||
@@ -20,17 +20,17 @@
|
||||
* Other fields may be added to the union in future.
|
||||
*/
|
||||
struct sha256 {
|
||||
union {
|
||||
uint32_t u32[8];
|
||||
unsigned char u8[32];
|
||||
} u;
|
||||
union {
|
||||
uint32_t u32[8];
|
||||
unsigned char u8[32];
|
||||
} u;
|
||||
};
|
||||
|
||||
/**
|
||||
* sha256 - return sha256 of an object.
|
||||
* @sha256: the sha256 to fill in
|
||||
* @p: pointer to memory,
|
||||
* @size: the number of bytes pointed to by @p
|
||||
* @size: the number of bytes pointed to by
|
||||
*
|
||||
* The bytes pointed to by @p is SHA256 hashed into @sha256. This is
|
||||
* equivalent to sha256_init(), sha256_update() then sha256_done().
|
||||
@@ -42,14 +42,14 @@ void sha256(struct sha256 *sha, const void *p, size_t size);
|
||||
*/
|
||||
struct sha256_ctx {
|
||||
#ifdef CCAN_CRYPTO_SHA256_USE_OPENSSL
|
||||
SHA256_CTX c;
|
||||
SHA256_CTX c;
|
||||
#else
|
||||
uint32_t s[8];
|
||||
union {
|
||||
uint32_t u32[16];
|
||||
unsigned char u8[64];
|
||||
} buf;
|
||||
size_t bytes;
|
||||
uint32_t s[8];
|
||||
union {
|
||||
uint32_t u32[16];
|
||||
unsigned char u8[64];
|
||||
} buf;
|
||||
size_t bytes;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -66,13 +66,13 @@ struct sha256_ctx {
|
||||
* Example:
|
||||
* static void hash_all(const char **arr, struct sha256 *hash)
|
||||
* {
|
||||
* size_t i;
|
||||
* struct sha256_ctx ctx;
|
||||
* size_t i;
|
||||
* struct sha256_ctx ctx;
|
||||
*
|
||||
* sha256_init(&ctx);
|
||||
* for (i = 0; arr[i]; i++)
|
||||
* sha256_update(&ctx, arr[i], strlen(arr[i]));
|
||||
* sha256_done(&ctx, hash);
|
||||
* sha256_init(&ctx);
|
||||
* for (i = 0; arr[i]; i++)
|
||||
* sha256_update(&ctx, arr[i], strlen(arr[i]));
|
||||
* sha256_done(&ctx, hash);
|
||||
* }
|
||||
*/
|
||||
void sha256_init(struct sha256_ctx *ctx);
|
||||
@@ -86,33 +86,33 @@ void sha256_init(struct sha256_ctx *ctx);
|
||||
* Example:
|
||||
* static void hash_all(const char **arr, struct sha256 *hash)
|
||||
* {
|
||||
* size_t i;
|
||||
* struct sha256_ctx ctx = SHA256_INIT;
|
||||
* size_t i;
|
||||
* struct sha256_ctx ctx = SHA256_INIT;
|
||||
*
|
||||
* for (i = 0; arr[i]; i++)
|
||||
* sha256_update(&ctx, arr[i], strlen(arr[i]));
|
||||
* sha256_done(&ctx, hash);
|
||||
* for (i = 0; arr[i]; i++)
|
||||
* sha256_update(&ctx, arr[i], strlen(arr[i]));
|
||||
* sha256_done(&ctx, hash);
|
||||
* }
|
||||
*/
|
||||
#ifdef CCAN_CRYPTO_SHA256_USE_OPENSSL
|
||||
#define SHA256_INIT \
|
||||
{ { { 0x6a09e667ul, 0xbb67ae85ul, 0x3c6ef372ul, 0xa54ff53aul, \
|
||||
0x510e527ful, 0x9b05688cul, 0x1f83d9abul, 0x5be0cd19ul }, \
|
||||
0x0, 0x0, \
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \
|
||||
0x0, 0x20 } }
|
||||
#define SHA256_INIT \
|
||||
{ { { 0x6a09e667ul, 0xbb67ae85ul, 0x3c6ef372ul, 0xa54ff53aul, \
|
||||
0x510e527ful, 0x9b05688cul, 0x1f83d9abul, 0x5be0cd19ul }, \
|
||||
0x0, 0x0, \
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \
|
||||
0x0, 0x20 } }
|
||||
#else
|
||||
#define SHA256_INIT \
|
||||
{ { 0x6a09e667ul, 0xbb67ae85ul, 0x3c6ef372ul, 0xa54ff53aul, \
|
||||
0x510e527ful, 0x9b05688cul, 0x1f83d9abul, 0x5be0cd19ul }, \
|
||||
{ { 0 } }, 0 }
|
||||
#define SHA256_INIT \
|
||||
{ { 0x6a09e667ul, 0xbb67ae85ul, 0x3c6ef372ul, 0xa54ff53aul, \
|
||||
0x510e527ful, 0x9b05688cul, 0x1f83d9abul, 0x5be0cd19ul }, \
|
||||
{ { 0 } }, 0 }
|
||||
#endif
|
||||
|
||||
/**
|
||||
* sha256_update - include some memory in the hash.
|
||||
* @ctx: the sha256_ctx to use
|
||||
* @p: pointer to memory,
|
||||
* @size: the number of bytes pointed to by @p
|
||||
* @size: the number of bytes pointed to by
|
||||
*
|
||||
* You can call this multiple times to hash more data, before calling
|
||||
* sha256_done().
|
||||
@@ -8,4 +8,9 @@
|
||||
#ifndef str_block_h
|
||||
#define str_block_h
|
||||
|
||||
typedef struct str_block {
|
||||
const char *start;
|
||||
const char *end;
|
||||
} str_block_t;
|
||||
|
||||
#endif /* str_block_h */
|
||||
7299
damus-c/wasm.c
7299
damus-c/wasm.c
File diff suppressed because it is too large
Load Diff
852
damus-c/wasm.h
852
damus-c/wasm.h
@@ -1,852 +0,0 @@
|
||||
|
||||
#ifndef PROTOVERSE_WASM_H
|
||||
#define PROTOVERSE_WASM_H
|
||||
|
||||
static const unsigned char WASM_MAGIC[] = {0,'a','s','m'};
|
||||
|
||||
#define WASM_VERSION 0x01
|
||||
#define MAX_U32_LEB128_BYTES 5
|
||||
#define MAX_U64_LEB128_BYTES 10
|
||||
#define MAX_CUSTOM_SECTIONS 32
|
||||
#define MAX_BUILTINS 64
|
||||
#define BUILTIN_SUSPEND 42
|
||||
|
||||
#define FUNC_TYPE_TAG 0x60
|
||||
|
||||
|
||||
#include "cursor.h"
|
||||
#include "error.h"
|
||||
|
||||
#ifdef NOINLINE
|
||||
#define INLINE __attribute__((noinline))
|
||||
#else
|
||||
#define INLINE inline
|
||||
#endif
|
||||
|
||||
|
||||
#define interp_error(p, fmt, ...) note_error(&((p)->errors), interp_codeptr(p), fmt, ##__VA_ARGS__)
|
||||
#define parse_err(p, fmt, ...) note_error(&((p)->errs), &(p)->cur, fmt, ##__VA_ARGS__)
|
||||
|
||||
#include "short_types.h"
|
||||
|
||||
enum valtype {
|
||||
val_i32 = 0x7F,
|
||||
val_i64 = 0x7E,
|
||||
val_f32 = 0x7D,
|
||||
val_f64 = 0x7C,
|
||||
val_ref_null = 0xD0,
|
||||
val_ref_func = 0x70,
|
||||
val_ref_extern = 0x6F,
|
||||
};
|
||||
|
||||
enum const_instr {
|
||||
ci_const_i32 = 0x41,
|
||||
ci_const_i64 = 0x42,
|
||||
ci_const_f32 = 0x43,
|
||||
ci_const_f64 = 0x44,
|
||||
ci_ref_null = 0xD0,
|
||||
ci_ref_func = 0xD2,
|
||||
ci_global_get = 0x23,
|
||||
ci_end = 0x0B,
|
||||
};
|
||||
|
||||
enum limit_type {
|
||||
limit_min = 0x00,
|
||||
limit_min_max = 0x01,
|
||||
};
|
||||
|
||||
struct limits {
|
||||
u32 min;
|
||||
u32 max;
|
||||
enum limit_type type;
|
||||
};
|
||||
|
||||
enum section_tag {
|
||||
section_custom,
|
||||
section_type,
|
||||
section_import,
|
||||
section_function,
|
||||
section_table,
|
||||
section_memory,
|
||||
section_global,
|
||||
section_export,
|
||||
section_start,
|
||||
section_element,
|
||||
section_code,
|
||||
section_data,
|
||||
section_data_count,
|
||||
section_name,
|
||||
num_sections,
|
||||
};
|
||||
|
||||
enum name_subsection_tag {
|
||||
name_subsection_module,
|
||||
name_subsection_funcs,
|
||||
name_subsection_locals,
|
||||
num_name_subsections,
|
||||
};
|
||||
|
||||
enum reftype {
|
||||
funcref = 0x70,
|
||||
externref = 0x6F,
|
||||
};
|
||||
|
||||
struct resulttype {
|
||||
unsigned char *valtypes; /* enum valtype */
|
||||
u32 num_valtypes;
|
||||
};
|
||||
|
||||
struct functype {
|
||||
struct resulttype params;
|
||||
struct resulttype result;
|
||||
};
|
||||
|
||||
struct table {
|
||||
enum reftype reftype;
|
||||
struct limits limits;
|
||||
};
|
||||
|
||||
struct tablesec {
|
||||
struct table *tables;
|
||||
u32 num_tables;
|
||||
};
|
||||
|
||||
enum elem_mode {
|
||||
elem_mode_passive,
|
||||
elem_mode_active,
|
||||
elem_mode_declarative,
|
||||
};
|
||||
|
||||
struct expr {
|
||||
u8 *code;
|
||||
u32 code_len;
|
||||
};
|
||||
|
||||
struct refval {
|
||||
u32 addr;
|
||||
};
|
||||
|
||||
struct table_inst {
|
||||
struct refval *refs;
|
||||
enum reftype reftype;
|
||||
u32 num_refs;
|
||||
};
|
||||
|
||||
struct numval {
|
||||
union {
|
||||
int i32;
|
||||
u32 u32;
|
||||
int64_t i64;
|
||||
uint64_t u64;
|
||||
float f32;
|
||||
double f64;
|
||||
};
|
||||
};
|
||||
|
||||
struct val {
|
||||
enum valtype type;
|
||||
union {
|
||||
struct numval num;
|
||||
struct refval ref;
|
||||
};
|
||||
};
|
||||
|
||||
struct elem_inst {
|
||||
struct val val;
|
||||
u16 elem;
|
||||
u16 init;
|
||||
};
|
||||
|
||||
struct elem {
|
||||
struct expr offset;
|
||||
u32 tableidx;
|
||||
struct expr *inits;
|
||||
u32 num_inits;
|
||||
enum elem_mode mode;
|
||||
enum reftype reftype;
|
||||
struct val val;
|
||||
};
|
||||
|
||||
struct customsec {
|
||||
const char *name;
|
||||
unsigned char *data;
|
||||
u32 data_len;
|
||||
};
|
||||
|
||||
struct elemsec {
|
||||
struct elem *elements;
|
||||
u32 num_elements;
|
||||
};
|
||||
|
||||
struct memsec {
|
||||
struct limits *mems; /* memtype */
|
||||
u32 num_mems;
|
||||
};
|
||||
|
||||
struct funcsec {
|
||||
u32 *type_indices;
|
||||
u32 num_indices;
|
||||
};
|
||||
|
||||
enum mut {
|
||||
mut_const,
|
||||
mut_var,
|
||||
};
|
||||
|
||||
struct globaltype {
|
||||
enum valtype valtype;
|
||||
enum mut mut;
|
||||
};
|
||||
|
||||
struct globalsec {
|
||||
struct global *globals;
|
||||
u32 num_globals;
|
||||
};
|
||||
|
||||
struct typesec {
|
||||
struct functype *functypes;
|
||||
u32 num_functypes;
|
||||
};
|
||||
|
||||
enum import_type {
|
||||
import_func,
|
||||
import_table,
|
||||
import_mem,
|
||||
import_global,
|
||||
};
|
||||
|
||||
struct importdesc {
|
||||
enum import_type type;
|
||||
union {
|
||||
u32 typeidx;
|
||||
struct limits tabletype;
|
||||
struct limits memtype;
|
||||
struct globaltype globaltype;
|
||||
};
|
||||
};
|
||||
|
||||
struct import {
|
||||
const char *module_name;
|
||||
const char *name;
|
||||
struct importdesc desc;
|
||||
int resolved_builtin;
|
||||
};
|
||||
|
||||
struct importsec {
|
||||
struct import *imports;
|
||||
u32 num_imports;
|
||||
};
|
||||
|
||||
struct global {
|
||||
struct globaltype type;
|
||||
struct expr init;
|
||||
struct val val;
|
||||
};
|
||||
|
||||
struct local_def {
|
||||
u32 num_types;
|
||||
enum valtype type;
|
||||
};
|
||||
|
||||
/* "code" */
|
||||
struct wasm_func {
|
||||
struct expr code;
|
||||
struct local_def *local_defs;
|
||||
u32 num_local_defs;
|
||||
};
|
||||
|
||||
enum func_type {
|
||||
func_type_wasm,
|
||||
func_type_builtin,
|
||||
};
|
||||
|
||||
struct func {
|
||||
union {
|
||||
struct wasm_func *wasm_func;
|
||||
struct builtin *builtin;
|
||||
};
|
||||
u32 num_locals;
|
||||
struct functype *functype;
|
||||
enum func_type type;
|
||||
const char *name;
|
||||
u32 idx;
|
||||
};
|
||||
|
||||
struct codesec {
|
||||
struct wasm_func *funcs;
|
||||
u32 num_funcs;
|
||||
};
|
||||
|
||||
enum exportdesc {
|
||||
export_func,
|
||||
export_table,
|
||||
export_mem,
|
||||
export_global,
|
||||
};
|
||||
|
||||
struct wexport {
|
||||
const char *name;
|
||||
u32 index;
|
||||
enum exportdesc desc;
|
||||
};
|
||||
|
||||
struct exportsec {
|
||||
struct wexport *exports;
|
||||
u32 num_exports;
|
||||
};
|
||||
|
||||
struct nameassoc {
|
||||
u32 index;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
struct namemap {
|
||||
struct nameassoc *names;
|
||||
u32 num_names;
|
||||
};
|
||||
|
||||
struct namesec {
|
||||
const char *module_name;
|
||||
struct namemap func_names;
|
||||
int parsed;
|
||||
};
|
||||
|
||||
struct wsection {
|
||||
enum section_tag tag;
|
||||
};
|
||||
|
||||
enum bulk_tag {
|
||||
i_memory_copy = 10,
|
||||
i_memory_fill = 11,
|
||||
i_table_init = 12,
|
||||
i_elem_drop = 13,
|
||||
i_table_copy = 14,
|
||||
i_table_grow = 15,
|
||||
i_table_size = 16,
|
||||
i_table_fill = 17,
|
||||
};
|
||||
|
||||
enum instr_tag {
|
||||
/* control instructions */
|
||||
i_unreachable = 0x00,
|
||||
i_nop = 0x01,
|
||||
i_block = 0x02,
|
||||
i_loop = 0x03,
|
||||
i_if = 0x04,
|
||||
i_else = 0x05,
|
||||
i_end = 0x0B,
|
||||
i_br = 0x0C,
|
||||
i_br_if = 0x0D,
|
||||
i_br_table = 0x0E,
|
||||
i_return = 0x0F,
|
||||
i_call = 0x10,
|
||||
i_call_indirect = 0x11,
|
||||
|
||||
/* parametric instructions */
|
||||
i_drop = 0x1A,
|
||||
i_select = 0x1B,
|
||||
i_selects = 0x1C,
|
||||
|
||||
/* variable instructions */
|
||||
i_local_get = 0x20,
|
||||
i_local_set = 0x21,
|
||||
i_local_tee = 0x22,
|
||||
i_global_get = 0x23,
|
||||
i_global_set = 0x24,
|
||||
i_table_get = 0x25,
|
||||
i_table_set = 0x26,
|
||||
|
||||
/* memory instructions */
|
||||
i_i32_load = 0x28,
|
||||
i_i64_load = 0x29,
|
||||
i_f32_load = 0x2A,
|
||||
i_f64_load = 0x2B,
|
||||
i_i32_load8_s = 0x2C,
|
||||
i_i32_load8_u = 0x2D,
|
||||
i_i32_load16_s = 0x2E,
|
||||
i_i32_load16_u = 0x2F,
|
||||
i_i64_load8_s = 0x30,
|
||||
i_i64_load8_u = 0x31,
|
||||
i_i64_load16_s = 0x32,
|
||||
i_i64_load16_u = 0x33,
|
||||
i_i64_load32_s = 0x34,
|
||||
i_i64_load32_u = 0x35,
|
||||
i_i32_store = 0x36,
|
||||
i_i64_store = 0x37,
|
||||
i_f32_store = 0x38,
|
||||
i_f64_store = 0x39,
|
||||
i_i32_store8 = 0x3A,
|
||||
i_i32_store16 = 0x3B,
|
||||
i_i64_store8 = 0x3C,
|
||||
i_i64_store16 = 0x3D,
|
||||
i_i64_store32 = 0x3E,
|
||||
i_memory_size = 0x3F,
|
||||
i_memory_grow = 0x40,
|
||||
|
||||
/* numeric instructions */
|
||||
i_i32_const = 0x41,
|
||||
i_i64_const = 0x42,
|
||||
i_f32_const = 0x43,
|
||||
i_f64_const = 0x44,
|
||||
|
||||
i_i32_eqz = 0x45,
|
||||
i_i32_eq = 0x46,
|
||||
i_i32_ne = 0x47,
|
||||
i_i32_lt_s = 0x48,
|
||||
i_i32_lt_u = 0x49,
|
||||
i_i32_gt_s = 0x4A,
|
||||
i_i32_gt_u = 0x4B,
|
||||
i_i32_le_s = 0x4C,
|
||||
i_i32_le_u = 0x4D,
|
||||
i_i32_ge_s = 0x4E,
|
||||
i_i32_ge_u = 0x4F,
|
||||
|
||||
i_i64_eqz = 0x50,
|
||||
i_i64_eq = 0x51,
|
||||
i_i64_ne = 0x52,
|
||||
i_i64_lt_s = 0x53,
|
||||
i_i64_lt_u = 0x54,
|
||||
i_i64_gt_s = 0x55,
|
||||
i_i64_gt_u = 0x56,
|
||||
i_i64_le_s = 0x57,
|
||||
i_i64_le_u = 0x58,
|
||||
i_i64_ge_s = 0x59,
|
||||
i_i64_ge_u = 0x5A,
|
||||
|
||||
i_f32_eq = 0x5B,
|
||||
i_f32_ne = 0x5C,
|
||||
i_f32_lt = 0x5D,
|
||||
i_f32_gt = 0x5E,
|
||||
i_f32_le = 0x5F,
|
||||
i_f32_ge = 0x60,
|
||||
|
||||
i_f64_eq = 0x61,
|
||||
i_f64_ne = 0x62,
|
||||
i_f64_lt = 0x63,
|
||||
i_f64_gt = 0x64,
|
||||
i_f64_le = 0x65,
|
||||
i_f64_ge = 0x66,
|
||||
|
||||
i_i32_clz = 0x67,
|
||||
i_i32_ctz = 0x68,
|
||||
i_i32_popcnt = 0x69,
|
||||
|
||||
i_i32_add = 0x6A,
|
||||
i_i32_sub = 0x6B,
|
||||
i_i32_mul = 0x6C,
|
||||
i_i32_div_s = 0x6D,
|
||||
i_i32_div_u = 0x6E,
|
||||
i_i32_rem_s = 0x6F,
|
||||
i_i32_rem_u = 0x70,
|
||||
i_i32_and = 0x71,
|
||||
i_i32_or = 0x72,
|
||||
i_i32_xor = 0x73,
|
||||
i_i32_shl = 0x74,
|
||||
i_i32_shr_s = 0x75,
|
||||
i_i32_shr_u = 0x76,
|
||||
i_i32_rotl = 0x77,
|
||||
i_i32_rotr = 0x78,
|
||||
|
||||
i_i64_clz = 0x79,
|
||||
i_i64_ctz = 0x7A,
|
||||
i_i64_popcnt = 0x7B,
|
||||
i_i64_add = 0x7C,
|
||||
i_i64_sub = 0x7D,
|
||||
i_i64_mul = 0x7E,
|
||||
i_i64_div_s = 0x7F,
|
||||
i_i64_div_u = 0x80,
|
||||
i_i64_rem_s = 0x81,
|
||||
i_i64_rem_u = 0x82,
|
||||
i_i64_and = 0x83,
|
||||
i_i64_or = 0x84,
|
||||
i_i64_xor = 0x85,
|
||||
i_i64_shl = 0x86,
|
||||
i_i64_shr_s = 0x87,
|
||||
i_i64_shr_u = 0x88,
|
||||
i_i64_rotl = 0x89,
|
||||
i_i64_rotr = 0x8A,
|
||||
|
||||
i_f32_abs = 0x8b,
|
||||
i_f32_neg = 0x8c,
|
||||
i_f32_ceil = 0x8d,
|
||||
i_f32_floor = 0x8e,
|
||||
i_f32_trunc = 0x8f,
|
||||
i_f32_nearest = 0x90,
|
||||
i_f32_sqrt = 0x91,
|
||||
i_f32_add = 0x92,
|
||||
i_f32_sub = 0x93,
|
||||
i_f32_mul = 0x94,
|
||||
i_f32_div = 0x95,
|
||||
i_f32_min = 0x96,
|
||||
i_f32_max = 0x97,
|
||||
i_f32_copysign = 0x98,
|
||||
|
||||
i_f64_abs = 0x99,
|
||||
i_f64_neg = 0x9a,
|
||||
i_f64_ceil = 0x9b,
|
||||
i_f64_floor = 0x9c,
|
||||
i_f64_trunc = 0x9d,
|
||||
i_f64_nearest = 0x9e,
|
||||
i_f64_sqrt = 0x9f,
|
||||
i_f64_add = 0xa0,
|
||||
i_f64_sub = 0xa1,
|
||||
i_f64_mul = 0xa2,
|
||||
i_f64_div = 0xa3,
|
||||
i_f64_min = 0xa4,
|
||||
i_f64_max = 0xa5,
|
||||
i_f64_copysign = 0xa6,
|
||||
|
||||
i_i32_wrap_i64 = 0xa7,
|
||||
i_i32_trunc_f32_s = 0xa8,
|
||||
i_i32_trunc_f32_u = 0xa9,
|
||||
i_i32_trunc_f64_s = 0xaa,
|
||||
i_i32_trunc_f64_u = 0xab,
|
||||
i_i64_extend_i32_s = 0xac,
|
||||
i_i64_extend_i32_u = 0xad,
|
||||
i_i64_trunc_f32_s = 0xae,
|
||||
i_i64_trunc_f32_u = 0xaf,
|
||||
i_i64_trunc_f64_s = 0xb0,
|
||||
i_i64_trunc_f64_u = 0xb1,
|
||||
i_f32_convert_i32_s = 0xb2,
|
||||
i_f32_convert_i32_u = 0xb3,
|
||||
i_f32_convert_i64_s = 0xb4,
|
||||
i_f32_convert_i64_u = 0xb5,
|
||||
i_f32_demote_f64 = 0xb6,
|
||||
i_f64_convert_i32_s = 0xb7,
|
||||
i_f64_convert_i32_u = 0xb8,
|
||||
i_f64_convert_i64_s = 0xb9,
|
||||
i_f64_convert_i64_u = 0xba,
|
||||
i_f64_promote_f32 = 0xbb,
|
||||
|
||||
i_i32_reinterpret_f32 = 0xbc,
|
||||
i_i64_reinterpret_f64 = 0xbd,
|
||||
i_f32_reinterpret_i32 = 0xbe,
|
||||
i_f64_reinterpret_i64 = 0xbf,
|
||||
|
||||
i_i32_extend8_s = 0xc0,
|
||||
i_i32_extend16_s = 0xc1,
|
||||
i_i64_extend8_s = 0xc2,
|
||||
i_i64_extend16_s = 0xc3,
|
||||
i_i64_extend32_s = 0xc4,
|
||||
|
||||
i_ref_null = 0xD0,
|
||||
i_ref_is_null = 0xD1,
|
||||
i_ref_func = 0xD2,
|
||||
|
||||
i_bulk_op = 0xFC,
|
||||
/* TODO: more instrs */
|
||||
|
||||
};
|
||||
|
||||
enum blocktype_tag {
|
||||
blocktype_empty,
|
||||
blocktype_valtype,
|
||||
blocktype_index,
|
||||
};
|
||||
|
||||
struct blocktype {
|
||||
enum blocktype_tag tag;
|
||||
union {
|
||||
enum valtype valtype;
|
||||
int type_index;
|
||||
};
|
||||
};
|
||||
|
||||
struct instrs {
|
||||
unsigned char *data;
|
||||
u32 len;
|
||||
};
|
||||
|
||||
struct block {
|
||||
struct blocktype type;
|
||||
struct expr instrs;
|
||||
};
|
||||
|
||||
struct memarg {
|
||||
u32 offset;
|
||||
u32 align;
|
||||
};
|
||||
|
||||
struct br_table {
|
||||
u32 num_label_indices;
|
||||
u32 label_indices[512];
|
||||
u32 default_label;
|
||||
};
|
||||
|
||||
struct call_indirect {
|
||||
u32 tableidx;
|
||||
u32 typeidx;
|
||||
};
|
||||
|
||||
struct table_init {
|
||||
u32 tableidx;
|
||||
u32 elemidx;
|
||||
};
|
||||
|
||||
struct table_copy {
|
||||
u32 from;
|
||||
u32 to;
|
||||
};
|
||||
|
||||
struct bulk_op {
|
||||
enum bulk_tag tag;
|
||||
union {
|
||||
struct table_init table_init;
|
||||
struct table_copy table_copy;
|
||||
u32 idx;
|
||||
};
|
||||
};
|
||||
|
||||
struct select_instr {
|
||||
u8 *valtypes;
|
||||
u32 num_valtypes;
|
||||
};
|
||||
|
||||
struct instr {
|
||||
enum instr_tag tag;
|
||||
int pos;
|
||||
union {
|
||||
struct br_table br_table;
|
||||
struct bulk_op bulk_op;
|
||||
struct call_indirect call_indirect;
|
||||
struct memarg memarg;
|
||||
struct select_instr select;
|
||||
struct block block;
|
||||
struct expr else_block;
|
||||
double f64;
|
||||
float f32;
|
||||
int i32;
|
||||
u32 u32;
|
||||
int64_t i64;
|
||||
u64 u64;
|
||||
unsigned char memidx;
|
||||
enum reftype reftype;
|
||||
};
|
||||
};
|
||||
|
||||
enum datamode {
|
||||
datamode_active,
|
||||
datamode_passive,
|
||||
};
|
||||
|
||||
struct wdata_active {
|
||||
u32 mem_index;
|
||||
struct expr offset_expr;
|
||||
};
|
||||
|
||||
struct wdata {
|
||||
struct wdata_active active;
|
||||
u8 *bytes;
|
||||
u32 bytes_len;
|
||||
enum datamode mode;
|
||||
};
|
||||
|
||||
struct datasec {
|
||||
struct wdata *datas;
|
||||
u32 num_datas;
|
||||
};
|
||||
|
||||
struct startsec {
|
||||
u32 start_fn;
|
||||
};
|
||||
|
||||
struct module {
|
||||
unsigned int parsed;
|
||||
unsigned int custom_sections;
|
||||
|
||||
struct func *funcs;
|
||||
|
||||
u32 num_funcs;
|
||||
|
||||
struct customsec custom_section[MAX_CUSTOM_SECTIONS];
|
||||
struct typesec type_section;
|
||||
struct funcsec func_section;
|
||||
struct importsec import_section;
|
||||
struct exportsec export_section;
|
||||
struct codesec code_section;
|
||||
struct tablesec table_section;
|
||||
struct memsec memory_section;
|
||||
struct globalsec global_section;
|
||||
struct startsec start_section;
|
||||
struct elemsec element_section;
|
||||
struct datasec data_section;
|
||||
struct namesec name_section;
|
||||
};
|
||||
|
||||
// make sure the struct is packed so that
|
||||
struct label {
|
||||
u32 instr_pos; // resolved status is stored in HOB of pos
|
||||
u32 jump;
|
||||
};
|
||||
|
||||
struct callframe {
|
||||
struct cursor code;
|
||||
struct val *locals;
|
||||
struct func *func;
|
||||
u16 prev_stack_items;
|
||||
};
|
||||
|
||||
struct resolver {
|
||||
u16 label;
|
||||
u8 end_tag;
|
||||
u8 start_tag;
|
||||
};
|
||||
|
||||
struct global_inst {
|
||||
struct val val;
|
||||
};
|
||||
|
||||
struct module_inst {
|
||||
struct table_inst *tables;
|
||||
struct global_inst *globals;
|
||||
struct elem_inst *elements;
|
||||
|
||||
u32 num_tables;
|
||||
u32 num_globals;
|
||||
u32 num_elements;
|
||||
|
||||
int start_fn;
|
||||
unsigned char *globals_init;
|
||||
};
|
||||
|
||||
struct wasi {
|
||||
int argc;
|
||||
const char **argv;
|
||||
|
||||
int environc;
|
||||
const char **environ;
|
||||
};
|
||||
|
||||
struct wasm_interp;
|
||||
|
||||
struct builtin {
|
||||
const char *name;
|
||||
int (*fn)(struct wasm_interp *);
|
||||
int (*prepare_args)(struct wasm_interp *);
|
||||
};
|
||||
|
||||
struct wasm_interp {
|
||||
struct module *module;
|
||||
struct module_inst module_inst;
|
||||
struct wasi wasi;
|
||||
void *context;
|
||||
|
||||
struct builtin builtins[MAX_BUILTINS];
|
||||
int num_builtins;
|
||||
|
||||
int prev_resolvers, quitting;
|
||||
|
||||
struct errors errors; /* struct error */
|
||||
size_t ops;
|
||||
|
||||
struct cursor callframes; /* struct callframe */
|
||||
struct cursor stack; /* struct val */
|
||||
struct cursor mem; /* u8/mixed */
|
||||
|
||||
struct cursor memory; /* memory pages (65536 blocks) */
|
||||
|
||||
struct cursor locals; /* struct val */
|
||||
struct cursor labels; /* struct labels */
|
||||
struct cursor num_labels;
|
||||
|
||||
// resolve stack for the current function. every time a control
|
||||
// instruction is encountered, the label index is pushed. When an
|
||||
// instruction is popped, we can resolve the label
|
||||
struct cursor resolver_stack; /* struct resolver */
|
||||
struct cursor resolver_offsets; /* int */
|
||||
};
|
||||
|
||||
struct wasm_parser {
|
||||
struct module module;
|
||||
struct builtin *builtins;
|
||||
u32 num_builtins;
|
||||
struct cursor cur;
|
||||
struct cursor mem;
|
||||
struct errors errs;
|
||||
};
|
||||
|
||||
|
||||
int run_wasm(unsigned char *wasm, unsigned long len, int argc, const char **argv, char **env, int *retval);
|
||||
int parse_wasm(struct wasm_parser *p);
|
||||
int wasm_interp_init(struct wasm_interp *interp, struct module *module);
|
||||
void wasm_parser_free(struct wasm_parser *parser);
|
||||
void wasm_parser_init(struct wasm_parser *p, u8 *wasm, size_t wasm_len, size_t arena_size, struct builtin *, int num_builtins);
|
||||
void wasm_interp_free(struct wasm_interp *interp);
|
||||
int interp_wasm_module(struct wasm_interp *interp, int *retval);
|
||||
int interp_wasm_module_resume(struct wasm_interp *interp, int *retval);
|
||||
void print_error_backtrace(struct errors *errors);
|
||||
void setup_wasi(struct wasm_interp *interp, int argc, const char **argv, char **env);
|
||||
void print_callstack(struct wasm_interp *interp);
|
||||
|
||||
// builtin helpers
|
||||
int get_params(struct wasm_interp *interp, struct val** vals, u32 num_vals);
|
||||
int get_var_params(struct wasm_interp *interp, struct val** vals, u32 *num_vals);
|
||||
u8 *interp_mem_ptr(struct wasm_interp *interp, u32 ptr, int size);
|
||||
|
||||
static INLINE struct callframe *top_callframe(struct cursor *cur)
|
||||
{
|
||||
return (struct callframe*)cursor_top(cur, sizeof(struct callframe));
|
||||
}
|
||||
|
||||
|
||||
static INLINE struct cursor *interp_codeptr(struct wasm_interp *interp)
|
||||
{
|
||||
struct callframe *frame;
|
||||
if (unlikely(!(frame = top_callframe(&interp->callframes))))
|
||||
return 0;
|
||||
return &frame->code;
|
||||
}
|
||||
|
||||
|
||||
static INLINE int mem_ptr_str(struct wasm_interp *interp, u32 ptr,
|
||||
const char **str)
|
||||
{
|
||||
// still technically unsafe if the string runs over the end of memory...
|
||||
if (!(*str = (const char*)interp_mem_ptr(interp, ptr, 1))) {
|
||||
return interp_error(interp, "int memptr");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static INLINE int mem_ptr_i32(struct wasm_interp *interp, u32 ptr, int **i)
|
||||
{
|
||||
if (!(*i = (int*)interp_mem_ptr(interp, ptr, sizeof(int))))
|
||||
return interp_error(interp, "int memptr");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static INLINE int cursor_pushval(struct cursor *cur, struct val *val)
|
||||
{
|
||||
return cursor_push(cur, (u8*)val, sizeof(*val));
|
||||
}
|
||||
|
||||
static INLINE int cursor_push_i32(struct cursor *stack, int i)
|
||||
{
|
||||
struct val val;
|
||||
val.type = val_i32;
|
||||
val.num.i32 = i;
|
||||
|
||||
return cursor_pushval(stack, &val);
|
||||
}
|
||||
|
||||
static INLINE int stack_push_i32(struct wasm_interp *interp, int i)
|
||||
{
|
||||
return cursor_push_i32(&interp->stack, i);
|
||||
}
|
||||
|
||||
static INLINE struct callframe *top_callframes(struct cursor *cur, int top)
|
||||
{
|
||||
return (struct callframe*)cursor_topn(cur, sizeof(struct callframe), top);
|
||||
}
|
||||
|
||||
static INLINE int was_section_parsed(struct module *module,
|
||||
enum section_tag section)
|
||||
{
|
||||
if (section == section_custom)
|
||||
return module->custom_sections > 0;
|
||||
|
||||
return module->parsed & (1 << section);
|
||||
}
|
||||
|
||||
|
||||
#endif /* PROTOVERSE_WASM_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,65 +1,12 @@
|
||||
{
|
||||
"originHash" : "1fc7e0b44329ba72cd285eeb022b5b92582cd01586b920d243cb0485c2e69dcc",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "codescanner",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/twostraws/CodeScanner.git",
|
||||
"state" : {
|
||||
"revision" : "9fa582f4b36c69c2a55bff5fb3377eb170ae273c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "cryptoswift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
|
||||
"state" : {
|
||||
"revision" : "e74bbbfbef939224b242ae7c342a90e60b88b5ce"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "emojikit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/tyiu/EmojiKit",
|
||||
"state" : {
|
||||
"revision" : "47a4b1402de26be0299dcb4d667c1faaf21a7874",
|
||||
"version" : "0.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "emojipicker",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/tyiu/EmojiPicker.git",
|
||||
"state" : {
|
||||
"revision" : "3f48903721eae223238ff0af17c22d6373d33813",
|
||||
"version" : "0.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "faviconfinder",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/will-lumley/FaviconFinder.git",
|
||||
"state" : {
|
||||
"revision" : "9279f4371f4877ca302ba3bf1015f3f58ae4a56c",
|
||||
"version" : "5.1.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "gsplayer",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/wxxsw/GSPlayer",
|
||||
"state" : {
|
||||
"revision" : "aa6dad7943d52f5207f7fcc2ad3e4274583443b8",
|
||||
"version" : "0.2.26"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "kingfisher",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/onevcat/Kingfisher",
|
||||
"state" : {
|
||||
"revision" : "4c6b067f96953ee19526e49e4189403a2be21fb3",
|
||||
"version" : "8.3.1"
|
||||
"revision" : "415b1d97fb38bda1e5a6b2dde63354720832110b",
|
||||
"version" : "7.6.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -69,76 +16,7 @@
|
||||
"state" : {
|
||||
"revision" : "40b4b38b3b1c83f7088c76189a742870e0ca06a9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-collections",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-collections.git",
|
||||
"state" : {
|
||||
"revision" : "ee97538f5b81ae89698fd95938896dec5217b148",
|
||||
"version" : "1.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-markdown-ui",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/damus-io/swift-markdown-ui",
|
||||
"state" : {
|
||||
"revision" : "76bb7971da7fbf429de1c84f1244adf657242fee"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-snapshot-testing",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-snapshot-testing",
|
||||
"state" : {
|
||||
"revision" : "5b356adceabff6ca027f6574aac79e9fee145d26",
|
||||
"version" : "1.14.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-syntax",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-syntax.git",
|
||||
"state" : {
|
||||
"revision" : "74203046135342e4a4a627476dd6caf8b28fe11b",
|
||||
"version" : "509.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-trie",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/tyiu/swift-trie",
|
||||
"state" : {
|
||||
"revision" : "4c50bff6c168f74425f70476be62a072980d2da7",
|
||||
"version" : "0.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftsoup",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/scinfu/SwiftSoup.git",
|
||||
"state" : {
|
||||
"revision" : "bba848db50462894e7fc0891d018dfecad4ef11e",
|
||||
"version" : "2.8.7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftycrop",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/benedom/SwiftyCrop",
|
||||
"state" : {
|
||||
"revision" : "454d0a0d4faf6f3a19c8d817ab9d7d27524bd79f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swipeactions",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/damus-io/SwipeActions.git",
|
||||
"state" : {
|
||||
"revision" : "33d99756c3112e1a07c1732e3cddc5ad5bd0c5f4"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
"version" : 2
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1520"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D79C4C132AFEB061003A41B4"
|
||||
BuildableName = "DamusNotificationService.appex"
|
||||
BlueprintName = "DamusNotificationService"
|
||||
ReferencedContainer = "container:damus.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4CE6DEE227F7A08100C66700"
|
||||
BuildableName = "damus.app"
|
||||
BlueprintName = "damus"
|
||||
ReferencedContainer = "container:damus.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = ""
|
||||
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||
launchStyle = "0"
|
||||
askForAppToLaunch = "Yes"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<RemoteRunnable
|
||||
runnableDebuggingMode = "1"
|
||||
BundleIdentifier = "com.jb55.damus2"
|
||||
RemotePath = "/Users/danielnogueira/Library/Developer/CoreSimulator/Devices/99E60B35-CE5D-4B45-AC35-00818C0AF3CB/data/Containers/Bundle/Application/7D0A5302-D07E-4C7C-B509-A7C552BD5A65/damus.app">
|
||||
</RemoteRunnable>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4CE6DEE227F7A08100C66700"
|
||||
BuildableName = "damus.app"
|
||||
BlueprintName = "damus"
|
||||
ReferencedContainer = "container:damus.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
askForAppToLaunch = "Yes"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4CE6DEE227F7A08100C66700"
|
||||
BuildableName = "damus.app"
|
||||
BlueprintName = "damus"
|
||||
ReferencedContainer = "container:damus.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,101 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1540"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D703D7162C66E47100A400EA"
|
||||
BuildableName = "HighlighterActionExtension.appex"
|
||||
BlueprintName = "HighlighterActionExtension"
|
||||
ReferencedContainer = "container:damus.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4CE6DEE227F7A08100C66700"
|
||||
BuildableName = "damus.app"
|
||||
BlueprintName = "damus"
|
||||
ReferencedContainer = "container:damus.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = ""
|
||||
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||
launchStyle = "0"
|
||||
askForAppToLaunch = "Yes"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<RemoteRunnable
|
||||
runnableDebuggingMode = "1"
|
||||
BundleIdentifier = "com.apple.mobilesafari"
|
||||
RemotePath = "/Library/Developer/CoreSimulator/Volumes/iOS_21F79/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 17.5.simruntime/Contents/Resources/RuntimeRoot/Applications/MobileSafari.app">
|
||||
</RemoteRunnable>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4CE6DEE227F7A08100C66700"
|
||||
BuildableName = "damus.app"
|
||||
BlueprintName = "damus"
|
||||
ReferencedContainer = "container:damus.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
askForAppToLaunch = "Yes"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4CE6DEE227F7A08100C66700"
|
||||
BuildableName = "damus.app"
|
||||
BlueprintName = "damus"
|
||||
ReferencedContainer = "container:damus.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1520"
|
||||
LastUpgradeVersion = "1420"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1520"
|
||||
LastUpgradeVersion = "1420"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -26,8 +26,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
@@ -40,7 +39,7 @@
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
skipped = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4CE6DEFC27F7A08200C66700"
|
||||
@@ -71,9 +70,6 @@
|
||||
ReferencedContainer = "container:damus.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<StoreKitConfigurationFileReference
|
||||
identifier = "../../Purple.storekit">
|
||||
</StoreKitConfigurationFileReference>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
//
|
||||
// AppAccessibilityIdentifiers.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Daniel D’Aquino on 2024-11-18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A collection of app-wide identifier constants used to facilitate UI tests to find the element they are looking for.
|
||||
///
|
||||
/// ## Implementation notes
|
||||
///
|
||||
/// - This is not an exhaustive list. Add more identifiers as needed.
|
||||
/// - Organize this by separating each category with `MARK` comment markers and a unique prefix, each category separated by 2 empty lines
|
||||
enum AppAccessibilityIdentifiers: String {
|
||||
// MARK: Login
|
||||
// Prefix: `sign_in`
|
||||
|
||||
/// Sign in button at the very start of the app
|
||||
case sign_in_option_button
|
||||
/// A secure text entry field where the user can put their private key when logging in
|
||||
case sign_in_nsec_key_entry_field
|
||||
/// Button to sign in after entering private key
|
||||
case sign_in_confirm_button
|
||||
|
||||
|
||||
// MARK: Sign Up / Create Account
|
||||
// Prefix: `sign_up`
|
||||
|
||||
/// Button to navigate to create account view
|
||||
case sign_up_option_button
|
||||
/// Text field for entering name during account creation
|
||||
case sign_up_name_field
|
||||
/// Text field for entering bio during account creation
|
||||
case sign_up_bio_field
|
||||
/// Button to proceed to the next step after entering profile info
|
||||
case sign_up_next_button
|
||||
/// Button to save keys after account creation
|
||||
case sign_up_save_keys_button
|
||||
/// Button to skip saving keys
|
||||
case sign_up_skip_save_keys_button
|
||||
|
||||
|
||||
// MARK: Onboarding
|
||||
// Prefix: `onboarding`
|
||||
|
||||
/// Any interest option button on the "select your interests" page during onboarding
|
||||
case onboarding_interest_option_button
|
||||
|
||||
/// The "next" button on the onboarding interest page
|
||||
case onboarding_interest_page_next_page
|
||||
|
||||
/// The "next" button on the onboarding content settings page
|
||||
case onboarding_content_settings_page_next_page
|
||||
|
||||
/// The skip button on the onboarding sheet
|
||||
case onboarding_sheet_skip_button
|
||||
|
||||
|
||||
// MARK: Post composer
|
||||
// Prefix: `post_composer`
|
||||
|
||||
/// The cancel post button
|
||||
case post_composer_cancel_button
|
||||
|
||||
/// The text view where the user types their note
|
||||
case post_composer_text_view
|
||||
|
||||
/// A user result in the mention autocomplete list
|
||||
case post_composer_mention_user_result
|
||||
|
||||
|
||||
// MARK: Post button (FAB)
|
||||
// Prefix: `post_button`
|
||||
|
||||
/// The floating action button to create a new post
|
||||
case post_button
|
||||
|
||||
// MARK: Main interface layout
|
||||
// Prefix: `main`
|
||||
|
||||
/// Profile picture item on the top toolbar, used to open the side menu
|
||||
case main_side_menu_button
|
||||
|
||||
|
||||
// MARK: Side menu
|
||||
// Prefix: `side_menu`
|
||||
|
||||
/// The profile option in the side menu
|
||||
case side_menu_profile_button
|
||||
|
||||
/// The logout button in the side menu
|
||||
case side_menu_logout_button
|
||||
|
||||
/// The logout confirmation button in the alert dialog
|
||||
case side_menu_logout_confirm_button
|
||||
|
||||
|
||||
// MARK: Items specific to the user's own profile
|
||||
// Prefix: `own_profile`
|
||||
|
||||
/// The edit profile button
|
||||
case own_profile_edit_button
|
||||
|
||||
/// The button to edit the banner image on the profile
|
||||
case own_profile_banner_image_edit_button
|
||||
|
||||
/// The button to pick the new banner image from URL
|
||||
case own_profile_banner_image_edit_from_url
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<svg width="430" height="813" viewBox="0 0 430 813" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_f_1069_29012)">
|
||||
<path d="M44.0811 256.851L186.315 111L276.203 223.574L244.02 388.295L69.9751 697.084L100.678 498.338L44.0811 256.851Z" fill="url(#paint0_linear_1069_29012)"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_f_1069_29012)">
|
||||
<path d="M116.509 587.348L206.677 479.401L230.746 273.265L266.666 231.183L367.424 396.975L281.292 659.008L266.665 801.413L66.889 763.694L116.509 587.348Z" fill="url(#paint1_linear_1069_29012)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_1069_29012" x="-66.6248" y="0.294121" width="453.534" height="807.496" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="55.3529" result="effect1_foregroundBlur_1069_29012"/>
|
||||
</filter>
|
||||
<filter id="filter1_f_1069_29012" x="-43.8172" y="120.477" width="521.947" height="791.642" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="55.3529" result="effect1_foregroundBlur_1069_29012"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_1069_29012" x1="230.179" y1="166.577" x2="-67.7956" y2="310.108" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#D34CD9"/>
|
||||
<stop offset="1" stop-color="#4E4DF4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_1069_29012" x1="139.483" y1="462.902" x2="377.854" y2="565.47" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0DE8FF"/>
|
||||
<stop offset="1" stop-color="#641AAE"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 34 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "shadow-2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.0 MiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "shadow.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 511 KiB |
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x1A",
|
||||
"green" : "0x93",
|
||||
"red" : "0xF7"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xD7",
|
||||
"green" : "0xD1",
|
||||
"red" : "0xD1"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x13",
|
||||
"green" : "0x11",
|
||||
"red" : "0x11"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xF9",
|
||||
"green" : "0xF3",
|
||||
"red" : "0xF3"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x25",
|
||||
"green" : "0x22",
|
||||
"red" : "0x22"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "244",
|
||||
"green" : "218",
|
||||
"red" : "244"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "92",
|
||||
"green" : "45",
|
||||
"red" : "93"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "236",
|
||||
"green" : "194",
|
||||
"red" : "238"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "109",
|
||||
"green" : "49",
|
||||
"red" : "111"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "197",
|
||||
"green" : "67",
|
||||
"red" : "204"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "255",
|
||||
"green" : "194",
|
||||
"red" : "255"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xFF",
|
||||
"green" : "0xFF",
|
||||
"red" : "0xFF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x00",
|
||||
"green" : "0x00",
|
||||
"red" : "0x00"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xE3",
|
||||
"green" : "0xD7",
|
||||
"red" : "0xF7"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x20",
|
||||
"green" : "0x13",
|
||||
"red" : "0x61"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x63",
|
||||
"green" : "0x11",
|
||||
"red" : "0xF5"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x6E",
|
||||
"green" : "0x20",
|
||||
"red" : "0xF8"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xEE",
|
||||
"green" : "0xE8",
|
||||
"red" : "0xF7"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x35",
|
||||
"green" : "0x04",
|
||||
"red" : "0x8B"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user