Compare commits
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
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_
|
||||||
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
## Summary
|
||||||
|
|
||||||
|
_[Please provide a summary of the changes in this PR.]_
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
- [ ] I have read (or I am familiar with) the [Contribution Guidelines](../docs/CONTRIBUTING.md)
|
||||||
|
- [ ] I have tested the changes in this PR
|
||||||
|
- [ ] 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.]_
|
||||||
@@ -6,3 +6,4 @@ damus.xcodeproj/xcshareddata/xcbaselines
|
|||||||
TODO.bak
|
TODO.bak
|
||||||
tags
|
tags
|
||||||
build-git-hash.txt
|
build-git-hash.txt
|
||||||
|
.build
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ Suhail Saqan <suhail.saqan@gmail.com> <43693074+suhailsaqan@users.noreply.github
|
|||||||
cr0bar <cr0bar@cr0.bar> <cr0bar@users.noreply.github.com>
|
cr0bar <cr0bar@cr0.bar> <cr0bar@users.noreply.github.com>
|
||||||
Swift <scoder1747@gmail.com> <120697811+scoder1747@users.noreply.github.com>
|
Swift <scoder1747@gmail.com> <120697811+scoder1747@users.noreply.github.com>
|
||||||
Daniel D'Aquino <daniel@daquino.me> <patches@damus.io>
|
Daniel D'Aquino <daniel@daquino.me> <patches@damus.io>
|
||||||
|
Transifex <transifex@transifex.com> <43880903+transifex-integration[bot]@users.noreply.github.com>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
### 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)
|
||||||
|
|
||||||
@@ -1,3 +1,435 @@
|
|||||||
|
## [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
|
## [1.6-25] - 2023-10-31
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -1651,4 +2083,3 @@
|
|||||||
|
|
||||||
|
|
||||||
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2
|
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<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>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
@@ -10,5 +14,9 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>com.apple.security.network.client</key>
|
<key>com.apple.security.network.client</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>keychain-access-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>$(AppIdentifierPrefix)com.jb55.damus2</string>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
//
|
|
||||||
// NostrEventInfoFromPushNotification.swift
|
|
||||||
// DamusNotificationService
|
|
||||||
//
|
|
||||||
// Created by Daniel D’Aquino on 2023-11-13.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
/// The representation of a JSON-encoded Nostr Event used by the push notification server
|
|
||||||
/// Needs to match with https://gitlab.com/soapbox-pub/strfry-policies/-/raw/433459d8084d1f2d6500fdf916f22caa3b4d7be5/src/types.ts
|
|
||||||
struct NostrEventInfoFromPushNotification: Codable {
|
|
||||||
let id: String // Hex-encoded
|
|
||||||
let sig: String // Hex-encoded
|
|
||||||
let kind: NostrKind
|
|
||||||
let tags: [[String]]
|
|
||||||
let pubkey: String // Hex-encoded
|
|
||||||
let content: String
|
|
||||||
let created_at: Int
|
|
||||||
|
|
||||||
static func from(dictionary: [AnyHashable: Any]) -> NostrEventInfoFromPushNotification? {
|
|
||||||
guard let id = dictionary["id"] as? String,
|
|
||||||
let sig = dictionary["sig"] as? String,
|
|
||||||
let kind_int = dictionary["kind"] as? UInt32,
|
|
||||||
let kind = NostrKind(rawValue: kind_int),
|
|
||||||
let tags = dictionary["tags"] as? [[String]],
|
|
||||||
let pubkey = dictionary["pubkey"] as? String,
|
|
||||||
let content = dictionary["content"] as? String,
|
|
||||||
let created_at = dictionary["created_at"] as? Int else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return NostrEventInfoFromPushNotification(id: id, sig: sig, kind: kind, tags: tags, pubkey: pubkey, content: content, created_at: created_at)
|
|
||||||
}
|
|
||||||
|
|
||||||
func reactionEmoji() -> String? {
|
|
||||||
guard self.kind == NostrKind.like else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch self.content {
|
|
||||||
case "", "+":
|
|
||||||
return "❤️"
|
|
||||||
case "-":
|
|
||||||
return "👎"
|
|
||||||
default:
|
|
||||||
return self.content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// NotificationExtensionState.swift
|
||||||
|
// DamusNotificationService
|
||||||
|
//
|
||||||
|
// Created by Daniel D’Aquino on 2023-11-27.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,16 +11,17 @@ import UserNotifications
|
|||||||
struct NotificationFormatter {
|
struct NotificationFormatter {
|
||||||
static var shared = NotificationFormatter()
|
static var shared = NotificationFormatter()
|
||||||
|
|
||||||
// TODO: These is a very generic notification formatter. Once we integrate NostrDB into the extension, we should reuse various functions present in `HomeModel.swift`
|
// MARK: - Formatting with NdbNote
|
||||||
func formatMessage(event: NostrEventInfoFromPushNotification) -> UNNotificationContent? {
|
|
||||||
|
func format_message(event: NdbNote) -> UNMutableNotificationContent? {
|
||||||
let content = 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`
|
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) {
|
let event_json_string = String(data: event_json_data, encoding: .utf8) {
|
||||||
content.userInfo = [
|
content.userInfo = [
|
||||||
"nostr_event_info": event_json_string
|
NDB_NOTE_JSON_USER_INFO_KEY: event_json_string
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
switch event.kind {
|
switch event.known_kind {
|
||||||
case .text:
|
case .text:
|
||||||
content.title = NSLocalizedString("Someone posted a note", comment: "Title label for push notification where someone posted a note")
|
content.title = NSLocalizedString("Someone posted a note", comment: "Title label for push notification where someone posted a note")
|
||||||
content.body = event.content
|
content.body = event.content
|
||||||
@@ -30,7 +31,7 @@ struct NotificationFormatter {
|
|||||||
content.body = NSLocalizedString("(Contents are encrypted)", comment: "Label on push notification indicating that the contents of the message are encrypted")
|
content.body = NSLocalizedString("(Contents are encrypted)", comment: "Label on push notification indicating that the contents of the message are encrypted")
|
||||||
break
|
break
|
||||||
case .like:
|
case .like:
|
||||||
guard let reactionEmoji = event.reactionEmoji() else {
|
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")
|
content.title = NSLocalizedString("Someone reacted to your note", comment: "Generic title label for push notifications where someone reacted to the user's post")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -45,4 +46,98 @@ struct NotificationFormatter {
|
|||||||
}
|
}
|
||||||
return content
|
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_txn = profiles.lookup(id: pk)
|
||||||
|
let profile = profile_txn?.unsafeUnownedValue
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,34 +5,151 @@
|
|||||||
// Created by Daniel D’Aquino on 2023-11-10.
|
// Created by Daniel D’Aquino on 2023-11-10.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Kingfisher
|
||||||
|
import ImageIO
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
import Intents
|
||||||
|
|
||||||
class NotificationService: UNNotificationServiceExtension {
|
class NotificationService: UNNotificationServiceExtension {
|
||||||
|
|
||||||
var contentHandler: ((UNNotificationContent) -> Void)?
|
var contentHandler: ((UNNotificationContent) -> Void)?
|
||||||
var bestAttemptContent: UNMutableNotificationContent?
|
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) {
|
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||||
|
configureKingfisherCache()
|
||||||
|
|
||||||
self.contentHandler = contentHandler
|
self.contentHandler = contentHandler
|
||||||
|
|
||||||
let ndb: Ndb? = try? Ndb(owns_db_file: false)
|
guard let nostr_event_json = request.content.userInfo["nostr_event"] as? String,
|
||||||
|
let nostr_event = NdbNote.owned_from_json(json: nostr_event_json)
|
||||||
// Modify the notification content here...
|
else {
|
||||||
guard let nostrEventInfoDictionary = request.content.userInfo["nostr_event"] as? [AnyHashable: Any],
|
// No nostr event detected. Just display the original notification
|
||||||
let nostrEventInfo = NostrEventInfoFromPushNotification.from(dictionary: nostrEventInfoDictionary) else {
|
|
||||||
contentHandler(request.content)
|
contentHandler(request.content)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log that we got a push notification
|
// Log that we got a push notification
|
||||||
if let pubkey = Pubkey(hex: nostrEventInfo.pubkey),
|
Log.debug("Got nostr event push notification from pubkey %s", for: .push_notifications, nostr_event.pubkey.hex())
|
||||||
let txn = ndb?.lookup_profile(pubkey) {
|
|
||||||
Log.debug("Got push notification from %s (%s)", for: .push_notifications, (txn.unsafeUnownedValue?.profile?.display_name ?? "Unknown"), nostrEventInfo.pubkey)
|
guard let state = 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
|
||||||
}
|
}
|
||||||
|
|
||||||
if let improvedContent = NotificationFormatter.shared.formatMessage(event: nostrEventInfo) {
|
let sender_profile = {
|
||||||
|
let txn = state.ndb.lookup_profile(nostr_event.pubkey)
|
||||||
|
let profile = txn?.unsafeUnownedValue?.profile
|
||||||
|
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 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 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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Task {
|
||||||
|
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)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,3 +162,162 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = ndb.lookup_note(recipient_note_id)
|
||||||
|
if let replying_to_pk = replying_to?.unsafeUnownedValue?.pubkey {
|
||||||
|
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_txn = ndb.lookup_profile(pubkey)
|
||||||
|
let profile = profile_txn?.unsafeUnownedValue?.profile
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?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>
|
||||||
@@ -1,3 +1,32 @@
|
|||||||
dependencies: [
|
// swift-tools-version: 6.0
|
||||||
.Package(url: "https://github.com/jb55/secp256k1.swift.git", branch: "main")
|
// 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"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?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>
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +1,73 @@
|
|||||||
[](https://github.com/damus-io/damus/actions/workflows/run-tests.yaml)
|
<div align="center">
|
||||||
|
|
||||||
# damus
|
<img src="./damus/Assets.xcassets/damus-home.imageset/damus-home@2x.png" alt="Damus Logo" title="Damus logo" width=""/>
|
||||||
|
|
||||||
|
# Damus
|
||||||
|
|
||||||
|
The social network you control
|
||||||
|
|
||||||
A twitter-like [nostr][nostr] client for iPhone, iPad and MacOS.
|
A twitter-like [nostr][nostr] client for iPhone, iPad and MacOS.
|
||||||
|
|
||||||
<img src="./ss.png" width="50%" height="50%" />
|
[](/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>
|
||||||
|
|
||||||
[nostr]: https://github.com/fiatjaf/nostr
|
[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
|
## Spec Compliance
|
||||||
|
|
||||||
damus implements the following [Nostr Implementation Possibilities][nips]
|
damus implements the following [Nostr Implementation Possibilities][nips]
|
||||||
|
|
||||||
- [NIP-01: Basic protocol flow][nip01]
|
- [NIP-01: Basic protocol flow][nip01]
|
||||||
|
- [NIP-04: Encrypted direct message][nip04]
|
||||||
- [NIP-08: Mentions][nip08]
|
- [NIP-08: Mentions][nip08]
|
||||||
- [NIP-10: Reply conventions][nip10]
|
- [NIP-10: Reply conventions][nip10]
|
||||||
- [NIP-12: Generic tag queries (hashtags)][nip12]
|
- [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
|
[nips]: https://github.com/nostr-protocol/nips
|
||||||
[nip01]: https://github.com/nostr-protocol/nips/blob/master/01.md
|
[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
|
[nip08]: https://github.com/nostr-protocol/nips/blob/master/08.md
|
||||||
[nip10]: https://github.com/nostr-protocol/nips/blob/master/10.md
|
[nip10]: https://github.com/nostr-protocol/nips/blob/master/10.md
|
||||||
[nip12]: https://github.com/nostr-protocol/nips/blob/master/12.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
|
## Getting Started on Damus
|
||||||
|
|
||||||
@@ -32,7 +78,7 @@ damus implements the following [Nostr Implementation Possibilities][nips]
|
|||||||
- Relays: You can add more relays to send your notes to by tapping the "+".
|
- Relays: You can add more relays to send your notes to by tapping the "+".
|
||||||
- Find more relays to add: https://nostr.info/relays/
|
- Find more relays to add: https://nostr.info/relays/
|
||||||
- Public Key (pubkey): Your public, personal address and how people can find and tag you
|
- 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 publically and share with other clients at your own risk!
|
- Secret Key: Your *private* key unique to you. Never share your private key publicly and share with other clients at your own risk!
|
||||||
- Save your keys somewhere safe
|
- Save your keys somewhere safe
|
||||||
- Log out
|
- Log out
|
||||||
|
|
||||||
@@ -46,19 +92,15 @@ 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
|
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
|
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
|
3. Go back to your 🏠 Personal Feed and tap the blue + button to compose your Note
|
||||||
4. Add @ direcly followed by the pubkey (e.g., `@npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s`)
|
4. Add @ directly 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.
|
- 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.
|
||||||
- Currently you can't delete your Notes in the iOS app
|
- Currently you can't delete your Notes in the iOS app
|
||||||
- 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
|
- 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
|
||||||
- Engaging with Notes
|
- 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
|
- 💬 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
|
- ♺ 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)
|
- ♡ 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)
|
#### 💬 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
|
- 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
|
||||||
@@ -76,7 +118,9 @@ 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)
|
4. For PFP, insert a URL containing your image (support video: https://cdn.jb55.com/vid/pfp-editor.mp4)
|
||||||
5. Save
|
5. Save
|
||||||
|
|
||||||
|
|
||||||
#### ⚡️ Request Sats
|
#### ⚡️ Request Sats
|
||||||
|
Paste an invoice from your favorite LN wallet.
|
||||||
(Sats or Satoshis are the smallest denomination of bitcoin)
|
(Sats or Satoshis are the smallest denomination of bitcoin)
|
||||||
|
|
||||||
**Alby (browser extension)**
|
**Alby (browser extension)**
|
||||||
@@ -117,6 +161,8 @@ Your internet protocol (IP) address is exposed to the relays you connect to, and
|
|||||||
|
|
||||||
The relay also learns which public keys you are requesting, meaning your public key will be tied to your IP address.
|
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
|
### Translations
|
||||||
|
|
||||||
Translators welcome! Join the [Transifex][transifex] project.
|
Translators welcome! Join the [Transifex][transifex] project.
|
||||||
@@ -127,8 +173,10 @@ All user-facing strings must have a comment in order to provide context to trans
|
|||||||
|
|
||||||
### Awards
|
### Awards
|
||||||
|
|
||||||
|
Damus lead dev and founder Will awards developers with satoshis!
|
||||||
There may be nostr badges awarded for contributors in the future... :)
|
There may be nostr badges awarded for contributors in the future... :)
|
||||||
|
|
||||||
|
|
||||||
First contributors:
|
First contributors:
|
||||||
|
|
||||||
1. @randymcmillan
|
1. @randymcmillan
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
//
|
|
||||||
// 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 note_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 note_blocks {
|
|
||||||
int words;
|
|
||||||
int num_blocks;
|
|
||||||
struct note_block *blocks;
|
|
||||||
} blocks_t;
|
|
||||||
|
|
||||||
void blocks_init(struct note_blocks *blocks);
|
|
||||||
void blocks_free(struct note_blocks *blocks);
|
|
||||||
|
|
||||||
#endif /* block_h */
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
// 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 "bolt11.h"
|
||||||
#include "amount.h"
|
#include "amount.h"
|
||||||
#include "nostr_bech32.h"
|
#include "nostr_bech32.h"
|
||||||
|
|||||||
@@ -1,393 +0,0 @@
|
|||||||
//
|
|
||||||
// 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 note_block *block) {
|
|
||||||
int d1, d2, d3, ind;
|
|
||||||
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 note_block *block) {
|
|
||||||
int c;
|
|
||||||
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 note_blocks *blocks, struct note_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 note_blocks *blocks, const u8 *start, const u8 *end)
|
|
||||||
{
|
|
||||||
struct note_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 consume_url_fragment(struct cursor *cur)
|
|
||||||
{
|
|
||||||
int c;
|
|
||||||
|
|
||||||
if ((c = peek_char(cur, 0)) < 0)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (c != '#' && c != '?') {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cur->p++;
|
|
||||||
|
|
||||||
return consume_until_whitespace(cur, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int consume_url_path(struct cursor *cur)
|
|
||||||
{
|
|
||||||
int c;
|
|
||||||
|
|
||||||
if ((c = peek_char(cur, 0)) < 0)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (c != '/') {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (cur->p < cur->end) {
|
|
||||||
c = *cur->p;
|
|
||||||
|
|
||||||
if (c == '?' || c == '#' || is_whitespace(c)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cur->p++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int consume_url_host(struct cursor *cur)
|
|
||||||
{
|
|
||||||
char c;
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
while (cur->p < cur->end) {
|
|
||||||
c = *cur->p;
|
|
||||||
// TODO: handle IDNs
|
|
||||||
if (is_alphanumeric(c) || c == '.' || c == '-')
|
|
||||||
{
|
|
||||||
count++;
|
|
||||||
cur->p++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return count != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// this means the end of the URL hostname is the end of the buffer and we finished
|
|
||||||
return count != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int parse_url(struct cursor *cur, struct note_block *block) {
|
|
||||||
u8 *start = cur->p;
|
|
||||||
u8 *host;
|
|
||||||
int host_len;
|
|
||||||
struct cursor path_cur;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure to save the hostname. We will use this to detect damus.io links
|
|
||||||
host = cur->p;
|
|
||||||
|
|
||||||
if (!consume_url_host(cur)) {
|
|
||||||
cur->p = start;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the length of the host string
|
|
||||||
host_len = (int)(cur->p - host);
|
|
||||||
|
|
||||||
// save the current parse state so that we can continue from here when
|
|
||||||
// parsing the bech32 in the damus.io link if we have it
|
|
||||||
copy_cursor(cur, &path_cur);
|
|
||||||
|
|
||||||
// skip leading /
|
|
||||||
cursor_skip(&path_cur, 1);
|
|
||||||
|
|
||||||
if (!consume_url_path(cur)) {
|
|
||||||
cur->p = start;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!consume_url_fragment(cur)) {
|
|
||||||
cur->p = start;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// smart parens
|
|
||||||
if (start - 1 >= 0 &&
|
|
||||||
start < cur->end &&
|
|
||||||
*(start - 1) == '(' &&
|
|
||||||
(cur->p - 1) < cur->end &&
|
|
||||||
*(cur->p - 1) == ')')
|
|
||||||
{
|
|
||||||
cur->p--;
|
|
||||||
}
|
|
||||||
|
|
||||||
// save the bech32 string pos in case we hit a damus.io link
|
|
||||||
block->block.str.start = (const char *)path_cur.p;
|
|
||||||
|
|
||||||
// if we have a damus link, make it a mention
|
|
||||||
if (host_len == 8
|
|
||||||
&& !strncmp((const char *)host, "damus.io", 8)
|
|
||||||
&& parse_nostr_bech32(&path_cur, &block->block.mention_bech32.bech32))
|
|
||||||
{
|
|
||||||
block->block.str.end = (const char *)path_cur.p;
|
|
||||||
block->type = BLOCK_MENTION_BECH32;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 note_block *block) {
|
|
||||||
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 note_block *block) {
|
|
||||||
u8 *start = cur->p;
|
|
||||||
|
|
||||||
parse_char(cur, '@');
|
|
||||||
parse_str(cur, "nostr:");
|
|
||||||
|
|
||||||
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 note_blocks *blocks, struct note_block block, 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 note_blocks *blocks, const char *content) {
|
|
||||||
int cp, c;
|
|
||||||
struct cursor cur;
|
|
||||||
struct note_block block;
|
|
||||||
u8 *start, *pre_mention;
|
|
||||||
|
|
||||||
blocks->words = 0;
|
|
||||||
blocks->num_blocks = 0;
|
|
||||||
make_cursor((u8*)content, (u8*)content + strlen(content), &cur);
|
|
||||||
|
|
||||||
start = cur.p;
|
|
||||||
while (cur.p < cur.end && blocks->num_blocks < MAX_BLOCKS) {
|
|
||||||
cp = peek_char(&cur, -1);
|
|
||||||
c = peek_char(&cur, 0);
|
|
||||||
|
|
||||||
// new word
|
|
||||||
if (is_whitespace(cp) && !is_whitespace(c)) {
|
|
||||||
blocks->words++;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre_mention = cur.p;
|
|
||||||
if (cp == -1 || is_left_boundary(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' || c == '@') && 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 note_blocks *blocks) {
|
|
||||||
blocks->blocks = malloc(sizeof(struct note_block) * MAX_BLOCKS);
|
|
||||||
blocks->num_blocks = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void blocks_free(struct note_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;
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
//
|
|
||||||
// damus.h
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2022-10-17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef damus_h
|
|
||||||
#define damus_h
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include "block.h"
|
|
||||||
|
|
||||||
typedef unsigned char u8;
|
|
||||||
|
|
||||||
int damus_parse_content(struct note_blocks *blocks, const char *content);
|
|
||||||
|
|
||||||
#endif /* damus_h */
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
/* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline char hexchar(unsigned int val)
|
|
||||||
{
|
|
||||||
if (val < 10)
|
|
||||||
return '0' + val;
|
|
||||||
if (val < 16)
|
|
||||||
return 'a' + val - 10;
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* CCAN_HEX_H */
|
|
||||||
@@ -1,306 +0,0 @@
|
|||||||
//
|
|
||||||
// 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, "nsec") == 0) {
|
|
||||||
*type = NOSTR_BECH32_NSEC;
|
|
||||||
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 parse_nostr_bech32_nsec(struct cursor *cur, struct bech32_nsec *nsec) {
|
|
||||||
return pull_bytes(cur, 32, &nsec->nsec);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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(obj->buffer, obj->buffer + obj->buflen, &bcur);
|
|
||||||
|
|
||||||
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_NSEC:
|
|
||||||
if (!parse_nostr_bech32_nsec(&bcur, &obj->data.nsec))
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
//
|
|
||||||
// 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,
|
|
||||||
NOSTR_BECH32_NSEC = 7,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct bech32_note {
|
|
||||||
const u8 *event_id;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct bech32_npub {
|
|
||||||
const u8 *pubkey;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct bech32_nsec {
|
|
||||||
const u8 *nsec;
|
|
||||||
};
|
|
||||||
|
|
||||||
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_nsec nsec;
|
|
||||||
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 */
|
|
||||||
@@ -1,308 +0,0 @@
|
|||||||
/* 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));
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
|
|
||||||
#ifndef PROTOVERSE_TYPEDEFS_H
|
|
||||||
#define PROTOVERSE_TYPEDEFS_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
typedef unsigned char u8;
|
|
||||||
typedef unsigned int u32;
|
|
||||||
typedef unsigned short u16;
|
|
||||||
typedef uint64_t u64;
|
|
||||||
typedef int64_t s64;
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* PROTOVERSE_TYPEDEFS_H */
|
|
||||||
@@ -1179,7 +1179,7 @@ static INLINE int parse_i64(struct cursor *read, uint64_t *val)
|
|||||||
shift = 0;
|
shift = 0;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (!pull_byte(read, &byte))
|
if (!cursor_pull_byte(read, &byte))
|
||||||
return 0;
|
return 0;
|
||||||
*val |= (byte & 0x7FULL) << shift;
|
*val |= (byte & 0x7FULL) << shift;
|
||||||
shift += 7;
|
shift += 7;
|
||||||
@@ -1199,7 +1199,7 @@ static INLINE int uleb128_read(struct cursor *read, unsigned int *val)
|
|||||||
*val = 0;
|
*val = 0;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (!pull_byte(read, &byte))
|
if (!cursor_pull_byte(read, &byte))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
*val |= (0x7F & byte) << shift;
|
*val |= (0x7F & byte) << shift;
|
||||||
@@ -1222,7 +1222,7 @@ static INLINE int sleb128_read(struct cursor *read, signed int *val)
|
|||||||
shift = 0;
|
shift = 0;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (!pull_byte(read, &byte))
|
if (!cursor_pull_byte(read, &byte))
|
||||||
return 0;
|
return 0;
|
||||||
*val |= ((byte & 0x7F) << shift);
|
*val |= ((byte & 0x7F) << shift);
|
||||||
shift += 7;
|
shift += 7;
|
||||||
@@ -1241,21 +1241,21 @@ static INLINE int uleb128_read(struct cursor *read, unsigned int *val)
|
|||||||
unsigned char p[6] = {0};
|
unsigned char p[6] = {0};
|
||||||
*val = 0;
|
*val = 0;
|
||||||
|
|
||||||
if (pull_byte(read, &p[0]) && (p[0] & 0x80) == 0) {
|
if (cursor_pull_byte(read, &p[0]) && (p[0] & 0x80) == 0) {
|
||||||
*val = LEB128_1(unsigned int);
|
*val = LEB128_1(unsigned int);
|
||||||
if (p[0] == 0x7F)
|
if (p[0] == 0x7F)
|
||||||
assert((int)*val == -1);
|
assert((int)*val == -1);
|
||||||
return 1;
|
return 1;
|
||||||
} else if (pull_byte(read, &p[1]) && (p[1] & 0x80) == 0) {
|
} else if (cursor_pull_byte(read, &p[1]) && (p[1] & 0x80) == 0) {
|
||||||
*val = LEB128_2(unsigned int);
|
*val = LEB128_2(unsigned int);
|
||||||
return 2;
|
return 2;
|
||||||
} else if (pull_byte(read, &p[2]) && (p[2] & 0x80) == 0) {
|
} else if (cursor_pull_byte(read, &p[2]) && (p[2] & 0x80) == 0) {
|
||||||
*val = LEB128_3(unsigned int);
|
*val = LEB128_3(unsigned int);
|
||||||
return 3;
|
return 3;
|
||||||
} else if (pull_byte(read, &p[3]) && (p[3] & 0x80) == 0) {
|
} else if (cursor_pull_byte(read, &p[3]) && (p[3] & 0x80) == 0) {
|
||||||
*val = LEB128_4(unsigned int);
|
*val = LEB128_4(unsigned int);
|
||||||
return 4;
|
return 4;
|
||||||
} else if (pull_byte(read, &p[4]) && (p[4] & 0x80) == 0) {
|
} else if (cursor_pull_byte(read, &p[4]) && (p[4] & 0x80) == 0) {
|
||||||
if (!(p[4] & 0xF0)) {
|
if (!(p[4] & 0xF0)) {
|
||||||
*val = LEB128_5(unsigned int);
|
*val = LEB128_5(unsigned int);
|
||||||
return 5;
|
return 5;
|
||||||
@@ -1296,7 +1296,7 @@ static int parse_section_tag(struct cursor *cur, enum section_tag *section)
|
|||||||
|
|
||||||
start = cur->p;
|
start = cur->p;
|
||||||
|
|
||||||
if (!pull_byte(cur, &byte)) {
|
if (!cursor_pull_byte(cur, &byte)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1315,7 +1315,7 @@ static int parse_valtype(struct wasm_parser *p, enum valtype *valtype)
|
|||||||
|
|
||||||
start = p->cur.p;
|
start = p->cur.p;
|
||||||
|
|
||||||
if (unlikely(!pull_byte(&p->cur, (unsigned char*)valtype))) {
|
if (unlikely(!cursor_pull_byte(&p->cur, (unsigned char*)valtype))) {
|
||||||
return parse_err(p, "valtype tag oob");
|
return parse_err(p, "valtype tag oob");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1416,7 +1416,7 @@ static int parse_export_desc(struct wasm_parser *p, enum exportdesc *desc)
|
|||||||
{
|
{
|
||||||
unsigned char byte;
|
unsigned char byte;
|
||||||
|
|
||||||
if (!pull_byte(&p->cur, &byte)) {
|
if (!cursor_pull_byte(&p->cur, &byte)) {
|
||||||
parse_err(p, "export desc byte eof");
|
parse_err(p, "export desc byte eof");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1523,7 +1523,7 @@ static int parse_name_subsection(struct wasm_parser *p, struct namesec *sec, u32
|
|||||||
u8 tag;
|
u8 tag;
|
||||||
u8 *start = p->cur.p;
|
u8 *start = p->cur.p;
|
||||||
|
|
||||||
if (!pull_byte(&p->cur, &tag))
|
if (!cursor_pull_byte(&p->cur, &tag))
|
||||||
return parse_err(p, "name subsection tag oob?");
|
return parse_err(p, "name subsection tag oob?");
|
||||||
|
|
||||||
if (!is_valid_name_subsection(tag))
|
if (!is_valid_name_subsection(tag))
|
||||||
@@ -1676,7 +1676,7 @@ static int parse_reftype(struct wasm_parser *p, enum reftype *reftype)
|
|||||||
{
|
{
|
||||||
u8 tag;
|
u8 tag;
|
||||||
|
|
||||||
if (!pull_byte(&p->cur, &tag)) {
|
if (!cursor_pull_byte(&p->cur, &tag)) {
|
||||||
parse_err(p, "reftype");
|
parse_err(p, "reftype");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1720,7 +1720,7 @@ static int parse_export_section(struct wasm_parser *p,
|
|||||||
static int parse_limits(struct wasm_parser *p, struct limits *limits)
|
static int parse_limits(struct wasm_parser *p, struct limits *limits)
|
||||||
{
|
{
|
||||||
unsigned char tag;
|
unsigned char tag;
|
||||||
if (!pull_byte(&p->cur, &tag)) {
|
if (!cursor_pull_byte(&p->cur, &tag)) {
|
||||||
return parse_err(p, "oob");
|
return parse_err(p, "oob");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1803,7 +1803,7 @@ static void print_code(u8 *code, int code_len)
|
|||||||
make_cursor(code, code + code_len, &c);
|
make_cursor(code, code + code_len, &c);
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (!pull_byte(&c, &tag)) {
|
if (!cursor_pull_byte(&c, &tag)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2169,7 +2169,7 @@ static int parse_const_expr(struct expr_parser *p, struct expr *expr)
|
|||||||
expr->code = p->code->p;
|
expr->code = p->code->p;
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
if (unlikely(!pull_byte(p->code, &tag))) {
|
if (unlikely(!cursor_pull_byte(p->code, &tag))) {
|
||||||
return note_error(p->errs, p->code, "oob");
|
return note_error(p->errs, p->code, "oob");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2332,7 +2332,7 @@ static int parse_instrs_until_at(struct expr_parser *p, u8 stop_instr,
|
|||||||
p->code->p - p->code->start,
|
p->code->p - p->code->start,
|
||||||
dbg_inst, instr_name(stop_instr));
|
dbg_inst, instr_name(stop_instr));
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (!pull_byte(p->code, &tag))
|
if (!cursor_pull_byte(p->code, &tag))
|
||||||
return note_error(p->errs, p->code, "oob");
|
return note_error(p->errs, p->code, "oob");
|
||||||
|
|
||||||
if ((tag != i_if && tag == stop_instr) ||
|
if ((tag != i_if && tag == stop_instr) ||
|
||||||
@@ -2413,7 +2413,7 @@ static int parse_element(struct wasm_parser *p, struct elem *elem)
|
|||||||
|
|
||||||
make_expr_parser(&p->errs, &p->cur, &expr_parser);
|
make_expr_parser(&p->errs, &p->cur, &expr_parser);
|
||||||
|
|
||||||
if (!pull_byte(&p->cur, &tag))
|
if (!cursor_pull_byte(&p->cur, &tag))
|
||||||
return parse_err(p, "tag");
|
return parse_err(p, "tag");
|
||||||
|
|
||||||
if (tag > 7)
|
if (tag > 7)
|
||||||
@@ -2545,7 +2545,7 @@ static int parse_wdata(struct wasm_parser *p, struct wdata *data)
|
|||||||
struct expr_parser parser;
|
struct expr_parser parser;
|
||||||
u8 tag;
|
u8 tag;
|
||||||
|
|
||||||
if (!pull_byte(&p->cur, &tag)) {
|
if (!cursor_pull_byte(&p->cur, &tag)) {
|
||||||
return parse_err(p, "tag");
|
return parse_err(p, "tag");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2700,7 +2700,7 @@ static int parse_importdesc(struct wasm_parser *p, struct importdesc *desc)
|
|||||||
{
|
{
|
||||||
u8 tag;
|
u8 tag;
|
||||||
|
|
||||||
if (!pull_byte(&p->cur, &tag)) {
|
if (!cursor_pull_byte(&p->cur, &tag)) {
|
||||||
parse_err(p, "oom");
|
parse_err(p, "oom");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -4134,7 +4134,7 @@ static int parse_blocktype(struct cursor *cur, struct errors *errs, struct block
|
|||||||
{
|
{
|
||||||
unsigned char byte;
|
unsigned char byte;
|
||||||
|
|
||||||
if (unlikely(!pull_byte(cur, &byte))) {
|
if (unlikely(!cursor_pull_byte(cur, &byte))) {
|
||||||
return note_error(errs, cur, "parse_blocktype: oob\n");
|
return note_error(errs, cur, "parse_blocktype: oob\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4656,7 +4656,7 @@ static int parse_bulk_op(struct cursor *code, struct errors *errs,
|
|||||||
{
|
{
|
||||||
u8 tag;
|
u8 tag;
|
||||||
|
|
||||||
if (unlikely(!pull_byte(code, &tag)))
|
if (unlikely(!cursor_pull_byte(code, &tag)))
|
||||||
return note_error(errs, code, "oob");
|
return note_error(errs, code, "oob");
|
||||||
|
|
||||||
if (unlikely(tag < 10 || tag > 17))
|
if (unlikely(tag < 10 || tag > 17))
|
||||||
@@ -6552,7 +6552,7 @@ static INLINE int interp_parse_instr(struct wasm_interp *interp,
|
|||||||
{
|
{
|
||||||
u8 tag;
|
u8 tag;
|
||||||
|
|
||||||
if (unlikely(!pull_byte(code, &tag))) {
|
if (unlikely(!cursor_pull_byte(code, &tag))) {
|
||||||
return interp_error(interp, "no more instrs to pull");
|
return interp_error(interp, "no more instrs to pull");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ static const unsigned char WASM_MAGIC[] = {0,'a','s','m'};
|
|||||||
#define interp_error(p, fmt, ...) note_error(&((p)->errors), interp_codeptr(p), fmt, ##__VA_ARGS__)
|
#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__)
|
#define parse_err(p, fmt, ...) note_error(&((p)->errs), &(p)->cur, fmt, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#include "short_types.h"
|
||||||
|
|
||||||
enum valtype {
|
enum valtype {
|
||||||
val_i32 = 0x7F,
|
val_i32 = 0x7F,
|
||||||
val_i64 = 0x7E,
|
val_i64 = 0x7E,
|
||||||
|
|||||||
@@ -1,5 +1,49 @@
|
|||||||
{
|
{
|
||||||
|
"originHash" : "1fc7e0b44329ba72cd285eeb022b5b92582cd01586b920d243cb0485c2e69dcc",
|
||||||
"pins" : [
|
"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",
|
"identity" : "gsplayer",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@@ -14,8 +58,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/onevcat/Kingfisher",
|
"location" : "https://github.com/onevcat/Kingfisher",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "415b1d97fb38bda1e5a6b2dde63354720832110b",
|
"revision" : "4c6b067f96953ee19526e49e4189403a2be21fb3",
|
||||||
"version" : "7.6.1"
|
"version" : "8.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -26,6 +70,15 @@
|
|||||||
"revision" : "40b4b38b3b1c83f7088c76189a742870e0ca06a9"
|
"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",
|
"identity" : "swift-markdown-ui",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@@ -51,7 +104,41 @@
|
|||||||
"revision" : "74203046135342e4a4a627476dd6caf8b28fe11b",
|
"revision" : "74203046135342e4a4a627476dd6caf8b28fe11b",
|
||||||
"version" : "509.0.0"
|
"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" : 2
|
"version" : 3
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1500"
|
LastUpgradeVersion = "1520"
|
||||||
wasCreatedForAppExtension = "YES"
|
wasCreatedForAppExtension = "YES"
|
||||||
version = "2.0">
|
version = "2.0">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
<RemoteRunnable
|
<RemoteRunnable
|
||||||
runnableDebuggingMode = "1"
|
runnableDebuggingMode = "1"
|
||||||
BundleIdentifier = "com.jb55.damus2"
|
BundleIdentifier = "com.jb55.damus2"
|
||||||
RemotePath = "/Users/danielnogueira/Library/Developer/CoreSimulator/Devices/99E60B35-CE5D-4B45-AC35-00818C0AF3CB/data/Containers/Bundle/Application/5A083DD0-FDE2-43D7-9172-2F97FAD18F20/damus.app">
|
RemotePath = "/Users/danielnogueira/Library/Developer/CoreSimulator/Devices/99E60B35-CE5D-4B45-AC35-00818C0AF3CB/data/Containers/Bundle/Application/7D0A5302-D07E-4C7C-B509-A7C552BD5A65/damus.app">
|
||||||
</RemoteRunnable>
|
</RemoteRunnable>
|
||||||
<MacroExpansion>
|
<MacroExpansion>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
<?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"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1500"
|
LastUpgradeVersion = "1520"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1500"
|
LastUpgradeVersion = "1520"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</TestableReference>
|
</TestableReference>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
skipped = "YES">
|
skipped = "NO">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "4CE6DEFC27F7A08200C66700"
|
BlueprintIdentifier = "4CE6DEFC27F7A08200C66700"
|
||||||
@@ -55,6 +55,7 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
enableAddressSanitizer = "YES"
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
@@ -71,6 +72,9 @@
|
|||||||
ReferencedContainer = "container:damus.xcodeproj">
|
ReferencedContainer = "container:damus.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
|
<StoreKitConfigurationFileReference
|
||||||
|
identifier = "../../Purple.storekit">
|
||||||
|
</StoreKitConfigurationFileReference>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Release"
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
//
|
||||||
|
// 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: 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
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "shadow-2.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.0 MiB |
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "shadow.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 511 KiB |
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x1A",
|
||||||
|
"green" : "0x93",
|
||||||
|
"red" : "0xF7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0xF2",
|
||||||
|
"green" : "0xD8",
|
||||||
|
"red" : "0xF4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x45",
|
||||||
|
"green" : "0x17",
|
||||||
|
"red" : "0x47"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 187 KiB |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "profile-banner.jpeg",
|
"filename" : "damoose.jpeg",
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 547 KiB After Width: | Height: | Size: 547 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,328 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="500"
|
||||||
|
height="130"
|
||||||
|
viewBox="0 0 132.29166 34.395832"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="0.92.1 r15371"
|
||||||
|
sodipodi:docname="ActivityPub-logo.svg">
|
||||||
|
<title
|
||||||
|
id="title4590">ActivityPub logo</title>
|
||||||
|
<defs
|
||||||
|
id="defs2">
|
||||||
|
<linearGradient
|
||||||
|
id="AP-4-0"
|
||||||
|
osb:paint="solid">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#5e5e5e;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop5660" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient5640"
|
||||||
|
osb:paint="solid">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop5638" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient5634"
|
||||||
|
osb:paint="solid">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop5632" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient5628"
|
||||||
|
osb:paint="solid">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop5626" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id="AP-3-7"
|
||||||
|
osb:paint="solid">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#c678c5;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop5498" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id="AP-2-3"
|
||||||
|
osb:paint="solid">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#6d6d6d;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop5230" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id="AP1-5"
|
||||||
|
osb:paint="solid">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#f1007e;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop5212" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#AP-3-7"
|
||||||
|
id="linearGradient5749"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="3319.292"
|
||||||
|
y1="-1291.2802"
|
||||||
|
x2="3344.3645"
|
||||||
|
y2="-1291.2802" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#AP1-5"
|
||||||
|
id="linearGradient7297-7"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="3241.6836"
|
||||||
|
y1="-1355.4329"
|
||||||
|
x2="3254.9529"
|
||||||
|
y2="-1355.4329" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#AP-2-3"
|
||||||
|
id="linearGradient7303-7"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="3225.7603"
|
||||||
|
y1="-1355.4329"
|
||||||
|
x2="3239.0295"
|
||||||
|
y2="-1355.4329" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#AP1-5"
|
||||||
|
id="linearGradient8308"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="3241.6836"
|
||||||
|
y1="-1355.4329"
|
||||||
|
x2="3254.9529"
|
||||||
|
y2="-1355.4329" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#AP1-5"
|
||||||
|
id="linearGradient8310"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="3241.6836"
|
||||||
|
y1="-1355.4329"
|
||||||
|
x2="3254.9529"
|
||||||
|
y2="-1355.4329" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#AP1-5"
|
||||||
|
id="linearGradient8312"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="3241.6836"
|
||||||
|
y1="-1355.4329"
|
||||||
|
x2="3254.9529"
|
||||||
|
y2="-1355.4329" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#AP-2-3"
|
||||||
|
id="linearGradient8314"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="3225.7603"
|
||||||
|
y1="-1355.4329"
|
||||||
|
x2="3239.0295"
|
||||||
|
y2="-1355.4329"
|
||||||
|
gradientTransform="matrix(3.7000834,0,0,3.7000834,-11935.582,4544.6634)" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#AP-2-3"
|
||||||
|
id="linearGradient5188"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(0.42732603,0,0,0.42732603,-1363.3009,454.91899)"
|
||||||
|
x1="3269.126"
|
||||||
|
y1="-1354.6217"
|
||||||
|
x2="3322.1943"
|
||||||
|
y2="-1354.6217" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#AP-2-3"
|
||||||
|
id="linearGradient4523"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(3.5811973,0,0,3.5811973,-11532.084,4918.1922)"
|
||||||
|
x1="3269.126"
|
||||||
|
y1="-1354.6217"
|
||||||
|
x2="3322.1943"
|
||||||
|
y2="-1354.6217" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#AP1-5"
|
||||||
|
id="linearGradient4526"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(3.5811973,0,0,3.5811973,-11528.758,4918.1922)"
|
||||||
|
x1="3323.9951"
|
||||||
|
y1="-1356.5363"
|
||||||
|
x2="3349.0676"
|
||||||
|
y2="-1356.5363" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="0.14509804"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.5"
|
||||||
|
inkscape:cx="395.506"
|
||||||
|
inkscape:cy="-201.19903"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:snap-global="true"
|
||||||
|
showguides="false"
|
||||||
|
inkscape:guide-bbox="true"
|
||||||
|
showborder="true"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0"
|
||||||
|
inkscape:showpageshadow="false"
|
||||||
|
borderlayer="false"
|
||||||
|
units="px">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4572"
|
||||||
|
enabled="false"
|
||||||
|
originx="7.1437514"
|
||||||
|
originy="-404.28382" />
|
||||||
|
<inkscape:grid
|
||||||
|
type="axonomgrid"
|
||||||
|
id="grid4574"
|
||||||
|
units="mm"
|
||||||
|
empspacing="12"
|
||||||
|
originx="7.1437514"
|
||||||
|
originy="-404.28382"
|
||||||
|
enabled="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="3278.981,1256.5057"
|
||||||
|
orientation="0,1"
|
||||||
|
id="guide5059"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="3278.981,1238.2495"
|
||||||
|
orientation="0,1"
|
||||||
|
id="guide5061"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title>ActivityPub logo</dc:title>
|
||||||
|
<cc:license
|
||||||
|
rdf:resource="http://creativecommons.org/publicdomain/zero/1.0/" />
|
||||||
|
<dc:date>2017-04-15</dc:date>
|
||||||
|
<dc:creator>
|
||||||
|
<cc:Agent>
|
||||||
|
<dc:title>Robert Martinez</dc:title>
|
||||||
|
</cc:Agent>
|
||||||
|
</dc:creator>
|
||||||
|
<dc:subject>
|
||||||
|
<rdf:Bag>
|
||||||
|
<rdf:li>ActivityPub</rdf:li>
|
||||||
|
</rdf:Bag>
|
||||||
|
</dc:subject>
|
||||||
|
</cc:Work>
|
||||||
|
<cc:License
|
||||||
|
rdf:about="http://creativecommons.org/publicdomain/zero/1.0/">
|
||||||
|
<cc:permits
|
||||||
|
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||||
|
<cc:permits
|
||||||
|
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||||
|
<cc:permits
|
||||||
|
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||||
|
</cc:License>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="opacity:1"
|
||||||
|
transform="translate(7.1437516,141.67967)">
|
||||||
|
<path
|
||||||
|
style="fill:#000000;stroke-width:0.26458335"
|
||||||
|
d=""
|
||||||
|
id="path5497"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<g
|
||||||
|
id="g5197"
|
||||||
|
transform="translate(1.3229166)">
|
||||||
|
<g
|
||||||
|
id="g5132-90"
|
||||||
|
style="fill:url(#linearGradient7297-7);fill-opacity:1"
|
||||||
|
transform="matrix(0.9789804,0,0,0.9789804,-3157.9561,1202.4422)">
|
||||||
|
<g
|
||||||
|
transform="matrix(0.2553682,0,0,0.2553682,2615.9213,-1125.3113)"
|
||||||
|
id="g5080-78"
|
||||||
|
style="fill:url(#linearGradient8312);fill-opacity:1">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path5404-0-0"
|
||||||
|
d="m 2450.431,-937.13662 51.9615,30 v 12 l -51.9615,30 v -12 l 41.5693,-24 -41.5692,-24 z"
|
||||||
|
style="fill:url(#linearGradient8308);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cccc"
|
||||||
|
style="fill:url(#linearGradient8310);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 2450.431,-913.13662 20.7847,12 -20.7847,12 z"
|
||||||
|
id="path5406-6-3"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g5127-1"
|
||||||
|
style="fill:url(#linearGradient7303-7);fill-opacity:1"
|
||||||
|
transform="matrix(0.9789804,0,0,0.9789804,-3157.9561,1202.4422)">
|
||||||
|
<path
|
||||||
|
id="path5467-2-0"
|
||||||
|
transform="matrix(0.27026418,0,0,0.27026418,3225.7603,-1228.2597)"
|
||||||
|
d="M 49.097656,-504.56641 0,-476.2207 v 11.33789 l 39.277344,-22.67578 v 45.35351 l 9.820312,5.66992 z m -19.638672,34.01563 -19.6406246,11.33789 19.6406246,11.33789 z"
|
||||||
|
style="fill:url(#linearGradient8314);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25000042px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g5203"
|
||||||
|
transform="matrix(2.2173353,0,0,2.2173353,-35.445741,150.88402)">
|
||||||
|
<g
|
||||||
|
id="g4523">
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="scscscscsscscscscscccccccccccccccscsccccscscccccccccccscsccccscsccccccccccccscscccsccccscscsccccscccccccccccccccccccccccccccccscssccccccccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="text5037-6"
|
||||||
|
transform="matrix(0.1193249,0,0,0.1193249,12.763965,-131.94382)"
|
||||||
|
d="m 263.22656,34.349609 c -1.66644,0 -2.95278,0.477436 -3.85742,1.429688 -0.90464,0.904639 -1.35742,2.069669 -1.35742,3.498047 0,1.428378 0.45278,2.59536 1.35742,3.5 0.90464,0.857027 2.19098,1.285156 3.85742,1.285156 1.66644,0 2.99818,-0.428129 3.99805,-1.285156 0.99986,-0.857027 1.5,-2.024009 1.5,-3.5 0,-1.475991 -0.50014,-2.665673 -1.5,-3.570313 -0.99987,-0.904639 -2.33161,-1.357422 -3.99805,-1.357422 z m 43.95117,0 c -1.66644,0 -2.95082,0.477436 -3.85546,1.429688 -0.90464,0.904639 -1.35743,2.069669 -1.35743,3.498047 0,1.428378 0.45279,2.59536 1.35743,3.5 0.90464,0.857027 2.18902,1.285156 3.85546,1.285156 1.66645,0 3.00014,-0.428129 4,-1.285156 0.99987,-0.857027 1.5,-2.024009 1.5,-3.5 0,-1.475991 -0.50013,-2.665673 -1.5,-3.570313 -0.99986,-0.904639 -2.33355,-1.357422 -4,-1.357422 z m -118.46166,0.357422 -14.49805,50.351563 h 8.92774 l 2.92773,-11.285156 h 11.78516 l 3.07031,11.285156 h 9.42773 L 195.78638,34.707031 Z m 58.71166,5.285157 -8.49804,2.642578 v 6.71289 h -3.92774 v 7.570313 h 3.92774 v 18.71289 c 0,3.713784 0.66684,6.356519 2,7.927735 1.38076,1.571216 3.42866,2.355468 6.14258,2.355468 1.5236,0 2.9747,-0.189411 4.35546,-0.570312 1.38077,-0.333288 2.59511,-0.761418 3.64258,-1.285156 L 254,77.273438 c -0.66658,0.285675 -1.28607,0.49974 -1.85742,0.642578 -0.57135,0.142837 -1.2155,0.214843 -1.92969,0.214843 -1.04748,0 -1.78438,-0.430082 -2.21289,-1.287109 -0.3809,-0.857027 -0.57227,-2.308127 -0.57227,-4.355469 V 56.917969 h 6.92774 v -7.570313 h -6.92774 z m 80.23243,0 -8.49805,2.642578 v 6.71289 h -3.92969 v 7.570313 h 3.92969 v 18.71289 c 0,3.713784 0.66489,6.356519 1.99805,7.927735 1.38076,1.571216 3.42866,2.355468 6.14257,2.355468 1.52361,0 2.97666,-0.189411 4.35743,-0.570312 1.38076,-0.333288 2.5951,-0.761418 3.64257,-1.285156 l -1.07226,-6.785156 c -0.66658,0.285675 -1.28607,0.49974 -1.85742,0.642578 -0.57135,0.142837 -1.21355,0.214843 -1.92774,0.214843 -1.04747,0 -1.78437,-0.430082 -2.21289,-1.287109 -0.3809,-0.857027 -0.57226,-2.308127 -0.57226,-4.355469 V 56.917969 h 6.92773 v -7.570313 h -6.92773 z m -135.65894,6.855468 h 0.28516 l 1.14453,7.857422 2.85547,11.640625 h -8.2832 l 2.78515,-11.570312 z m 31.94605,1.572266 c -4.33275,0 -7.61963,1.570458 -9.85743,4.71289 -2.23779,3.142433 -3.35546,7.833061 -3.35546,14.070313 0,2.856756 0.21406,5.452139 0.64257,7.785156 0.47613,2.285405 1.23768,4.261293 2.28516,5.927735 1.04748,1.618828 2.38117,2.880516 4,3.785156 1.66644,0.857027 3.71238,1.285156 6.14062,1.285156 1.66645,0 3.33356,-0.238718 5,-0.714844 1.66645,-0.476126 3.09485,-1.190326 4.28516,-2.142578 l -1.78516,-6.570312 c -0.71418,0.476126 -1.50039,0.881555 -2.35742,1.214844 -0.80941,0.285675 -1.80968,0.427734 -3,0.427734 -2.23779,0 -3.88025,-1.022971 -4.92773,-3.070313 -0.99987,-2.047342 -1.5,-4.690077 -1.5,-7.927734 0,-3.856621 0.50013,-6.641415 1.5,-8.355469 0.99986,-1.761666 2.50027,-2.642578 4.5,-2.642578 1.09509,0 2.02335,0.117406 2.78515,0.355469 0.80942,0.19045 1.64298,0.501174 2.5,0.929687 l 2,-7.070312 c -1.04747,-0.571351 -2.26181,-1.048787 -3.64257,-1.429688 -1.33316,-0.3809 -3.07033,-0.570312 -5.21289,-0.570312 z m 35.06445,0.927734 v 35.710938 h 8.5 V 49.347656 Z m 11.05469,0 12.64257,36.066406 h 5.7129 l 11.99804,-36.066406 h -9.14062 l -4.42774,18.570313 -0.78711,5.71289 h -0.28515 l -0.85742,-5.642578 -4.92774,-18.640625 z m 32.89843,0 v 35.710938 h 8.49805 V 49.347656 Z m 33.53125,0 12.42774,35.710938 c -0.28568,1.571216 -0.64375,2.832904 -1.07227,3.785156 -0.42851,0.952252 -0.92865,1.641799 -1.5,2.070312 -0.52374,0.476127 -1.11858,0.714844 -1.78515,0.714844 -0.61897,0.04761 -1.23846,-0.04905 -1.85743,-0.287109 l -1.42773,7.285156 c 0.66658,0.380901 1.45278,0.642319 2.35742,0.785156 0.95225,0.190451 1.92787,0.28711 2.92774,0.28711 1.42837,0 2.64271,-0.430083 3.64257,-1.28711 1.04748,-0.809414 1.97575,-1.999096 2.78516,-3.570312 0.80941,-1.571216 1.57097,-3.475098 2.28516,-5.712891 0.71419,-2.237792 1.47574,-4.761168 2.28515,-7.570312 l 8.92774,-32.210938 h -8.71289 l -4.14258,19.998047 -0.64258,5.642578 h -0.35742 l -0.92774,-5.572265 -5,-20.06836 z"
|
||||||
|
style="color:#000000;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:8.52205467px;line-height:2.53632545px;font-family:'PT Sans Narrow';-inkscape-font-specification:'PT Sans Narrow Bold Condensed';letter-spacing:0.22319667px;word-spacing:2.60024095px;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#linearGradient4523);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.27365798px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||||
|
<path
|
||||||
|
id="text5065-3"
|
||||||
|
transform="matrix(0.1193249,0,0,0.1193249,12.763965,-131.94382)"
|
||||||
|
d="m 386.9082,34.349609 c -2.04734,0 -4.09523,0.119359 -6.14258,0.357422 -2.04734,0.190451 -3.92657,0.476521 -5.64062,0.857422 v 49.494141 h 8.99805 V 67.845703 c 0.19045,0.04761 0.49922,0.09497 0.92773,0.142578 l 1.42969,0.142578 h 1.35742 0.92773 c 2.04735,0 4.02324,-0.30877 5.92774,-0.927734 1.95212,-0.618964 3.66659,-1.619234 5.14258,-3 1.47599,-1.380766 2.64102,-3.165289 3.49804,-5.355469 0.90464,-2.19018 1.35743,-4.857568 1.35743,-8 0,-3.47572 -0.52284,-6.285167 -1.57032,-8.427734 -1.04747,-2.19018 -2.40582,-3.879997 -4.07226,-5.070313 -1.66644,-1.190315 -3.57033,-1.976521 -5.71289,-2.357421 -2.09496,-0.428514 -4.23756,-0.642579 -6.42774,-0.642579 z m 51.72461,0.714844 v 48.564453 c 1.14271,0.571352 2.76052,1.09614 4.85547,1.572266 2.14257,0.476126 4.47653,0.71289 7,0.71289 4.61842,0 8.25948,-1.570458 10.92578,-4.71289 2.66631,-3.190045 4,-8.164791 4,-14.925781 0,-6.237252 -0.95292,-10.761169 -2.85742,-13.570313 -1.85689,-2.809144 -4.47497,-4.214844 -7.85547,-4.214844 -3.19004,0 -5.64141,1.047624 -7.35547,3.142578 h -0.21484 V 35.064453 Z m -50.86719,7.285156 c 0.99987,0 1.95279,0.142059 2.85743,0.427735 0.90464,0.285675 1.68889,0.761158 2.35547,1.427734 0.71418,0.618964 1.26167,1.477176 1.64257,2.572266 0.42852,1.09509 0.64258,2.428784 0.64258,4 0,1.856891 -0.21406,3.402697 -0.64258,4.640625 -0.42851,1.190315 -1.02335,2.143233 -1.78515,2.857422 -0.71419,0.666576 -1.54775,1.142058 -2.5,1.427734 -0.95225,0.285676 -1.95057,0.429687 -2.99805,0.429687 -0.28568,10e-7 -0.83316,-0.02465 -1.64258,-0.07227 -0.7618,-0.09522 -1.28659,-0.189931 -1.57226,-0.285156 v -17.06836 c 0.95225,-0.238063 2.16658,-0.357422 3.64257,-0.357422 z m 20.31836,6.998047 v 23.210938 c 0,2.666306 0.21407,4.880911 0.64258,6.642578 0.42852,1.714054 1.04606,3.070448 1.85547,4.070312 0.80942,0.999865 1.78699,1.691365 2.92969,2.072266 1.1427,0.428513 2.45174,0.642578 3.92773,0.642578 2.28541,0 4.18929,-0.547488 5.71289,-1.642578 1.57122,-1.09509 2.81021,-2.476137 3.71485,-4.142578 h 0.21289 l 1.5,4.857422 h 6.42773 c -0.3809,-1.523604 -0.64232,-3.215374 -0.78515,-5.072266 -0.14284,-1.904504 -0.21485,-3.833039 -0.21485,-5.785156 V 49.347656 h -8.49804 v 23.853516 c -0.38091,1.380765 -1.02505,2.572401 -1.92969,3.572266 -0.90464,0.952252 -2.02232,1.427734 -3.35547,1.427734 -1.38077,0 -2.33368,-0.547488 -2.85742,-1.642578 -0.52374,-1.09509 -0.78516,-3.046325 -0.78516,-5.855469 V 49.347656 Z m 43.83204,6.927735 c 1.61882,0 2.80851,0.858211 3.57031,2.572265 0.7618,1.666441 1.14258,4.307223 1.14258,7.925782 0,4.094684 -0.47549,7.023489 -1.42774,8.785156 -0.95225,1.714054 -2.3333,2.572265 -4.14258,2.572265 -1.61882,0 -2.92787,-0.26337 -3.92773,-0.787109 V 59.990234 c 0.80941,-2.475855 2.40453,-3.714843 4.78516,-3.714843 z"
|
||||||
|
style="color:#000000;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:8.52205467px;line-height:2.53632545px;font-family:'PT Sans Narrow';-inkscape-font-specification:'PT Sans Narrow Bold Condensed';letter-spacing:0.22319667px;word-spacing:2.60024095px;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#linearGradient4526);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.24196777px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ActivityPub-logo.svg",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "alby.svg",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "alby.svg",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "alby.svg",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "atproto.png",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 300 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "blink.png",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 215 KiB After Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "coinos.png",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 176 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "mutiny.png",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |