├── .changeset ├── README.md ├── config.json ├── funny-jeans-deny.md ├── nervous-ears-tease.md ├── purple-flowers-pay.md └── soft-trees-battle.md ├── .eslintrc.js ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmrc ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── configs ├── eslint-config-custom │ ├── index.js │ └── package.json └── tsconfig │ ├── base.json │ ├── nextjs.json │ ├── package.json │ └── react-library.json ├── docs ├── .gitignore ├── CHANGELOG.md ├── Logo.jsx ├── README.md ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages │ ├── _meta.json │ ├── create-mod │ │ ├── _meta.json │ │ ├── getting-started.mdx │ │ └── reference.mdx │ ├── data-storage.mdx │ ├── faq.mdx │ ├── farcaster-frames.mdx │ ├── index.mdx │ ├── integrate │ │ ├── _meta.json │ │ ├── creation.mdx │ │ ├── getting-started.mdx │ │ └── rich-embeds.mdx │ ├── metadata-cache.mdx │ ├── mod-editor.mdx │ ├── mods-list.mdx │ ├── permissions.mdx │ ├── security.mdx │ └── ui │ │ ├── _meta.json │ │ ├── getting-started.mdx │ │ └── react-shadcn.mdx ├── public │ ├── all-mods.png │ ├── create-mods.png │ ├── create-poll.png │ ├── creation.png │ ├── farcaster-frames.gif │ ├── favicon.ico │ ├── mod-editor.png │ ├── mod-protocol-white.svg │ ├── mod-protocol.svg │ ├── mod.svg │ ├── mods.png │ ├── og.png │ ├── render-poll-mod.png │ └── rich-embed.png ├── theme.config.tsx └── tsconfig.json ├── examples ├── api │ ├── .env.example │ ├── .eslintrc.js │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── src │ │ ├── app │ │ │ └── api │ │ │ │ ├── cast-embeds-metadata │ │ │ │ ├── by-url │ │ │ │ │ └── route.ts │ │ │ │ ├── lib │ │ │ │ │ ├── chain-index.ts │ │ │ │ │ └── db.ts │ │ │ │ ├── route.ts │ │ │ │ └── types │ │ │ │ │ └── db.d.ts │ │ │ │ ├── chatgpt-shorten │ │ │ │ └── route.ts │ │ │ │ ├── chatgpt │ │ │ │ └── route.ts │ │ │ │ ├── dall-e │ │ │ │ └── route.ts │ │ │ │ ├── farcaster │ │ │ │ ├── channels │ │ │ │ │ ├── route.ts │ │ │ │ │ └── v2 │ │ │ │ │ │ ├── levenshtein-distance.ts │ │ │ │ │ │ └── route.ts │ │ │ │ ├── mentions │ │ │ │ │ └── route.ts │ │ │ │ └── user-by-username │ │ │ │ │ └── route.ts │ │ │ │ ├── giphy-picker │ │ │ │ └── route.ts │ │ │ │ ├── imgur-upload │ │ │ │ └── route.ts │ │ │ │ ├── infura-ipfs-upload │ │ │ │ └── route.ts │ │ │ │ ├── livepeer-video │ │ │ │ └── route.ts │ │ │ │ ├── nft-chain-logo │ │ │ │ ├── lib │ │ │ │ │ └── chains │ │ │ │ │ │ ├── chain-icon.ts │ │ │ │ │ │ └── chain-index.ts │ │ │ │ └── route.ts │ │ │ │ ├── nft-minter │ │ │ │ └── route.ts │ │ │ │ ├── open-graph │ │ │ │ ├── lib │ │ │ │ │ ├── chains │ │ │ │ │ │ └── chain-index.ts │ │ │ │ │ ├── url-handlers │ │ │ │ │ │ ├── arweave.ts │ │ │ │ │ │ ├── caip-19.ts │ │ │ │ │ │ ├── image-file.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── local-fetch.ts │ │ │ │ │ │ ├── metascraper.ts │ │ │ │ │ │ ├── opensea.ts │ │ │ │ │ │ ├── zora-premint.ts │ │ │ │ │ │ └── zora.ts │ │ │ │ │ └── util.ts │ │ │ │ ├── route.ts │ │ │ │ └── types │ │ │ │ │ └── url-handler.ts │ │ │ │ └── zora-create │ │ │ │ └── route.ts │ │ └── middleware.ts │ └── tsconfig.json ├── metadata-indexer │ ├── .env.sample │ ├── .eslintrc.js │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── docker-compose.yml │ ├── jest.config.ts │ ├── nixpacks.toml │ ├── package.json │ ├── src │ │ ├── db.ts │ │ ├── hubReplicator.ts │ │ ├── hubSubscriber.ts │ │ ├── index.ts │ │ ├── indexerQueue.ts │ │ ├── log.ts │ │ ├── migrations │ │ │ ├── 001_initial_migration.ts │ │ │ └── 002_customog.ts │ │ ├── server.ts │ │ └── util │ │ │ ├── normalizeUrl.ts │ │ │ ├── util.test.ts │ │ │ └── util.ts │ └── tsconfig.json └── nextjs-shadcn │ ├── .env.example │ ├── .eslintrc.js │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── postcss.config.js │ ├── src │ └── app │ │ ├── cast.tsx │ │ ├── dummy-casts.ts │ │ ├── editor-example.tsx │ │ ├── embeds.tsx │ │ ├── globals.css │ │ ├── layout.tsx │ │ ├── make-message.ts │ │ ├── page.tsx │ │ ├── post-message │ │ └── frame-action │ │ │ └── route.ts │ │ ├── relative-date.ts │ │ ├── signer │ │ └── route.ts │ │ ├── use-experimental-mods.ts │ │ └── use-farcaster-connect.ts │ ├── tailwind.config.js │ └── tsconfig.json ├── mods ├── chatgpt-shorten │ ├── CHANGELOG.md │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── action.ts │ │ ├── error.ts │ │ ├── loading.ts │ │ ├── manifest.ts │ │ └── success.ts │ └── tsconfig.json ├── chatgpt │ ├── CHANGELOG.md │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── action.ts │ │ ├── error.ts │ │ ├── loading.ts │ │ ├── manifest.ts │ │ └── success.ts │ └── tsconfig.json ├── dall-e │ ├── CHANGELOG.md │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── action.ts │ │ ├── error.ts │ │ ├── loading.ts │ │ ├── manifest.ts │ │ └── success.ts │ └── tsconfig.json ├── farcaster-frames-render │ ├── CHANGELOG.md │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── manifest.ts │ │ ├── rerender.ts │ │ └── view.ts │ └── tsconfig.json ├── giphy-picker │ ├── CHANGELOG.md │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── action.ts │ │ ├── error.ts │ │ ├── loading.ts │ │ ├── manifest.ts │ │ └── success.ts │ └── tsconfig.json ├── image-render │ ├── CHANGELOG.md │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── manifest.ts │ │ └── view.ts │ └── tsconfig.json ├── imgur-upload │ ├── CHANGELOG.md │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── action.ts │ │ ├── error.ts │ │ ├── loading.ts │ │ ├── manifest.ts │ │ └── upload.ts │ └── tsconfig.json ├── infura-ipfs-upload │ ├── CHANGELOG.md │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── action.ts │ │ ├── error.ts │ │ ├── loading.ts │ │ ├── manifest.ts │ │ └── upload.ts │ └── tsconfig.json ├── livepeer-video │ ├── CHANGELOG.md │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── action.ts │ │ ├── error.ts │ │ ├── loading.ts │ │ ├── manifest.ts │ │ └── upload.ts │ └── tsconfig.json ├── nft-minter │ ├── CHANGELOG.md │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── manifest.ts │ │ └── view.ts │ └── tsconfig.json ├── url-render │ ├── CHANGELOG.md │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── fullimage.ts │ │ └── manifest.ts │ └── tsconfig.json ├── video-render │ ├── CHANGELOG.md │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── manifest.ts │ │ └── view.ts │ └── tsconfig.json ├── zora-create │ ├── CHANGELOG.md │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── action.ts │ │ ├── error.ts │ │ ├── loading.ts │ │ └── manifest.ts │ └── tsconfig.json └── zora-nft-minter │ ├── CHANGELOG.md │ ├── index.ts │ ├── package.json │ ├── src │ ├── manifest.ts │ └── view.ts │ └── tsconfig.json ├── package.json ├── packages ├── core │ ├── .eslintrc.js │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── embeds.ts │ │ ├── index.ts │ │ ├── manifest.ts │ │ ├── renderer.ts │ │ └── web-handlers.ts │ └── tsconfig.json ├── farcaster │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── api-helpers.ts │ │ ├── channels.ts │ │ ├── format-cast-for-hub.ts │ │ ├── index.ts │ │ ├── mentions.ts │ │ ├── regexps.ts │ │ ├── structure-cast.ts │ │ └── types.ts │ └── tsconfig.json ├── mod-registry │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── react-editor │ ├── .eslintrc.js │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── create-editor-config.tsx │ │ ├── errors.ts │ │ ├── extension-clipboard.tsx │ │ ├── extension-savedraft.tsx │ │ ├── extension-warnonnavigate.tsx │ │ ├── index.tsx │ │ ├── use-editor.tsx │ │ ├── use-key-press.tsx │ │ └── use-text-length.tsx │ └── tsconfig.json ├── react-ui-shadcn │ ├── .eslintrc.js │ ├── CHANGELOG.md │ ├── README.md │ ├── components.json │ ├── package.json │ ├── postcss.config.js │ ├── src │ │ ├── components │ │ │ ├── cast-length-ui-indicator.tsx │ │ │ ├── channel-list.tsx │ │ │ ├── channel-picker.tsx │ │ │ ├── creation-mods-search.tsx │ │ │ ├── mention-list.tsx │ │ │ ├── theme-provider.tsx │ │ │ └── ui │ │ │ │ ├── alert.tsx │ │ │ │ ├── aspect-ratio.tsx │ │ │ │ ├── avatar.tsx │ │ │ │ ├── button.tsx │ │ │ │ ├── circular-progress.tsx │ │ │ │ ├── command.tsx │ │ │ │ ├── dialog.tsx │ │ │ │ ├── input.tsx │ │ │ │ ├── link.tsx │ │ │ │ ├── popover.tsx │ │ │ │ ├── scroll-area.tsx │ │ │ │ ├── select.tsx │ │ │ │ ├── sheet.tsx │ │ │ │ ├── skeleton.tsx │ │ │ │ ├── tabs.tsx │ │ │ │ └── textarea.tsx │ │ ├── lib │ │ │ ├── embeds.tsx │ │ │ ├── mentions.ts │ │ │ └── utils.ts │ │ ├── public │ │ │ └── video-js.css │ │ └── renderers │ │ │ ├── alert.tsx │ │ │ ├── avatar.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── circular-progress.tsx │ │ │ ├── combobox.tsx │ │ │ ├── container.tsx │ │ │ ├── dialog.tsx │ │ │ ├── horizontal-layout.tsx │ │ │ ├── image-grid-list.tsx │ │ │ ├── image.tsx │ │ │ ├── index.tsx │ │ │ ├── input.tsx │ │ │ ├── link.tsx │ │ │ ├── select.tsx │ │ │ ├── tabs.tsx │ │ │ ├── text.tsx │ │ │ ├── textarea.tsx │ │ │ ├── vertical-layout.tsx │ │ │ └── video.tsx │ ├── tailwind.config.js │ └── tsconfig.json ├── react │ ├── .eslintrc.js │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── action-resolver-add-embed.ts │ │ ├── action-resolver-eth-personal-sign.ts │ │ ├── action-resolver-exit.ts │ │ ├── action-resolver-http.ts │ │ ├── action-resolver-open-file.ts │ │ ├── action-resolver-open-link.ts │ │ ├── action-resolver-send-eth-transaction.ts │ │ ├── action-resolver-send-fc-frame-action.ts │ │ ├── action-resolver-set-input.ts │ │ ├── index.tsx │ │ └── render-embed.tsx │ └── tsconfig.json ├── tiptap-extension-channel-mention │ ├── .eslintrc.js │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── mention.ts │ └── tsconfig.json └── tiptap-extension-link │ ├── .eslintrc.js │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ ├── helpers │ │ ├── autolink.ts │ │ ├── clickHandler.ts │ │ ├── pasteHandler.ts │ │ └── textcuts.ts │ ├── index.ts │ └── link.ts │ └── tsconfig.json ├── patches └── @zoralabs+protocol-sdk+0.5.0.patch ├── tsconfig.json ├── turbo.json └── yarn.lock /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } -------------------------------------------------------------------------------- /.changeset/funny-jeans-deny.md: -------------------------------------------------------------------------------- 1 | --- 2 | "metadata-indexer": patch 3 | --- 4 | 5 | fix: ignore NaN values for image dimensions 6 | -------------------------------------------------------------------------------- /.changeset/nervous-ears-tease.md: -------------------------------------------------------------------------------- 1 | --- 2 | "api": patch 3 | --- 4 | 5 | feat: include valid_frame in customOpenGraph object in opengraph api response if the frame passes validation 6 | -------------------------------------------------------------------------------- /.changeset/purple-flowers-pay.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@mod-protocol/core": patch 3 | "api": patch 4 | --- 5 | 6 | fix: include all meta tags in the opengraph response 7 | -------------------------------------------------------------------------------- /.changeset/soft-trees-battle.md: -------------------------------------------------------------------------------- 1 | --- 2 | "metadata-indexer": patch 3 | --- 4 | 5 | fix: reindex urls on each mention, save empty metadata for urls that return bad responses 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // This tells ESLint to load the config from the package `eslint-config-custom` 3 | extends: ["custom"], 4 | settings: { 5 | next: { 6 | rootDir: ["examples/nextjs-*"], 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Change Summary 2 | 3 | 4 | 5 | ## Merge Checklist 6 | 7 | 8 | 9 | - [ ] PR has a changeset 10 | - [ ] PR includes documentation if necessary 11 | - [ ] PR updates the [rich-embed examples](https://github.com/mod-protocol/mod/blob/main/examples/nextjs-shadcn/src/app/embeds.tsx) if necessary 12 | - [ ] includes a parallel PR for [Mod-starter](https://github.com/mod-protocol/mod-starter) and the gateway if necessary 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | 11 | # next.js 12 | .next/ 13 | out/ 14 | build 15 | dist/ 16 | 17 | # misc 18 | .DS_Store 19 | *.pem 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # local env files 27 | .env 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # turbo 34 | .turbo 35 | 36 | # vercel 37 | .vercel 38 | 39 | *.tgz -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers = true 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.disableAutomaticTypeAcquisition": true 3 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023-2024 Discove and others 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This is no longer supported, please consider using Open Frames & frames.js instead 2 | 3 | # Mod Monorepo 4 | 5 | This is the monorepo for the packages of [Mod protocol](https://www.modprotocol.org) 6 | 7 | [![Quickstart](https://img.shields.io/badge/Quickstart-37a779?style=for-the-badge)](https://docs.modprotocol.org/integrate/getting-started) 8 | 9 | [Docs](https://docs.modprotocol.org) 10 | 11 | [Demo](https://example-nextjs.modprotocol.org) 12 | 13 | [Boilerplate starter](https://github.com/mod-protocol/mod-starter) 14 | 15 | ## Contributing 16 | 17 | See [CONTRIBUTING.md](/CONTRIBUTING.md) 18 | -------------------------------------------------------------------------------- /configs/eslint-config-custom/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["turbo", "prettier"], 3 | parser: "@typescript-eslint/parser", 4 | plugins: ["react-hooks"], 5 | rules: { 6 | "no-console": ["warn"], 7 | "react-hooks/rules-of-hooks": "error", 8 | "react-hooks/exhaustive-deps": [ 9 | "warn", 10 | { 11 | additionalHooks: "(useEditor)", 12 | }, 13 | ], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /configs/eslint-config-custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-custom", 3 | "version": "0.0.0", 4 | "main": "index.js", 5 | "private": true, 6 | "dependencies": { 7 | "@typescript-eslint/eslint-plugin": "^6.7.2", 8 | "@typescript-eslint/parser": "^6.7.4", 9 | "eslint-config-prettier": "^8.3.0", 10 | "eslint-config-turbo": "^1.9.3", 11 | "eslint-plugin-react": "7.28.0", 12 | "eslint-plugin-react-hooks": "^4.6.0" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | } 17 | } -------------------------------------------------------------------------------- /configs/tsconfig/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "composite": false, 6 | "declaration": true, 7 | "declarationMap": true, 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "inlineSources": false, 11 | "isolatedModules": true, 12 | "moduleResolution": "node", 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "preserveWatchOutput": true, 16 | "skipLibCheck": true, 17 | "strict": true 18 | }, 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /configs/tsconfig/nextjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Next.js", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "plugins": [{ "name": "next" }], 7 | "allowJs": true, 8 | "declaration": false, 9 | "declarationMap": false, 10 | "incremental": true, 11 | "jsx": "preserve", 12 | "lib": ["dom", "dom.iterable", "esnext"], 13 | "module": "esnext", 14 | "noEmit": true, 15 | "resolveJsonModule": true, 16 | "strict": false, 17 | "target": "es5" 18 | }, 19 | "include": ["src", "next-env.d.ts"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /configs/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsconfig", 3 | "version": "0.0.0", 4 | "private": true, 5 | "publishConfig": { 6 | "access": "public" 7 | } 8 | } -------------------------------------------------------------------------------- /configs/tsconfig/react-library.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "React Library", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "jsx": "react-jsx", 7 | "lib": ["ES2015", "DOM"], 8 | "module": "ESNext", 9 | "target": "es6" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | .next 2 | node_modules 3 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # docs 2 | 3 | ## 0.4.0 4 | 5 | ### Minor Changes 6 | 7 | - 2ff629a: Add Farcaster frames support, including a new action Mods can call, ADDFCFRAMEACTION 8 | 9 | ## 0.3.0 10 | 11 | ### Minor Changes 12 | 13 | - cbfe215: add channel mentions 14 | 15 | ## 0.2.0 16 | 17 | ### Minor Changes 18 | 19 | - 2c8a220: extract global css from videojs component into a separate export, remove tailwind plugins from being bundled in the library directly 20 | 21 | ## 0.1.0 22 | 23 | ### Minor Changes 24 | 25 | - 63b4864: Renames Content rendering to Rich Embeds, Renames Mini-apps to Mods, updates docs 26 | 27 | ### Patch Changes 28 | 29 | - 2b7c514: Adds the combobox, select, textarea mod elements. Adds `Model Definitions` to Manifests. Adds the `ethPersonalSign` action. Adds `json-ld` indexing to metadata. Adds a `loadingLabel` prop to buttons 30 | -------------------------------------------------------------------------------- /docs/Logo.jsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { useTheme } from "nextra-theme-docs"; 3 | import React, { useState, useEffect } from "react"; 4 | 5 | export default function Logo() { 6 | const { resolvedTheme } = useTheme(); 7 | const [isClient, setIsClient] = useState(false) 8 | 9 | useEffect(() => { 10 | setIsClient(true) 11 | }, []) 12 | 13 | // don't render on server as is creating a hydration warning/not refreshing when there's a mismatch between default theme and client theme 14 | if (!isClient) return null; 15 | 16 | return ( 17 | Mod protocol 27 | ); 28 | } -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Local Development 2 | 3 | First, run `yarn install` to install the dependencies. 4 | 5 | Then, run `yarn dev` to start the development server and visit localhost:3000. 6 | 7 | ## License 8 | 9 | This project is licensed under the MIT License. 10 | -------------------------------------------------------------------------------- /docs/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /docs/next.config.js: -------------------------------------------------------------------------------- 1 | const withNextra = require("nextra")({ 2 | theme: "nextra-theme-docs", 3 | themeConfig: "./theme.config.tsx", 4 | }); 5 | 6 | module.exports = withNextra(); 7 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.4.0", 4 | "description": "Mod protocol docs", 5 | "private": true, 6 | "scripts": { 7 | "dev": "next dev -p 3002", 8 | "build": "next build", 9 | "start": "next start -p 3002" 10 | }, 11 | "dependencies": { 12 | "next": "^13.5.6", 13 | "nextra": "^2.13.2", 14 | "nextra-theme-docs": "^2.13.2", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "18.11.10", 20 | "typescript": "^5.2.2" 21 | } 22 | } -------------------------------------------------------------------------------- /docs/pages/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": "Introduction", 3 | "integrate": "Setup", 4 | "ui": "UI Libraries", 5 | "metadata-cache": "Metadata Indexer", 6 | "mod-editor": "Farcaster Editor", 7 | "create-mod": "Create a Mod", 8 | "data-storage": "Data Storage", 9 | "farcaster-frames": "Farcaster Frames", 10 | "permissions": "Permissions", 11 | "security": "Security", 12 | "mods-list": "Available Mods", 13 | "faq": "FAQ", 14 | "example": { 15 | "title": "Live Example ↗", 16 | "type": "page", 17 | "href": "https://example-nextjs.modprotocol.org", 18 | "newWindow": true 19 | }, 20 | "contact": { 21 | "title": "Contact ↗", 22 | "type": "page", 23 | "href": "mailto:david+docs@modprotocol.org", 24 | "newWindow": true 25 | }, 26 | "website": { 27 | "title": "Homepage ↗", 28 | "type": "page", 29 | "href": "https://www.modprotocol.org", 30 | "newWindow": true 31 | }, 32 | "suggest-a-mod": { 33 | "title": "Suggest a Mod ↗", 34 | "type": "page", 35 | "href": "https://tally.so/r/wvDAJl", 36 | "newWindow": true 37 | } 38 | } -------------------------------------------------------------------------------- /docs/pages/create-mod/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "getting-started": "Quickstart", 3 | "reference": "Schema Reference" 4 | } -------------------------------------------------------------------------------- /docs/pages/faq.mdx: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## Can I integrate the Mods using my own editor? 4 | 5 | Yes the mods are separate from the editor. But you will need to provide methods for handling changes to the input or embeds 6 | 7 | ## Can I use this with my own UI library? 8 | 9 | Yes. These libraries are designed to decouple our UI implementation to allow for easy implementation by anyone for other libraries. 10 | 11 | ## Is there a way to use this without using React? 12 | 13 | Not at the moment, but multiple library support will come eventually. Requests on what you'd like to be supported is helpful, please contact david at modprotocol.org. 14 | There may be bounties available to implement a library. 15 | 16 | ## React-native support? 17 | 18 | Coming soon. There's an open draft Pull Request for this. Help wanted! 19 | 20 | ## Why does Mod protocol use configuration files and not React components? 21 | 22 | Mods that are displayed within feeds need to be secure, performant and consistent in design. 23 | This is harder to achieve with React Components, and also restricts Mod protocol to only projects that use React. 24 | 25 | ## Who decides which Mods are shown to the user? 26 | 27 | In the first phase, all first party Mods will be available to end users, with integrating apps having the control over which Mods to use in their apps. 28 | As the number of Mods grows, we will have Mods designed to be installed in channels by their community leads. 29 | We will also have a way for users to opt into Mods they want, by minting the Mod. 30 | 31 | ## What kinds of Mods do you think will be made and most popular? 32 | 33 | - shopping & media 34 | - games 35 | - community/DAO tools 36 | - onchain transacting 37 | - compliance & safety 38 | - platform integrations 39 | - daily services 40 | - curation & ad services 41 | - publisher tools -------------------------------------------------------------------------------- /docs/pages/farcaster-frames.mdx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | ## By integrating Mod, you also get Farcaster Frames support automatically 4 | 5 | There is a Mod, [Farcaster Frames Render](https://github.com/mod-protocol/mod/blob/7f4d2c59c60c916ab1e683f112d7c319cbb5d3fc/mods/farcaster-frames-render) that integrates Farcaster Frames into your app. In order for the Farcaster Frames to work, you need to provide a `onSendFcFrameAction` property to the `RichEmbed` `resolvers` object. 6 | An example of this function implementation using neynar can be found [here](https://github.com/mod-protocol/mod/blob/a96dc6b4482d6ede3fee7baada9d5abe8bb430a1/examples/nextjs-shadcn/src/app/embeds.tsx#L51) 7 | 8 | # 9 | 10 | frames example -------------------------------------------------------------------------------- /docs/pages/index.mdx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | # Welcome 4 | 5 | ### Mod is a simple toolkit for apps that run in other apps, without compromising security, performance or UX. 6 | 7 | # 8 | 9 | create mod example 10 | 11 | **Mod helps you replace external links with actions that users can take within your app.** 12 | That means: 13 | - Allow users to transact, mint, swap, create, play from within your app, instead of users leaving your site. 14 | - Mod helps you make money with referral fees from protocols like Zora that your users interact with. 15 | - Mod helps you drive DAU numbers by delighting your users with new experiences, and keeping them in your app. 16 | - Mod is an open source standard, set of SDKs, and marketplace of Mods made by developers like you. 17 | 18 | ## Get started 19 | 20 | [![Quickstart](https://img.shields.io/badge/Quickstart-37a779?style=for-the-badge)](/integrate/getting-started) -------------------------------------------------------------------------------- /docs/pages/integrate/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "getting-started": "Quickstart", 3 | "creation": "Creation Mods", 4 | "rich-embeds": "Rich-embed Mods" 5 | } -------------------------------------------------------------------------------- /docs/pages/integrate/getting-started.mdx: -------------------------------------------------------------------------------- 1 | import { Steps } from 'nextra/components' 2 | 3 | # Quickstart 4 | 5 | 6 | ### Step 1 7 | 8 | [Add Creation Mods to your app](creation.mdx) 9 | 10 | ### Step 2 11 | 12 | [Add Rich-embed Mods to your app](rich-embeds.mdx) 13 | 14 | ### Step 3 15 | 16 | [Make a Mod](/create-mod/getting-started) 17 | 18 | 19 | 20 | ## Alternatively, fork the boilerplate starter 21 | 22 | Fork the [Mod-starter repo](https://github.com/mod-protocol/mod-starter) to get started -------------------------------------------------------------------------------- /docs/pages/mods-list.mdx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | # Available Mods 4 | 5 | 6 | Some Mods are in development and some others are ones that we're excited for. 7 | Anyone can [create a Mod](/create-mod/getting-started.mdx), but we're creating some of the first ones while we develop the initial Mod SDKs. 8 | 9 | all mods 10 | 11 | Last updated: Dec 8, 2023 -------------------------------------------------------------------------------- /docs/pages/permissions.mdx: -------------------------------------------------------------------------------- 1 | # Permissions 2 | 3 | Mods can specify `permissions` in their `Manifest`. 4 | Permissions enable Mods to request access to specific data or SDKs, and for integrators of Mod to filter out Mods that require permissions 5 | that they either don't or don't want to support. 6 | 7 | ## Currently supported permissions 8 | 9 | `user.wallet.address` Requests the currently connected user's wallet address as a hex string 10 | 11 | `web3.eth.personal.sign` Indicates the Mod needs to be able to callback the App with the parameters of an Ethereum signature. 12 | -------------------------------------------------------------------------------- /docs/pages/security.mdx: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | To start off with, all the code is MIT licensed & open source (Mods, SDKs, ...), so you're free to audit it yourself. 4 | 5 | In the short term, we're building and selecting every Mod that goes into the public registry. 6 | All the Mods are open source and versioned through npm, and you're free to review the code yourself for any concerns, or before updating packages. 7 | We don't load new Mods or Mod updates over the wire. 8 | 9 | In the long term, when there are many Mods, particularly community made ones, we will have a different system for labelling 10 | Mods that gives high trust and security guarantees, more akin to systems used by App stores, but in a decentralized manner. 11 | 12 | There are also explorations of running Mods inside secure javascript execution environments, and Mod will in future support any such web standards 13 | that don't make sacrifices in performance or user experience. -------------------------------------------------------------------------------- /docs/pages/ui/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "getting-started": "Quickstart", 3 | "react-shadcn": "Tailwind + Radix" 4 | } -------------------------------------------------------------------------------- /docs/pages/ui/getting-started.mdx: -------------------------------------------------------------------------------- 1 | # UI libraries 2 | 3 | Mod decouples the UI implementation from the core SDK. 4 | This allows apps to use a UI library that fits their current codebase, or even use their existing codebase components. 5 | 6 | Mod currently supports one official UI library: [react-ui-shadcn](/react-shadcn.mdx), which uses a a combination of Tailwind CSS and Radix UI. -------------------------------------------------------------------------------- /docs/public/all-mods.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mod-protocol/mod/4361b912aba06d244e125ed14690eaa1197a9270/docs/public/all-mods.png -------------------------------------------------------------------------------- /docs/public/create-mods.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mod-protocol/mod/4361b912aba06d244e125ed14690eaa1197a9270/docs/public/create-mods.png -------------------------------------------------------------------------------- /docs/public/create-poll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mod-protocol/mod/4361b912aba06d244e125ed14690eaa1197a9270/docs/public/create-poll.png -------------------------------------------------------------------------------- /docs/public/creation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mod-protocol/mod/4361b912aba06d244e125ed14690eaa1197a9270/docs/public/creation.png -------------------------------------------------------------------------------- /docs/public/farcaster-frames.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mod-protocol/mod/4361b912aba06d244e125ed14690eaa1197a9270/docs/public/farcaster-frames.gif -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mod-protocol/mod/4361b912aba06d244e125ed14690eaa1197a9270/docs/public/favicon.ico -------------------------------------------------------------------------------- /docs/public/mod-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mod-protocol/mod/4361b912aba06d244e125ed14690eaa1197a9270/docs/public/mod-editor.png -------------------------------------------------------------------------------- /docs/public/mods.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mod-protocol/mod/4361b912aba06d244e125ed14690eaa1197a9270/docs/public/mods.png -------------------------------------------------------------------------------- /docs/public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mod-protocol/mod/4361b912aba06d244e125ed14690eaa1197a9270/docs/public/og.png -------------------------------------------------------------------------------- /docs/public/render-poll-mod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mod-protocol/mod/4361b912aba06d244e125ed14690eaa1197a9270/docs/public/render-poll-mod.png -------------------------------------------------------------------------------- /docs/public/rich-embed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mod-protocol/mod/4361b912aba06d244e125ed14690eaa1197a9270/docs/public/rich-embed.png -------------------------------------------------------------------------------- /docs/theme.config.tsx: -------------------------------------------------------------------------------- 1 | import { DocsThemeConfig } from "nextra-theme-docs"; 2 | import Logo from "./Logo"; 3 | 4 | const config: DocsThemeConfig = { 5 | logo: Logo, 6 | project: { 7 | link: "https://github.com/mod-protocol/mod", 8 | }, 9 | docsRepositoryBase: "https://github.com/mod-protocol/mod/docs", 10 | footer: { 11 | text: "Mod Protocol", 12 | }, 13 | head: null, 14 | primaryHue: { dark: 100, light: 248 }, 15 | primarySaturation: { dark: 55, light: 67 }, 16 | useNextSeoProps() { 17 | return { 18 | titleTemplate: "%s - Mod Protocol Docs", 19 | defaultTitle: "Mod Protocol Docs", 20 | description: 21 | "A protocol and set of open source libraries for decentralized social Mods", 22 | openGraph: { 23 | titleTemplate: "%s - Mod Protocol Docs", 24 | description: 25 | "A protocol and set of open source libraries for decentralized social Mods", 26 | images: [ 27 | { 28 | url: "https://docs.modprotocol.org/og.png", 29 | width: 1200, 30 | height: 630, 31 | alt: "Mod Protocol Docs", 32 | type: "image/png", 33 | }, 34 | ], 35 | }, 36 | twitter: { 37 | handle: "@modprotocol", 38 | cardType: "summary_large_image", 39 | }, 40 | }; 41 | }, 42 | }; 43 | 44 | export default config; 45 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "incremental": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve" 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/api/.env.example: -------------------------------------------------------------------------------- 1 | # Needs to be an IPFS api key 2 | INFURA_API_KEY="REQUIRED" 3 | INFURA_API_SECRET="REQUIRED" 4 | GIPHY_API_KEY="REQUIRED" 5 | MICROLINK_API_KEY="REQUIRED" 6 | OPENSEA_API_KEY="REQUIRED" 7 | CHATGPT_API_SECRET="REQUIRED" 8 | NEYNAR_API_SECRET="REQUIRED" 9 | LIVEPEER_API_SECRET="REQUIRED" 10 | DATABASE_URL="REQUIRED" 11 | # Must be funded with MATIC on Mumbai for Irys https://mumbaifaucet.com/ 12 | PRIVATE_KEY="REQUIRED" 13 | CHATGPT_ORGANIZATION_ID="REQUIRED" 14 | NFT_STORAGE_API_KEY="REQUIRED" 15 | ZORA_ADMIN_PRIVATE_KEY="REQUIRED" 16 | -------------------------------------------------------------------------------- /examples/api/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["custom", "next"], 4 | }; 5 | -------------------------------------------------------------------------------- /examples/api/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /examples/api/README.md: -------------------------------------------------------------------------------- 1 | # Readme 2 | 3 | This is a standard next.js serverless api that hosts backend services for Mods. 4 | You can either use a hosted version of these, or self host using this repo, and your own secrets. 5 | 6 | # Setup to develop locally 7 | 8 | Copy `.env.example` to a new file called `.env` in this directory, and fill it with your secrets from the respective services. You can get these secrets by registering for the respective services. 9 | 10 | ```bash 11 | yarn dev 12 | ``` 13 | 14 | Open [http://localhost:3001](http://localhost:3001) with your browser to see the result. 15 | -------------------------------------------------------------------------------- /examples/api/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /examples/api/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | transpilePackages: ["@mod-protocol/react"], 4 | }; 5 | -------------------------------------------------------------------------------- /examples/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "0.2.3", 4 | "private": true, 5 | "scripts": { 6 | "db-types": "kysely-codegen --out-file src/app/api/cast-embeds-metadata/types/db.d.ts --dialect postgres", 7 | "dev": "next dev -p 3001", 8 | "build": "next build", 9 | "start": "next start -p 3001", 10 | "lint": "next lint", 11 | "postinstall": "cd ../.. && npx patch-package --reverse && npx patch-package --error-on-fail" 12 | }, 13 | "dependencies": { 14 | "@irys/sdk": "^0.0.4", 15 | "@lit-protocol/lit-node-client": "^3.0.24", 16 | "@lit-protocol/types": "^2.2.61", 17 | "@mod-protocol/core": "^0.2.1", 18 | "@reservoir0x/reservoir-sdk": "^1.8.4", 19 | "@vercel/postgres-kysely": "^0.6.0", 20 | "@zoralabs/protocol-sdk": "^0.5.0", 21 | "chatgpt": "^5.2.5", 22 | "cheerio": "^1.0.0-rc.12", 23 | "frames.js": "^0.5.0", 24 | "kysely": "^0.26.3", 25 | "next": "^13.5.6", 26 | "nft.storage": "^7.1.1", 27 | "open-graph-scraper": "^6.3.2", 28 | "pg": "^8.11.3", 29 | "react": "^18.2.0", 30 | "react-dom": "^18.2.0", 31 | "siwe": "^1.1.6", 32 | "uint8arrays": "^3.0.0", 33 | "viem": "1.20.1" 34 | }, 35 | "devDependencies": { 36 | "@types/node": "^17.0.12", 37 | "@types/pg": "^8.10.7", 38 | "@types/react": "^18.0.22", 39 | "@types/react-dom": "^18.0.7", 40 | "autoprefixer": "^10.4.14", 41 | "eslint-config-custom": "*", 42 | "eslint-config-next": "^13.5.3", 43 | "kysely-codegen": "^0.11.0", 44 | "postcss": "^8.4.27", 45 | "tailwindcss": "^3.3.3", 46 | "tsconfig": "*", 47 | "typescript": "^5.2.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/api/src/app/api/cast-embeds-metadata/lib/chain-index.ts: -------------------------------------------------------------------------------- 1 | import * as chains from "viem/chains"; 2 | 3 | export const chainById = Object.values(chains).reduce( 4 | (acc: { [key: number]: chains.Chain }, cur) => { 5 | if (cur.id) acc[cur.id] = cur; 6 | return acc; 7 | }, 8 | {} 9 | ); 10 | chainById[1] = { ...chains.mainnet, network: "ethereum" }; // Convenience: rename 'homestead' to 'ethereum' 11 | 12 | export const chainByName = Object.values(chains).reduce( 13 | (acc: { [key: string]: chains.Chain }, cur) => { 14 | if (cur.network) acc[cur.network] = cur; 15 | return acc; 16 | }, 17 | { ethereum: { ...chains.mainnet, network: "ethereum" } } // Convenience for ethereum, which is 'homestead' otherwise 18 | ); 19 | -------------------------------------------------------------------------------- /examples/api/src/app/api/cast-embeds-metadata/lib/db.ts: -------------------------------------------------------------------------------- 1 | import { Kysely, PostgresDialect } from "kysely"; 2 | import { DB } from "../types/db"; 3 | import { createKysely } from "@vercel/postgres-kysely"; 4 | import { Pool } from "pg"; 5 | 6 | export const db = 7 | process.env.NODE_ENV !== "production" 8 | ? new Kysely({ 9 | dialect: new PostgresDialect({ 10 | pool: new Pool({ 11 | connectionString: process.env.DATABASE_URL, 12 | }), 13 | }), 14 | }) 15 | : createKysely({ 16 | connectionString: process.env.DATABASE_URL, 17 | }); 18 | -------------------------------------------------------------------------------- /examples/api/src/app/api/chatgpt-shorten/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { ChatGPTAPI } from "chatgpt"; 3 | 4 | export async function POST(request: NextRequest) { 5 | const body = await request.json(); 6 | 7 | const api = new ChatGPTAPI({ 8 | apiKey: process.env.CHATGPT_API_SECRET, 9 | // completionParams: { 10 | // model: "gpt-4", 11 | // }, 12 | }); 13 | 14 | let prompt = ""; 15 | if (body.text.length > 280) { 16 | // send a message and wait for the response 17 | prompt = `Please shorten the following text to below 280 characters:`; 18 | } else { 19 | prompt = `Please shorten the following text:`; 20 | } 21 | 22 | const response = await api.sendMessage( 23 | ` 24 | ${prompt} 25 | 26 | ${body.text} 27 | ` 28 | ); 29 | 30 | return NextResponse.json({ response }); 31 | } 32 | 33 | // needed for preflight requests to succeed 34 | export const OPTIONS = async (request: NextRequest) => { 35 | // Return Response 36 | return NextResponse.json({}); 37 | }; 38 | -------------------------------------------------------------------------------- /examples/api/src/app/api/chatgpt/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { ChatGPTAPI } from "chatgpt"; 3 | 4 | export async function POST(request: NextRequest) { 5 | const body = await request.json(); 6 | 7 | const api = new ChatGPTAPI({ 8 | apiKey: process.env.CHATGPT_API_SECRET, 9 | // completionParams: { 10 | // model: "gpt-4", 11 | // }, 12 | }); 13 | 14 | const response = await api.sendMessage( 15 | ` 16 | ${body.prompt} 17 | 18 | ${body.text} 19 | ` 20 | ); 21 | 22 | return NextResponse.json({ response }); 23 | } 24 | 25 | // needed for preflight requests to succeed 26 | export const OPTIONS = async (request: NextRequest) => { 27 | // Return Response 28 | return NextResponse.json({}); 29 | }; 30 | -------------------------------------------------------------------------------- /examples/api/src/app/api/farcaster/channels/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | 3 | export async function GET(request: NextRequest) { 4 | const { searchParams } = new URL(request.url); 5 | const q = searchParams.get("q"); 6 | 7 | const channelsRes = await fetch( 8 | "https://api.neynar.com/v2/farcaster/channel/list", 9 | { 10 | headers: { 11 | api_key: process.env.NEYNAR_API_SECRET, 12 | }, 13 | } 14 | ); 15 | const result = await channelsRes.json(); 16 | 17 | const channels = result.channels.map((channel) => ({ 18 | name: channel.name, 19 | parent_url: channel.url, 20 | image: channel.image_url, 21 | channel_id: channel.id, 22 | })); 23 | 24 | const channelsWithHome = [ 25 | { 26 | name: "Home", 27 | parent_url: null, 28 | image: "https://warpcast.com/~/channel-images/home.png", 29 | channel_id: "home", 30 | }, 31 | ...channels.sort((a, b) => (a.name < b.name ? -1 : 1)), 32 | ]; 33 | 34 | return NextResponse.json({ 35 | channels: !!q 36 | ? channelsWithHome.filter((channel) => { 37 | return channel.name.toLowerCase().includes(q.toLowerCase()); 38 | }) 39 | : channelsWithHome, 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /examples/api/src/app/api/farcaster/user-by-username/route.ts: -------------------------------------------------------------------------------- 1 | import { FarcasterMention } from "@mod-protocol/farcaster"; 2 | import { NextResponse, NextRequest } from "next/server"; 3 | 4 | export async function GET(request: NextRequest): Promise< 5 | | NextResponse<{ 6 | message: string; 7 | }> 8 | | NextResponse<{ 9 | data: FarcasterMention; 10 | }> 11 | > { 12 | const username = request.nextUrl.searchParams.get("username"); 13 | 14 | const req = await fetch( 15 | `https://api.neynar.com/v1/farcaster/user-by-username?api_key=${ 16 | process.env.NEYNAR_API_SECRET 17 | }&username=${encodeURIComponent(username)}` 18 | ); 19 | 20 | if (req.status >= 400) { 21 | return NextResponse.json( 22 | { message: "Something went wrong" }, 23 | { status: req.status } 24 | ); 25 | } 26 | 27 | const result = await req.json(); 28 | const user = result.result.user; 29 | 30 | return NextResponse.json({ 31 | data: { 32 | fid: user.fid, 33 | display_name: user.display_name, 34 | username: user.username, 35 | avatar_url: user.pfp_url, 36 | }, 37 | }); 38 | } 39 | 40 | // Alternative query if implementing yourself 41 | // ` 42 | // select 43 | // fid, display_name, username, avatar_url, LEAST(extensions.levenshtein(display_name, search_query::text, 1, 1, 3), extensions.levenshtein(username, search_query::text, 1, 1, 3)) as levenshtein_distance 44 | // from 45 | // profiles 46 | // where 47 | // display_name ilike search_query || '%' or username ilike search_query || '%' 48 | // order by 49 | // LEAST(extensions.levenshtein(display_name, search_query::text, 1, 1, 3) + 1, extensions.levenshtein(username, search_query::text, 1, 1, 3)) asc 50 | // limit 10 51 | // ` 52 | -------------------------------------------------------------------------------- /examples/api/src/app/api/giphy-picker/route.ts: -------------------------------------------------------------------------------- 1 | import toNumber from "lodash.tonumber"; 2 | import toString from "lodash.tostring"; 3 | import { NextRequest, NextResponse } from "next/server"; 4 | 5 | const { GIPHY_API_KEY } = process.env; 6 | 7 | export async function GET(request: NextRequest) { 8 | const q = request.nextUrl.searchParams.get("q"); 9 | const type = request.nextUrl.searchParams.get("type") || "gifs"; 10 | const limit = Math.max( 11 | Math.min(toNumber(request.nextUrl.searchParams.get("limit")) || 24, 100), 12 | 24 13 | ); 14 | const offset = request.nextUrl.searchParams.get("offset") || 0; 15 | 16 | if (!["gifs", "stickers"].includes(type)) { 17 | return NextResponse.json( 18 | { 19 | message: `Unrecognized type '${type}'; valid options are: gifs, stickers`, 20 | }, 21 | { status: 400 } 22 | ); 23 | } 24 | 25 | const requestUrl = new URL("https://api.giphy.com/"); 26 | requestUrl.searchParams.set("api_key", GIPHY_API_KEY); 27 | requestUrl.searchParams.set("limit", toString(limit)); 28 | requestUrl.searchParams.set("offset", toString(offset)); 29 | requestUrl.searchParams.set("rating", "g"); 30 | requestUrl.searchParams.set("bundle", "messaging_non_clips"); 31 | 32 | if (!q) { 33 | requestUrl.pathname = `/v1/${type}/trending`; 34 | } else { 35 | requestUrl.pathname = `/v1/${type}/search`; 36 | requestUrl.searchParams.set("q", q); 37 | requestUrl.searchParams.set("lang", "en"); 38 | } 39 | 40 | const res = await fetch(requestUrl.href).then((res) => res.json()); 41 | 42 | return NextResponse.json({ 43 | list: res.data.map((item) => item.images.original.url), 44 | hasMore: 45 | res.pagination.total_count > res.pagination.count + res.pagination.offset, 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /examples/api/src/app/api/imgur-upload/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | 3 | const { IMGUR_CLIENT_ID } = process.env; 4 | 5 | const uploadToImgur = async (file: File): Promise => { 6 | const formData = new FormData(); 7 | formData.append("image", file); 8 | formData.append("type", "file"); 9 | const response = await fetch("https://api.imgur.com/3/upload", { 10 | method: "POST", 11 | headers: { 12 | Authorization: `Client-ID ${IMGUR_CLIENT_ID}`, // replace with your Client ID 13 | }, 14 | body: formData, 15 | }); 16 | 17 | if (!response.ok) { 18 | return null; 19 | } 20 | 21 | const { data } = await response.json(); 22 | 23 | if (!data) return null; 24 | 25 | return data.link; 26 | }; 27 | 28 | export async function POST(request: NextRequest) { 29 | const form = await request.formData(); 30 | const file: File = form.get("file") as File; 31 | 32 | try { 33 | const url = await uploadToImgur(file); 34 | 35 | if (url) { 36 | return NextResponse.json({ url }); 37 | } else { 38 | return NextResponse.json( 39 | { error: "Failed to upload to Imgur" }, 40 | { status: 500 } 41 | ); 42 | } 43 | } catch (error) { 44 | return NextResponse.json({ error: error.message }, { status: 500 }); 45 | } 46 | } 47 | 48 | // needed for preflight requests to succeed 49 | export const OPTIONS = async (request: NextRequest) => { 50 | return NextResponse.json({}); 51 | }; 52 | -------------------------------------------------------------------------------- /examples/api/src/app/api/infura-ipfs-upload/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | 3 | const { INFURA_API_KEY, INFURA_API_SECRET } = process.env; 4 | 5 | export async function POST(request: NextRequest) { 6 | const form = await request.formData(); 7 | 8 | const uploadRes: Response | null = await fetch( 9 | "https://ipfs.infura.io:5001/api/v0/add", 10 | { 11 | method: "POST", 12 | body: form, 13 | headers: { 14 | Authorization: 15 | "Basic " + 16 | Buffer.from(INFURA_API_KEY + ":" + INFURA_API_SECRET).toString( 17 | "base64" 18 | ), 19 | }, 20 | } 21 | ).catch(() => null); 22 | 23 | if (!uploadRes?.ok) { 24 | return NextResponse.json( 25 | { message: "Something was wrong" }, 26 | { status: 500 } 27 | ); 28 | } 29 | 30 | const json = await uploadRes.json(); 31 | 32 | return NextResponse.json({ 33 | url: `https://discove.infura-ipfs.io/ipfs/${json.Hash}`, 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /examples/api/src/app/api/nft-chain-logo/lib/chains/chain-icon.ts: -------------------------------------------------------------------------------- 1 | import { chainById } from "./chain-index"; 2 | 3 | export async function resolveChainIcon(chainId: number): Promise { 4 | const chain = await fetch( 5 | `https://raw.githubusercontent.com/ethereum-lists/chains/master/_data/chains/eip155-${chainId}.json` 6 | ); 7 | if (!chain.ok) { 8 | return null; 9 | } 10 | const chainJson = await chain.json(); 11 | 12 | const iconRes = await fetch( 13 | `https://raw.githubusercontent.com/ethereum-lists/chains/master/_data/icons/${chainJson.icon}.json` 14 | ); 15 | if (!iconRes.ok) { 16 | return null; 17 | } 18 | const icons = await iconRes.json(); 19 | let { url } = icons[0]; 20 | 21 | if (url) { 22 | if (url.startsWith("ipfs://")) { 23 | url = url.replace( 24 | "ipfs://", 25 | "https://raw.githubusercontent.com/ethereum-lists/chains/master/_data/iconsDownload/" 26 | ); 27 | } 28 | } else { 29 | // Fall back to alternative asset db, might want to check if it actually exists before returning 30 | const chain = chainById[chainId]; 31 | url = `https://raw.githubusercontent.com/DefiLlama/icons/master/assets/agg_icons/${chain.network}.jpg`; 32 | } 33 | 34 | return url; 35 | } 36 | -------------------------------------------------------------------------------- /examples/api/src/app/api/nft-chain-logo/lib/chains/chain-index.ts: -------------------------------------------------------------------------------- 1 | import * as chains from "viem/chains"; 2 | 3 | export const chainById = Object.values(chains).reduce( 4 | (acc: { [key: number]: chains.Chain }, cur) => { 5 | if (cur.id) acc[cur.id] = cur; 6 | return acc; 7 | }, 8 | {} 9 | ); 10 | 11 | export const chainByName = Object.values(chains).reduce( 12 | (acc: { [key: string]: chains.Chain }, cur) => { 13 | if (cur.network) acc[cur.network] = cur; 14 | return acc; 15 | }, 16 | { ethereum: chains.mainnet } // Convenience for ethereum, which is 'homestead' otherwise 17 | ); 18 | -------------------------------------------------------------------------------- /examples/api/src/app/api/nft-chain-logo/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest } from "next/server"; 2 | import { resolveChainIcon } from "./lib/chains/chain-icon"; 3 | import { chainById, chainByName } from "./lib/chains/chain-index"; 4 | 5 | export async function GET(request: NextRequest) { 6 | let chainNameOrId = 7 | request.nextUrl.searchParams.get("chain") || 8 | parseInt(request.nextUrl.searchParams.get("chain-id")); 9 | 10 | const chain = chainById[chainNameOrId] || chainByName[chainNameOrId]; 11 | 12 | if (!chain) { 13 | return new Response(null, { 14 | status: 404, 15 | }); 16 | } 17 | 18 | let chainIconUrl = await resolveChainIcon(chain.id); 19 | 20 | const res = await fetch(chainIconUrl); 21 | 22 | return new Response(res.body, { 23 | status: res.status, 24 | headers: res.headers, 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /examples/api/src/app/api/open-graph/lib/chains/chain-index.ts: -------------------------------------------------------------------------------- 1 | import * as chains from "viem/chains"; 2 | 3 | export const chainById = Object.values(chains).reduce( 4 | (acc: { [key: number]: chains.Chain }, cur) => { 5 | if (cur.id) acc[cur.id] = cur; 6 | return acc; 7 | }, 8 | {} 9 | ); 10 | chainById[1] = { ...chains.mainnet, network: "ethereum" }; // Convenience: rename 'homestead' to 'ethereum' 11 | 12 | export const chainByName = Object.values(chains).reduce( 13 | (acc: { [key: string]: chains.Chain }, cur) => { 14 | if (cur.network) acc[cur.network] = cur; 15 | return acc; 16 | }, 17 | { ethereum: { ...chains.mainnet, network: "ethereum" } } // Convenience for ethereum, which is 'homestead' otherwise 18 | ); 19 | -------------------------------------------------------------------------------- /examples/api/src/app/api/open-graph/lib/url-handlers/arweave.ts: -------------------------------------------------------------------------------- 1 | import { UrlMetadata } from "@mod-protocol/core"; 2 | import { UrlHandler } from "../../types/url-handler"; 3 | 4 | async function handleArweave(url: string): Promise { 5 | // `https://gateway.irys.xyz/${irysTransactionId}` 6 | const transactionId = url.split(":")[2]; 7 | 8 | const reformattedUrl = `https://gateway.irys.xyz/${transactionId}`; 9 | 10 | const response = await fetch(reformattedUrl, { 11 | method: "HEAD", 12 | }); 13 | 14 | // Get content-type 15 | const mimeType = response.headers.get("content-type"); 16 | 17 | if (mimeType === "application/json") { 18 | try { 19 | const arweaveData = await fetch(reformattedUrl); 20 | 21 | const body = await arweaveData.json(); 22 | 23 | // Check for schema 24 | if (body["@type"] === "WebPage") 25 | return { 26 | image: { 27 | url: body.image, 28 | }, 29 | "json-ld": { WebPage: [body] }, 30 | description: body.description, 31 | alt: body.name, 32 | title: body.name, 33 | logo: { 34 | url: body.image, 35 | }, 36 | mimeType: "application/json", 37 | }; 38 | } catch (err) { 39 | console.error(err); 40 | } 41 | } 42 | // TODO: handle html 43 | 44 | return null; 45 | } 46 | 47 | const urlHandler: UrlHandler = { 48 | name: "Arweave", 49 | matchers: ["arweave:7wIU:*"], 50 | handler: handleArweave, 51 | }; 52 | 53 | export default urlHandler; 54 | -------------------------------------------------------------------------------- /examples/api/src/app/api/open-graph/lib/url-handlers/image-file.ts: -------------------------------------------------------------------------------- 1 | import { UrlMetadata } from "@mod-protocol/core"; 2 | import { UrlHandler } from "../../types/url-handler"; 3 | import mime from "mime"; 4 | 5 | // Microlink API fails to fetch metadata for direct urls 6 | // If we have the url, we can construct the metadata ourselves 7 | async function handleImageFileUrl(url: string): Promise { 8 | let mimeType: string | null = mime.getType(url); 9 | 10 | if (!mimeType) { 11 | // Get HEAD 12 | const response = await fetch(url, { 13 | method: "HEAD", 14 | }); 15 | 16 | // Get content-type 17 | mimeType = response.headers.get("content-type"); 18 | } 19 | 20 | if (!mimeType.startsWith("image/")) { 21 | return null; 22 | } 23 | 24 | const urlMetadata: UrlMetadata = { 25 | image: { 26 | url, 27 | }, 28 | mimeType, 29 | }; 30 | 31 | return urlMetadata; 32 | } 33 | 34 | const handler: UrlHandler = { 35 | name: "Image File", 36 | matchers: [/\.((jpg|jpeg|png|gif|bmp|webp|tiff|svg))(?=\?|$)/i], 37 | handler: handleImageFileUrl, 38 | }; 39 | 40 | export default handler; 41 | -------------------------------------------------------------------------------- /examples/api/src/app/api/open-graph/lib/url-handlers/index.ts: -------------------------------------------------------------------------------- 1 | import { UrlHandler } from "../../types/url-handler"; 2 | import caip19 from "./caip-19"; 3 | import opensea from "./opensea"; 4 | import zora from "./zora"; 5 | import zoraPremint from "./zora-premint"; 6 | import imageFileUrl from "./image-file"; 7 | import metascraper from "./metascraper"; 8 | import localFetch from "./local-fetch"; 9 | 10 | const handlers: UrlHandler[] = [ 11 | opensea, 12 | zoraPremint, 13 | zora, 14 | caip19, 15 | imageFileUrl, 16 | localFetch, 17 | metascraper, 18 | ]; 19 | 20 | export default handlers; 21 | -------------------------------------------------------------------------------- /examples/api/src/app/api/open-graph/lib/url-handlers/zora-premint.ts: -------------------------------------------------------------------------------- 1 | import { UrlMetadata } from "@mod-protocol/core"; 2 | import { UrlHandler } from "../../types/url-handler"; 3 | import fallback from "./metascraper"; 4 | 5 | async function handleZoraPremintUrl(url: string): Promise { 6 | const metadata = fallback.handler(url, { nftMetadata: false }); 7 | return metadata; 8 | } 9 | 10 | const handler: UrlHandler = { 11 | name: "Zora Premint", 12 | matchers: [/https:\/\/zora\.co\/collect\/([^\/]+):([^\/]+)\/(premint-\d+)/], 13 | handler: handleZoraPremintUrl, 14 | }; 15 | 16 | export default handler; 17 | -------------------------------------------------------------------------------- /examples/api/src/app/api/open-graph/lib/url-handlers/zora.ts: -------------------------------------------------------------------------------- 1 | import { UrlMetadata } from "@mod-protocol/core"; 2 | import { UrlHandler } from "../../types/url-handler"; 3 | import { fetchNFTMetadata, toUrlMetadata } from "../util"; 4 | 5 | async function handleZoraCollectUrl(url: string): Promise { 6 | const parsedUrl = new URL(url); 7 | const pathname = parsedUrl.pathname; 8 | const pathParts = pathname.split("/"); 9 | const tokenId = pathParts[3]; 10 | const chainAndContractAddress = pathParts[2].toLowerCase(); 11 | let [chain, contractAddress] = chainAndContractAddress.split(":"); 12 | 13 | const chainMapping = { 14 | eth: "ethereum", 15 | oeth: "optimism", 16 | base: "base", 17 | zora: "zora", 18 | }; 19 | 20 | if (chainMapping[chain]) { 21 | chain = chainMapping[chain]; 22 | } 23 | 24 | const nftMetadata = await fetchNFTMetadata({ 25 | contractAddress, 26 | tokenId, 27 | chain, 28 | mintUrl: url, 29 | }); 30 | 31 | const urlMetadata: UrlMetadata = toUrlMetadata(nftMetadata); 32 | 33 | return urlMetadata; 34 | } 35 | 36 | const handler: UrlHandler = { 37 | name: "Zora Collect", 38 | matchers: ["https://zora.co/collect/.*"], 39 | handler: handleZoraCollectUrl, 40 | }; 41 | 42 | export default handler; 43 | -------------------------------------------------------------------------------- /examples/api/src/app/api/open-graph/route.ts: -------------------------------------------------------------------------------- 1 | export const dynamic = "force-dynamic"; 2 | export const maxDuration = 20; 3 | 4 | import { NextResponse, NextRequest } from "next/server"; 5 | import { UrlMetadata } from "@mod-protocol/core"; 6 | import urlHandlers from "./lib/url-handlers"; 7 | 8 | export async function GET(request: NextRequest) { 9 | const url = decodeURIComponent(request.nextUrl.searchParams.get("url")); 10 | try { 11 | let urlMetadata: UrlMetadata | null = null; 12 | let handlerName: string | null = null; 13 | for (const { matchers, handler, name } of urlHandlers) { 14 | if (matchers.some((matcher) => url.match(matcher))) { 15 | urlMetadata = await handler(url); 16 | handlerName = name; 17 | // Stop after successful match 18 | if (urlMetadata) break; 19 | } 20 | } 21 | 22 | if (!urlMetadata) { 23 | throw new Error(`No handler returned a valid response for ${url}`); 24 | } 25 | 26 | return NextResponse.json({ ...urlMetadata, handlerName }); 27 | } catch (err) { 28 | console.error(`Error fetching metadata for ${url}`); 29 | console.error(err); 30 | return NextResponse.json( 31 | { message: err.message }, 32 | { status: err.status ?? 400 } 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/api/src/app/api/open-graph/types/url-handler.ts: -------------------------------------------------------------------------------- 1 | import { UrlMetadata } from "@mod-protocol/core"; 2 | 3 | export type UrlHandler = { 4 | name: string; 5 | matchers: (string | RegExp)[]; 6 | handler: (url: string, options?: any) => Promise; 7 | }; 8 | -------------------------------------------------------------------------------- /examples/api/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | 3 | export async function middleware(request: NextRequest) { 4 | const response = NextResponse.next(); 5 | 6 | if (request.nextUrl.pathname.startsWith("/api")) { 7 | response.headers.append("Access-Control-Allow-Credentials", "true"); 8 | response.headers.append("Access-Control-Allow-Origin", "*"); 9 | response.headers.append( 10 | "Access-Control-Allow-Methods", 11 | "DELETE, POST, PUT, GET, OPTIONS, HEAD" 12 | ); 13 | response.headers.append( 14 | "Access-Control-Allow-Headers", 15 | "X-CSRF-Token, X-Requested-With, Authorization, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" 16 | ); 17 | } 18 | return response; 19 | } 20 | -------------------------------------------------------------------------------- /examples/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/nextjs.json", 3 | "compilerOptions": { 4 | "plugins": [ 5 | { 6 | "name": "next" 7 | } 8 | ], 9 | }, 10 | "include": [ 11 | "next-env.d.ts", 12 | "**/*.ts", 13 | "**/*.tsx", 14 | ".next/types/**/*.ts" 15 | ], 16 | "exclude": [ 17 | "node_modules" 18 | ] 19 | } -------------------------------------------------------------------------------- /examples/metadata-indexer/.env.sample: -------------------------------------------------------------------------------- 1 | OPENGRAPH_API="http://localhost:3001/api/open-graph" 2 | POSTGRES_URL= 3 | HUB_SSL=true 4 | HUB_HOST= 5 | MAX_CONCURRENCY_HUB= 6 | MAX_CONCURRENCY_FETCH= 7 | INDEX_DISABLE=false 8 | BACKFILL_DISABLE=false 9 | PORT=3003 -------------------------------------------------------------------------------- /examples/metadata-indexer/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // This tells ESLint to load the config from the package `eslint-config-custom` 3 | extends: ["custom"], 4 | settings: { 5 | next: { 6 | rootDir: ["src"], 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /examples/metadata-indexer/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | 11 | # next.js 12 | .next/ 13 | out/ 14 | build 15 | dist/ 16 | 17 | # misc 18 | .DS_Store 19 | *.pem 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # local env files 27 | .env 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # turbo 34 | .turbo 35 | 36 | # vercel 37 | .vercel 38 | 39 | tsconfig.tsbuildinfo -------------------------------------------------------------------------------- /examples/metadata-indexer/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # metadata-indexer 2 | 3 | ## 0.0.4 4 | 5 | ### Patch Changes 6 | 7 | - 11d0204: fix: handle insertion error when cast has no url embeds 8 | 9 | ## 0.0.3 10 | 11 | ### Patch Changes 12 | 13 | - df47fc8: fix: close hub connection on unexpected process exit 14 | 15 | ## 0.0.2 16 | 17 | ### Patch Changes 18 | 19 | - 5643e7b: feat: add metadata indexer example 20 | -------------------------------------------------------------------------------- /examples/metadata-indexer/README.md: -------------------------------------------------------------------------------- 1 | # URL Metadata Indexer 2 | 3 | This application processes casts on the Farcaster protocol and indexes the metadata of URL embeds found in the casts. Requires the companion open-graph indexer to be running. 4 | 5 | ## Usage 6 | 7 | Modify the environment variable of `app` in `docker-compose.yml` then run with Docker Compose. 8 | 9 | ``` 10 | docker-compose up -d postgres 11 | yarn start 12 | ``` -------------------------------------------------------------------------------- /examples/metadata-indexer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | # Need to install python3 due to ffi requirements in core. Once we release a new version of core removing the dep, 8 | # we can remove this. 9 | dockerfile_inline: | 10 | FROM node:20.2-alpine 11 | RUN apk add --no-cache libc6-compat python3 make g++ linux-headers 12 | restart: unless-stopped 13 | command: ["sh", "-c", "yarn install && exec yarn start"] 14 | init: true 15 | environment: 16 | - NODE_OPTIONS=--max-old-space-size=512 # Limit memory usage 17 | - POSTGRES_URL=postgres://app:password@postgres:5432/metadata 18 | - OPENGRAPH_API=http://host.docker.internal:3001/api/open-graph 19 | - HUB_SSL=false 20 | - HUB_HOST=host.docker.internal:2283 21 | volumes: 22 | - .:/home/node/app 23 | - app_node_modules:/home/node/app/node_modules 24 | working_dir: /home/node/app 25 | depends_on: 26 | - postgres 27 | networks: 28 | - my_network 29 | 30 | postgres: 31 | image: 'postgres:15-alpine' 32 | restart: unless-stopped 33 | ports: 34 | - '6542:5432' # Use a port unlikely to be in use so the example "Just Works" 35 | environment: 36 | - POSTGRES_DB=metadata 37 | - POSTGRES_USER=app 38 | - POSTGRES_PASSWORD=password 39 | volumes: 40 | - pgdata:/var/lib/postgresql/data 41 | healthcheck: 42 | # Need to specify name/user to avoid `FATAL: role "root" does not exist` errors in logs 43 | test: ['CMD-SHELL', 'env', 'pg_isready', '--dbname', '$$POSTGRES_DB', '-U', '$$POSTGRES_USER'] 44 | interval: 10s 45 | timeout: 10s 46 | retries: 3 47 | networks: 48 | - my_network 49 | 50 | volumes: 51 | pgdata: 52 | app_node_modules: 53 | 54 | networks: 55 | my_network: 56 | -------------------------------------------------------------------------------- /examples/metadata-indexer/jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "jest"; 2 | 3 | const jestConfig: Config = { 4 | testEnvironment: "node", 5 | coveragePathIgnorePatterns: ["/build/", "/node_modules/"], 6 | testPathIgnorePatterns: ["/build", "/node_modules"], 7 | extensionsToTreatAsEsm: [".ts"], 8 | moduleNameMapper: { 9 | "^(\\.{1,2}/.*)\\.js$": "$1", 10 | }, 11 | /** 12 | * For high performance with minimal configuration transform with TS with swc. 13 | * @see https://github.com/farcasterxyz/hub/issues/314 14 | */ 15 | transform: { 16 | "^.+\\.(t|j)sx?$": "@swc/jest", 17 | }, 18 | maxWorkers: "50%", 19 | }; 20 | 21 | export default jestConfig; 22 | -------------------------------------------------------------------------------- /examples/metadata-indexer/nixpacks.toml: -------------------------------------------------------------------------------- 1 | [phases.setup] 2 | nixPkgs = ['...', 'python3', 'gcc'] -------------------------------------------------------------------------------- /examples/metadata-indexer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metadata-indexer", 3 | "version": "0.0.4", 4 | "license": "MIT", 5 | "private": true, 6 | "scripts": { 7 | "build": "tsc --project ./tsconfig.json", 8 | "prestart": "npm run build", 9 | "start": "node ./build/index.js start", 10 | "test": "yarn build:all && NODE_OPTIONS=\"--experimental-vm-modules --max-old-space-size=4096\" jest" 11 | }, 12 | "dependencies": { 13 | "@farcaster/hub-nodejs": "^0.10.15", 14 | "@types/express": "^4.17.21", 15 | "dotenv": "^16.3.1", 16 | "express": "^4.18.2", 17 | "express-rate-limit": "^7.1.5", 18 | "fastq": "^1.15.0", 19 | "humanize-duration": "^3.30.0", 20 | "kysely": "^0.26.3", 21 | "mime": "^3.0.0", 22 | "normalize-url": "^8.0.0", 23 | "pg": "^8.11.3", 24 | "pg-cursor": "^2.10.3", 25 | "pino": "^8.16.1", 26 | "pino-pretty": "^10.2.3", 27 | "tiny-typed-emitter": "^2.1.0" 28 | }, 29 | "devDependencies": { 30 | "@swc/cli": "^0.1.62", 31 | "@swc/core": "^1.3.95", 32 | "@swc/jest": "^0.2.29", 33 | "@types/humanize-duration": "^3.27.2", 34 | "@types/jest": "^29.0.2", 35 | "@types/mime": "^3.0.3", 36 | "@types/node": "^20.8.8", 37 | "@types/pg": "^8.10.7", 38 | "@types/pg-cursor": "^2.7.1", 39 | "eslint": "^8.52.0", 40 | "eslint-config-custom": "^0.0.0", 41 | "jest": "^29.7.0", 42 | "prettier": "^3.0.3", 43 | "tsx": "^3.14.0", 44 | "typescript": "^5.2.2" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/metadata-indexer/src/log.ts: -------------------------------------------------------------------------------- 1 | import { pino } from "pino"; 2 | 3 | const LOG_LEVEL = process.env["LOG_LEVEL"] || "info"; 4 | 5 | export const log = pino({ 6 | level: LOG_LEVEL, 7 | transport: { 8 | target: "pino-pretty", 9 | options: { 10 | colorize: true, 11 | singleLine: true, 12 | }, 13 | }, 14 | }); 15 | 16 | export type Logger = pino.Logger; 17 | -------------------------------------------------------------------------------- /examples/metadata-indexer/src/migrations/002_customog.ts: -------------------------------------------------------------------------------- 1 | import { DB } from "../db"; 2 | 3 | export const up = async (db: DB) => { 4 | await db.schema 5 | .alterTable("urlMetadata") 6 | .addColumn("customOpenGraph", "jsonb") 7 | .execute(); 8 | }; 9 | 10 | export const down = async (db: DB) => { 11 | await db.schema 12 | .alterTable("urlMetadata") 13 | .dropColumn("customOpenGraph") 14 | .execute(); 15 | }; 16 | -------------------------------------------------------------------------------- /examples/metadata-indexer/src/server.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response } from "express"; 2 | import { IndexerQueue } from "./indexerQueue"; 3 | import { rateLimit } from "express-rate-limit"; 4 | 5 | export class Server { 6 | private app: express.Application; 7 | 8 | constructor(private indexerQueue: IndexerQueue) { 9 | this.app = express(); 10 | this.configuration(); 11 | this.routes(); 12 | } 13 | 14 | public configuration() { 15 | this.app.set("port", process.env["PORT"] || 3000); 16 | } 17 | 18 | private routes() { 19 | // Middlewares 20 | this.app.use(express.json()); 21 | this.app.use(express.urlencoded({ extended: true })); 22 | const limiter = rateLimit({ 23 | windowMs: 60 * 1000, // 1 minute 24 | max: 100, // Limit each IP to 100 requests per `window` (here, per 1 minutes) 25 | standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers 26 | legacyHeaders: false, // Disable the `X-RateLimit-*` headers 27 | }); 28 | this.app.use(limiter); 29 | 30 | // POST endpoint at /refresh 31 | this.app.post("/refresh", (req: Request, res: Response) => { 32 | // Extracting the 'url' query parameter 33 | const url = req.query["url"] as string; 34 | if (url) { 35 | // URL-decoding the 'url' parameter 36 | const decodedUrl = decodeURIComponent(url); 37 | 38 | this.indexerQueue.push(decodedUrl, { force: true }); 39 | 40 | res.status(200).send({ message: "URL received", url: decodedUrl }); 41 | } else { 42 | res.status(400).send({ message: "URL parameter is missing" }); 43 | } 44 | }); 45 | } 46 | 47 | public start() { 48 | this.app.listen(this.app.get("port"), () => { 49 | console.log(`Server is listening on port ${this.app.get("port")}`); 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/metadata-indexer/src/util/util.test.ts: -------------------------------------------------------------------------------- 1 | import { shuffle } from "./util"; 2 | 3 | describe("util", () => { 4 | test("shuffle", () => { 5 | const array = [1, 2, 3, 4, 5]; 6 | const shuffled = shuffle(array); 7 | expect(shuffled).not.toEqual(array); 8 | expect(shuffled.sort()).toEqual(array); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /examples/metadata-indexer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Needed to import CJS modules like Jest */ 4 | "allowSyntheticDefaultImports": true, 5 | 6 | /* Emit additional JS to ease support for importing CJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 7 | "esModuleInterop": true, 8 | 9 | /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 10 | "target": "es2022", 11 | 12 | "moduleResolution": "Node", 13 | "sourceMap": true, 14 | 15 | "forceConsistentCasingInFileNames": true, 16 | "skipLibCheck": true, 17 | 18 | "strict": true, 19 | "allowUnreachableCode": false, 20 | "exactOptionalPropertyTypes": false, 21 | "noFallthroughCasesInSwitch": true, 22 | "noImplicitOverride": true, 23 | "noImplicitReturns": true, 24 | "noPropertyAccessFromIndexSignature": true, 25 | "noUncheckedIndexedAccess": true, 26 | "noUnusedLocals": false, // causes hard failures when iterating in dev, enforce with instead 27 | "noUnusedParameters": false, 28 | 29 | "resolveJsonModule": true, 30 | // BullMQ doesn't support ESM 31 | // https://github.com/taskforcesh/bullmq/issues/1534 32 | "module": "CommonJS", 33 | "baseUrl": "./src", 34 | "outDir": "build", 35 | "rootDir": "src" 36 | }, 37 | 38 | "ts-node": { 39 | "swc": true 40 | }, 41 | 42 | "exclude": ["node_modules", "jest.config.ts"] 43 | } 44 | -------------------------------------------------------------------------------- /examples/nextjs-shadcn/.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_API_URL="http://localhost:3001/api" 2 | NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID="" 3 | NEXT_PUBLIC_URL="http://localhost:3000" 4 | NEXT_PUBLIC_HOST="localhost:3000" 5 | NEXT_PUBLIC_EXPERIMENTAL_MODS="false" 6 | NEYNAR_API_KEY= 7 | FARCASTER_DEVELOPER_FID= 8 | FARCASTER_DEVELOPER_MNEMONIC= 9 | -------------------------------------------------------------------------------- /examples/nextjs-shadcn/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["custom", "next"], 4 | }; 5 | -------------------------------------------------------------------------------- /examples/nextjs-shadcn/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /examples/nextjs-shadcn/README.md: -------------------------------------------------------------------------------- 1 | ## Getting Started 2 | 3 | Copy `.env.example` to a new file called `.env` in this directory. In order for this example to work, it depends on the backend in `../api` at port `3001` to be running. You can do this by navigating to `../api` and following the `README` there to run the server. 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | yarn dev 9 | ``` 10 | 11 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 12 | -------------------------------------------------------------------------------- /examples/nextjs-shadcn/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /examples/nextjs-shadcn/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | // this is mod-monorepo specific 4 | transpilePackages: ["@mod-protocol/react"], 5 | 6 | images: { 7 | domains: [ 8 | "*.i.imgur.com", 9 | "i.imgur.com", 10 | // cloudflare proxy 11 | "https://imagedelivery.net", 12 | "https://www.discove.xyz", 13 | // preview deployments 14 | "*-discove.vercel.app", 15 | // warpcast 16 | "res.cloudinary.com", 17 | "warpcast.com", 18 | "ipfs.decentralized-content.com", 19 | "i.seadn.io", 20 | "www.github.com", 21 | "opengraph.githubassets.com", 22 | ], 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /examples/nextjs-shadcn/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.4.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@farcaster/core": "^0.13.5", 13 | "@farcaster/hub-web": "^0.7.3", 14 | "@mod-protocol/core": "^0.2.0", 15 | "@mod-protocol/mod-registry": "^0.2.0", 16 | "@mod-protocol/react": "^0.2.0", 17 | "@mod-protocol/react-editor": "^0.1.0", 18 | "@mod-protocol/react-ui-shadcn": "^0.3.1", 19 | "@rainbow-me/rainbowkit": "^1.2.0", 20 | "axios": "^1.6.7", 21 | "encoding": "^0.1.13", 22 | "next": "^13.5.6", 23 | "next-themes": "^0.2.1", 24 | "qrcode.react": "^3.1.0", 25 | "react": "^18.2.0", 26 | "react-dom": "^18.2.0", 27 | "tailwindcss-animate": "^1.0.6", 28 | "viem": "^1.19.0", 29 | "wagmi": "^1.4.6" 30 | }, 31 | "devDependencies": { 32 | "@tailwindcss/typography": "^0.5.9", 33 | "@types/node": "^17.0.12", 34 | "@types/react": "^18.0.22", 35 | "@types/react-dom": "^18.0.7", 36 | "autoprefixer": "^10.4.14", 37 | "eslint-config-custom": "*", 38 | "eslint-config-next": "^13.5.3", 39 | "postcss": "^8.4.27", 40 | "tailwindcss": "^3.3.3", 41 | "tsconfig": "*", 42 | "typescript": "^5.2.2" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/nextjs-shadcn/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /examples/nextjs-shadcn/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from "@mod-protocol/react-ui-shadcn/dist/components/theme-provider"; 2 | import "./globals.css"; 3 | 4 | export default function RootLayout({ 5 | children, 6 | }: { 7 | children: React.ReactNode; 8 | }) { 9 | return ( 10 | 11 | 12 | 13 | {children} 14 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /examples/nextjs-shadcn/src/app/make-message.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HashScheme, 3 | Message, 4 | MessageData, 5 | NobleEd25519Signer, 6 | } from "@farcaster/hub-web"; 7 | import { blake3 } from "@noble/hashes/blake3"; 8 | 9 | // fixme: actually store this? 10 | function getSigner(privateKey: string): NobleEd25519Signer { 11 | const ed25519Signer = new NobleEd25519Signer(Buffer.from(privateKey, "hex")); 12 | return ed25519Signer; 13 | } 14 | 15 | function getSignerFromStorage(key: string = "keyPair"): NobleEd25519Signer { 16 | const privateKey = JSON.parse(localStorage.getItem(key) || "{}").privateKey; 17 | return getSigner(privateKey); 18 | } 19 | 20 | export async function makeMessage(messageData: MessageData) { 21 | const signer = getSignerFromStorage(); 22 | 23 | const dataBytes = MessageData.encode(messageData).finish(); 24 | 25 | const hash = blake3(dataBytes, { dkLen: 20 }); 26 | 27 | const signature = await signer.signMessageHash(hash); 28 | if (signature.isErr()) return null; 29 | 30 | const signerKey = await signer.getSignerKey(); 31 | if (signerKey.isErr()) return null; 32 | 33 | const message = Message.create({ 34 | data: messageData, 35 | hash, 36 | hashScheme: HashScheme.BLAKE3, 37 | signature: signature.value, 38 | signatureScheme: signer.scheme, 39 | signer: signerKey.value, 40 | }); 41 | 42 | return message; 43 | } 44 | -------------------------------------------------------------------------------- /examples/nextjs-shadcn/src/app/post-message/frame-action/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | 3 | //proxy so as to not expose neynar api key 4 | export async function POST(req: NextRequest, res: NextResponse) { 5 | const data = await req.json(); 6 | 7 | try { 8 | const options = { 9 | method: "POST", 10 | headers: { 11 | accept: "application/json", 12 | api_key: process.env.NEYNAR_API_KEY, 13 | "content-type": "application/json", 14 | }, 15 | body: JSON.stringify(data), 16 | }; 17 | 18 | const res = await fetch( 19 | "https://api.neynar.com/v2/farcaster/frame/action", 20 | options 21 | ); 22 | const resJson = (await res.json()) as { 23 | version: string; 24 | frames_url: string; 25 | image: string; 26 | buttons: Array<{ index: number; title: string }>; 27 | post_url: string; 28 | }; 29 | 30 | return NextResponse.json( 31 | { 32 | "fc:frame": resJson.version, 33 | "fc:frame:image": resJson.image, 34 | "fc:frame:post_url": resJson.post_url, 35 | ...resJson.buttons.reduce((prev, next, i) => { 36 | return { 37 | ...prev, 38 | ["fc:frame:button:" + next.index]: next.title, 39 | }; 40 | }, {}), 41 | }, 42 | { status: 200 } 43 | ); 44 | } catch (error) { 45 | console.error(error); 46 | return NextResponse.error(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/nextjs-shadcn/src/app/relative-date.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | 3 | export function fromNow(date: Date): string { 4 | const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000); 5 | const years = Math.floor(seconds / 31536000); 6 | const months = Math.floor(seconds / 2592000); 7 | const days = Math.floor(seconds / 86400); 8 | 9 | if (days > 548) { 10 | return years + "y"; 11 | } 12 | if (days >= 320 && days <= 547) { 13 | return "1y"; 14 | } 15 | if (days >= 45 && days <= 319) { 16 | return months + "mo"; 17 | } 18 | if (days >= 26 && days <= 45) { 19 | return "1mo"; 20 | } 21 | 22 | // rounds down 23 | const hours = Math.floor(seconds / 3600); 24 | 25 | if (hours >= 48 && days <= 25) { 26 | return days + "d"; 27 | } 28 | if (hours >= 22 && hours <= 48) { 29 | return "1d"; 30 | } 31 | 32 | // rounds down 33 | const minutes = Math.floor(seconds / 60); 34 | 35 | if (minutes >= 120) { 36 | return hours + "h"; 37 | } 38 | if (minutes >= 45) { 39 | return "1h"; 40 | } 41 | if (minutes >= 1) { 42 | return minutes + "m"; 43 | } 44 | // don't show seconds, so that it doesn't have SSR issues 45 | return "1m"; 46 | } 47 | 48 | // https://github.com/vercel/next.js/discussions/38263 49 | export const useRelativeDate = (date: Date | null): string | null => { 50 | const formattedDate = useMemo(() => (date ? fromNow(date) : null), [date]); 51 | 52 | return formattedDate; 53 | }; 54 | -------------------------------------------------------------------------------- /examples/nextjs-shadcn/src/app/use-experimental-mods.ts: -------------------------------------------------------------------------------- 1 | import { useSearchParams } from "next/navigation"; 2 | 3 | export const useExperimentalMods = (): boolean => { 4 | const searchParams = useSearchParams(); 5 | 6 | const EXPERIMENTAL_MODS = 7 | process.env.NEXT_PUBLIC_EXPERIMENTAL_MODS === "true" || 8 | searchParams.get("experimental") === "true"; 9 | 10 | return EXPERIMENTAL_MODS; 11 | }; 12 | -------------------------------------------------------------------------------- /examples/nextjs-shadcn/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/nextjs.json", 3 | "compilerOptions": { 4 | "plugins": [ 5 | { 6 | "name": "next" 7 | } 8 | ], 9 | }, 10 | "include": [ 11 | "next-env.d.ts", 12 | "**/*.ts", 13 | "**/*.tsx", 14 | ".next/types/**/*.ts" 15 | ], 16 | "exclude": [ 17 | "node_modules" 18 | ] 19 | } -------------------------------------------------------------------------------- /mods/chatgpt-shorten/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mods/chatgpt-shorten 2 | 3 | ## 0.1.1 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [df98c06] 8 | - Updated dependencies [2901ee9] 9 | - Updated dependencies [2ff629a] 10 | - @mod-protocol/core@0.2.0 11 | 12 | ## 0.1.0 13 | 14 | ### Minor Changes 15 | 16 | - 63b4864: Renames Content rendering to Rich Embeds, Renames Mini-apps to Mods, updates docs 17 | 18 | ### Patch Changes 19 | 20 | - Updated dependencies [584ffc2] 21 | - Updated dependencies [f2141a9] 22 | - Updated dependencies [2b7c514] 23 | - Updated dependencies [f2141a9] 24 | - Updated dependencies [584ffc2] 25 | - Updated dependencies [69be77d] 26 | - Updated dependencies [63b4864] 27 | - @mod-protocol/core@0.1.0 28 | 29 | ## 0.0.2 30 | 31 | ### Patch Changes 32 | 33 | - Updated dependencies [8ae8453] 34 | - Updated dependencies [15d9d7f] 35 | - Updated dependencies [4a50bdd] 36 | - @mod-protocol/core@0.0.2 37 | -------------------------------------------------------------------------------- /mods/chatgpt-shorten/index.ts: -------------------------------------------------------------------------------- 1 | export { default as default } from "./src/manifest"; 2 | -------------------------------------------------------------------------------- /mods/chatgpt-shorten/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mods/chatgpt-shorten", 3 | "main": "./index.ts", 4 | "types": "./index.ts", 5 | "version": "0.1.1", 6 | "private": true, 7 | "dependencies": { 8 | "@mod-protocol/core": "^0.2.0" 9 | } 10 | } -------------------------------------------------------------------------------- /mods/chatgpt-shorten/src/action.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const action: ModElement[] = [ 4 | { 5 | type: "vertical-layout", 6 | onload: { 7 | ref: "myChatGPTServerRequest", 8 | type: "POST", 9 | url: "{{api}}/chatgpt-shorten", 10 | body: { 11 | json: { 12 | type: "object", 13 | value: { 14 | text: { 15 | type: "string", 16 | value: "{{input}}", 17 | }, 18 | }, 19 | }, 20 | }, 21 | onsuccess: "#success", 22 | onerror: "#error", 23 | onloading: "#loading", 24 | }, 25 | }, 26 | ]; 27 | 28 | export default action; 29 | -------------------------------------------------------------------------------- /mods/chatgpt-shorten/src/error.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const error: ModElement[] = [ 4 | { 5 | type: "text", 6 | label: "ERROR: {{refs.myChatGPTServerRequest.error.message}}", 7 | }, 8 | { 9 | type: "button", 10 | label: "Retry", 11 | onclick: { 12 | ref: "myChatGPTServerRequest", 13 | type: "POST", 14 | url: "{{api}}/chatgpt-shorten", 15 | body: { 16 | json: { 17 | type: "object", 18 | value: { 19 | text: { 20 | type: "string", 21 | value: "{{input}}", 22 | }, 23 | }, 24 | }, 25 | }, 26 | onsuccess: "#success", 27 | onerror: "#error", 28 | onloading: "#loading", 29 | }, 30 | }, 31 | ]; 32 | 33 | export default error; 34 | -------------------------------------------------------------------------------- /mods/chatgpt-shorten/src/loading.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const loading: ModElement[] = [ 4 | { 5 | type: "text", 6 | label: "Shortening the input...", 7 | }, 8 | { 9 | type: "circular-progress", 10 | }, 11 | ]; 12 | 13 | export default loading; 14 | -------------------------------------------------------------------------------- /mods/chatgpt-shorten/src/manifest.ts: -------------------------------------------------------------------------------- 1 | import { ModManifest } from "@mod-protocol/core"; 2 | import action from "./action"; 3 | import success from "./success"; 4 | import error from "./error"; 5 | import loading from "./loading"; 6 | 7 | const manifest: ModManifest = { 8 | slug: "chatgpt-shorten", 9 | name: "Shorten text using AI", 10 | custodyAddress: "furlong.eth", 11 | logo: "https://i.imgur.com/hV566qC.png", 12 | custodyGithubUsername: "davidfurlong", 13 | version: "0.0.1", 14 | creationEntrypoints: action, 15 | elements: { 16 | "#success": success, 17 | "#error": error, 18 | "#loading": loading, 19 | }, 20 | }; 21 | 22 | export default manifest; 23 | -------------------------------------------------------------------------------- /mods/chatgpt-shorten/src/success.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const success: ModElement[] = [ 4 | { 5 | type: "vertical-layout", 6 | elements: [ 7 | { 8 | type: "text", 9 | label: "{{refs.myChatGPTServerRequest.response.data.response.text}}", 10 | }, 11 | { 12 | type: "horizontal-layout", 13 | elements: [ 14 | { 15 | type: "button", 16 | label: "Replace", 17 | onclick: { 18 | type: "SETINPUT", 19 | value: 20 | "{{refs.myChatGPTServerRequest.response.data.response.text}}", 21 | onsuccess: { 22 | type: "EXIT", 23 | }, 24 | }, 25 | }, 26 | { 27 | type: "button", 28 | label: "Retry", 29 | variant: "secondary", 30 | onclick: { 31 | ref: "myChatGPTServerRequest", 32 | type: "POST", 33 | url: "{{api}}/chatgpt-shorten", 34 | body: { 35 | json: { 36 | type: "object", 37 | value: { 38 | text: { 39 | type: "string", 40 | value: "{{input}}", 41 | }, 42 | }, 43 | }, 44 | }, 45 | onsuccess: "#success", 46 | onerror: "#error", 47 | }, 48 | }, 49 | ], 50 | }, 51 | ], 52 | }, 53 | ]; 54 | 55 | export default success; 56 | -------------------------------------------------------------------------------- /mods/chatgpt-shorten/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /mods/chatgpt/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mods/chatgpt 2 | 3 | ## 0.1.1 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [df98c06] 8 | - Updated dependencies [2901ee9] 9 | - Updated dependencies [2ff629a] 10 | - @mod-protocol/core@0.2.0 11 | 12 | ## 0.1.0 13 | 14 | ### Minor Changes 15 | 16 | - 63b4864: Renames Content rendering to Rich Embeds, Renames Mini-apps to Mods, updates docs 17 | 18 | ### Patch Changes 19 | 20 | - 2b7c514: Adds the combobox, select, textarea mod elements. Adds `Model Definitions` to Manifests. Adds the `ethPersonalSign` action. Adds `json-ld` indexing to metadata. Adds a `loadingLabel` prop to buttons 21 | - Updated dependencies [584ffc2] 22 | - Updated dependencies [f2141a9] 23 | - Updated dependencies [2b7c514] 24 | - Updated dependencies [f2141a9] 25 | - Updated dependencies [584ffc2] 26 | - Updated dependencies [69be77d] 27 | - Updated dependencies [63b4864] 28 | - @mod-protocol/core@0.1.0 29 | 30 | ## 0.0.2 31 | 32 | ### Patch Changes 33 | 34 | - Updated dependencies [8ae8453] 35 | - Updated dependencies [15d9d7f] 36 | - Updated dependencies [4a50bdd] 37 | - @mod-protocol/core@0.0.2 38 | -------------------------------------------------------------------------------- /mods/chatgpt/index.ts: -------------------------------------------------------------------------------- 1 | export { default as default } from "./src/manifest"; 2 | -------------------------------------------------------------------------------- /mods/chatgpt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mods/chatgpt", 3 | "main": "./index.ts", 4 | "types": "./index.ts", 5 | "private": true, 6 | "version": "0.1.1", 7 | "dependencies": { 8 | "@mod-protocol/core": "^0.2.0" 9 | } 10 | } -------------------------------------------------------------------------------- /mods/chatgpt/src/action.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const action: ModElement[] = [ 4 | { 5 | type: "vertical-layout", 6 | elements: [ 7 | { 8 | type: "input", 9 | placeholder: "Ask the AI anything", 10 | isClearable: true, 11 | ref: "prompt", 12 | // onchange: { 13 | // ref: "mySearchQueryRequest", 14 | // }, 15 | }, 16 | { 17 | type: "button", 18 | label: "Send", 19 | onclick: { 20 | ref: "myChatGPTServerRequest", 21 | type: "POST", 22 | url: "{{api}}/chatgpt", 23 | body: { 24 | json: { 25 | type: "object", 26 | value: { 27 | prompt: { 28 | type: "string", 29 | value: "{{refs.prompt.value}}", 30 | }, 31 | text: { 32 | type: "string", 33 | value: "{{input}}", 34 | }, 35 | }, 36 | }, 37 | }, 38 | onsuccess: "#success", 39 | onerror: "#error", 40 | onloading: "#loading", 41 | }, 42 | }, 43 | ], 44 | }, 45 | ]; 46 | 47 | export default action; 48 | -------------------------------------------------------------------------------- /mods/chatgpt/src/error.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const error: ModElement[] = [ 4 | { 5 | type: "text", 6 | label: "ERROR: {{refs.myChatGPTServerRequest.error.message}}", 7 | }, 8 | { 9 | type: "button", 10 | label: "Retry", 11 | onclick: { 12 | ref: "myChatGPTServerRequest", 13 | type: "POST", 14 | url: "{{api}}/chatgpt", 15 | body: { 16 | json: { 17 | type: "object", 18 | value: { 19 | prompt: { 20 | type: "string", 21 | value: "{{refs.prompt.value}}", 22 | }, 23 | text: { 24 | type: "string", 25 | value: "{{input}}", 26 | }, 27 | }, 28 | }, 29 | }, 30 | onsuccess: "#success", 31 | onerror: "#error", 32 | onloading: "#loading", 33 | }, 34 | }, 35 | ]; 36 | 37 | export default error; 38 | -------------------------------------------------------------------------------- /mods/chatgpt/src/loading.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const loading: ModElement[] = [ 4 | { 5 | type: "text", 6 | label: "Fetching a response...", 7 | }, 8 | { 9 | type: "circular-progress", 10 | }, 11 | ]; 12 | 13 | export default loading; 14 | -------------------------------------------------------------------------------- /mods/chatgpt/src/manifest.ts: -------------------------------------------------------------------------------- 1 | import { ModManifest } from "@mod-protocol/core"; 2 | import action from "./action"; 3 | import success from "./success"; 4 | import error from "./error"; 5 | import loading from "./loading"; 6 | 7 | const manifest: ModManifest = { 8 | slug: "chatgpt", 9 | name: "Prompt ChatGPT", 10 | custodyAddress: "furlong.eth", 11 | logo: "https://i.imgur.com/YayIWi1.png", 12 | custodyGithubUsername: "davidfurlong", 13 | version: "0.0.1", 14 | creationEntrypoints: action, 15 | elements: { 16 | "#success": success, 17 | "#error": error, 18 | "#loading": loading, 19 | }, 20 | }; 21 | 22 | export default manifest; 23 | -------------------------------------------------------------------------------- /mods/chatgpt/src/success.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const success: ModElement[] = [ 4 | { 5 | type: "vertical-layout", 6 | elements: [ 7 | { 8 | type: "text", 9 | label: "{{refs.myChatGPTServerRequest.response.data.response.text}}", 10 | }, 11 | { 12 | type: "horizontal-layout", 13 | elements: [ 14 | { 15 | type: "button", 16 | label: "Replace", 17 | onclick: { 18 | type: "SETINPUT", 19 | value: 20 | "{{refs.myChatGPTServerRequest.response.data.response.text}}", 21 | onsuccess: { 22 | type: "EXIT", 23 | }, 24 | }, 25 | }, 26 | { 27 | type: "button", 28 | label: "Retry", 29 | variant: "secondary", 30 | onclick: { 31 | ref: "myChatGPTServerRequest", 32 | type: "POST", 33 | url: "{{api}}/chatgpt", 34 | body: { 35 | json: { 36 | type: "object", 37 | value: { 38 | text: { 39 | type: "string", 40 | value: "{{input}}", 41 | }, 42 | }, 43 | }, 44 | }, 45 | onsuccess: "#success", 46 | onerror: "#error", 47 | }, 48 | }, 49 | ], 50 | }, 51 | ], 52 | }, 53 | ]; 54 | 55 | export default success; 56 | -------------------------------------------------------------------------------- /mods/chatgpt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /mods/dall-e/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mods/dall-e 2 | 3 | ## 0.0.3 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [df98c06] 8 | - Updated dependencies [2901ee9] 9 | - Updated dependencies [2ff629a] 10 | - @mod-protocol/core@0.2.0 11 | 12 | ## 0.0.2 13 | 14 | ### Patch Changes 15 | 16 | - Updated dependencies [584ffc2] 17 | - Updated dependencies [f2141a9] 18 | - Updated dependencies [2b7c514] 19 | - Updated dependencies [f2141a9] 20 | - Updated dependencies [584ffc2] 21 | - Updated dependencies [69be77d] 22 | - Updated dependencies [63b4864] 23 | - @mod-protocol/core@0.1.0 24 | -------------------------------------------------------------------------------- /mods/dall-e/index.ts: -------------------------------------------------------------------------------- 1 | export { default as default } from "./src/manifest"; 2 | -------------------------------------------------------------------------------- /mods/dall-e/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mods/dall-e", 3 | "main": "./index.ts", 4 | "types": "./index.ts", 5 | "private": true, 6 | "version": "0.0.3", 7 | "dependencies": { 8 | "@mod-protocol/core": "^0.2.0" 9 | } 10 | } -------------------------------------------------------------------------------- /mods/dall-e/src/action.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const action: ModElement[] = [ 4 | { 5 | type: "vertical-layout", 6 | elements: [ 7 | { 8 | type: "input", 9 | placeholder: "Ask DALL-E to create any image", 10 | ref: "prompt", 11 | }, 12 | { 13 | type: "button", 14 | label: "Imagine", 15 | onclick: { 16 | ref: "myChatGPTServerRequest", 17 | type: "POST", 18 | url: "{{api}}/dall-e", 19 | body: { 20 | json: { 21 | type: "object", 22 | value: { 23 | prompt: { 24 | type: "string", 25 | value: "{{refs.prompt.value}}", 26 | }, 27 | }, 28 | }, 29 | }, 30 | onsuccess: "#success", 31 | onerror: "#error", 32 | onloading: "#loading", 33 | }, 34 | }, 35 | ], 36 | }, 37 | ]; 38 | 39 | export default action; 40 | -------------------------------------------------------------------------------- /mods/dall-e/src/error.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const error: ModElement[] = [ 4 | { 5 | type: "text", 6 | label: "ERROR: {{refs.myChatGPTServerRequest.error.message}}", 7 | }, 8 | { 9 | type: "button", 10 | label: "Retry", 11 | onclick: { 12 | ref: "myChatGPTServerRequest", 13 | type: "POST", 14 | url: "{{api}}/dall-e", 15 | body: { 16 | json: { 17 | type: "object", 18 | value: { 19 | prompt: { 20 | type: "string", 21 | value: "{{refs.prompt.value}}", 22 | }, 23 | text: { 24 | type: "string", 25 | value: "{{input}}", 26 | }, 27 | }, 28 | }, 29 | }, 30 | onsuccess: "#success", 31 | onerror: "#error", 32 | onloading: "#loading", 33 | }, 34 | }, 35 | ]; 36 | 37 | export default error; 38 | -------------------------------------------------------------------------------- /mods/dall-e/src/loading.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const loading: ModElement[] = [ 4 | { 5 | type: "horizontal-layout", 6 | elements: [ 7 | { 8 | type: "circular-progress", 9 | }, 10 | { 11 | type: "text", 12 | label: "Give it a sec, it's the closest thing to magic we have...", 13 | }, 14 | ], 15 | }, 16 | ]; 17 | 18 | export default loading; 19 | -------------------------------------------------------------------------------- /mods/dall-e/src/manifest.ts: -------------------------------------------------------------------------------- 1 | import { ModManifest } from "@mod-protocol/core"; 2 | import action from "./action"; 3 | import success from "./success"; 4 | import error from "./error"; 5 | import loading from "./loading"; 6 | 7 | const manifest: ModManifest = { 8 | slug: "dall-e", 9 | name: "Use AI to make an image", 10 | custodyAddress: "furlong.eth", 11 | logo: "https://i.imgur.com/zKiFDal.png", 12 | custodyGithubUsername: "davidfurlong", 13 | version: "0.0.1", 14 | creationEntrypoints: action, 15 | elements: { 16 | "#success": success, 17 | "#error": error, 18 | "#loading": loading, 19 | }, 20 | }; 21 | 22 | export default manifest; 23 | -------------------------------------------------------------------------------- /mods/dall-e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /mods/farcaster-frames-render/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mods/farcaster-frames-render 2 | 3 | ## 0.2.2 4 | 5 | ### Patch Changes 6 | 7 | - Fix aspect ratio 8 | 9 | ## 0.2.1 10 | 11 | ### Patch Changes 12 | 13 | - 3a7078a: fix: multi-step frames 14 | - Updated dependencies [3a7078a] 15 | - @mod-protocol/core@0.2.1 16 | 17 | ## 0.2.0 18 | 19 | ### Minor Changes 20 | 21 | - 2901ee9: Adds Farcaster Frames 22 | - 2ff629a: Add Farcaster frames support, including a new action Mods can call, ADDFCFRAMEACTION 23 | 24 | ### Patch Changes 25 | 26 | - Updated dependencies [df98c06] 27 | - Updated dependencies [2901ee9] 28 | - Updated dependencies [2ff629a] 29 | - @mod-protocol/core@0.2.0 30 | -------------------------------------------------------------------------------- /mods/farcaster-frames-render/index.ts: -------------------------------------------------------------------------------- 1 | export { default as default } from "./src/manifest"; 2 | -------------------------------------------------------------------------------- /mods/farcaster-frames-render/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mods/farcaster-frames-render", 3 | "main": "./index.ts", 4 | "types": "./index.ts", 5 | "version": "0.2.2", 6 | "private": true, 7 | "dependencies": { 8 | "@mod-protocol/core": "^0.2.1" 9 | } 10 | } -------------------------------------------------------------------------------- /mods/farcaster-frames-render/src/manifest.ts: -------------------------------------------------------------------------------- 1 | import { ModManifest } from "@mod-protocol/core"; 2 | import view from "./view"; 3 | import rerender from "./rerender"; 4 | 5 | const manifest: ModManifest = { 6 | slug: "farcaster-frames-render", 7 | name: "View Farcaster Frames", 8 | custodyAddress: "furlong.eth", 9 | version: "0.0.1", 10 | logo: "https://i.imgur.com/B8TP2gG.png", 11 | custodyGithubUsername: "davidfurlong", 12 | richEmbedEntrypoints: [ 13 | { 14 | if: { 15 | value: "{{embed.metadata.customOpenGraph.['fc:frame']}}", 16 | match: { 17 | equals: "vNext", 18 | }, 19 | }, 20 | element: view, 21 | }, 22 | ], 23 | elements: { 24 | "#view": view, 25 | "#rerender": rerender, 26 | }, 27 | }; 28 | 29 | export default manifest; 30 | -------------------------------------------------------------------------------- /mods/farcaster-frames-render/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": [ 4 | "." 5 | ], 6 | "exclude": [ 7 | "dist", 8 | "build", 9 | "node_modules" 10 | ] 11 | } -------------------------------------------------------------------------------- /mods/giphy-picker/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mods/giphy-picker 2 | 3 | ## 0.1.1 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [df98c06] 8 | - Updated dependencies [2901ee9] 9 | - Updated dependencies [2ff629a] 10 | - @mod-protocol/core@0.2.0 11 | 12 | ## 0.1.0 13 | 14 | ### Minor Changes 15 | 16 | - 63b4864: Renames Content rendering to Rich Embeds, Renames Mini-apps to Mods, updates docs 17 | 18 | ### Patch Changes 19 | 20 | - 2b7c514: Adds the combobox, select, textarea mod elements. Adds `Model Definitions` to Manifests. Adds the `ethPersonalSign` action. Adds `json-ld` indexing to metadata. Adds a `loadingLabel` prop to buttons 21 | - Updated dependencies [584ffc2] 22 | - Updated dependencies [f2141a9] 23 | - Updated dependencies [2b7c514] 24 | - Updated dependencies [f2141a9] 25 | - Updated dependencies [584ffc2] 26 | - Updated dependencies [69be77d] 27 | - Updated dependencies [63b4864] 28 | - @mod-protocol/core@0.1.0 29 | 30 | ## 0.0.2 31 | 32 | ### Patch Changes 33 | 34 | - Updated dependencies [8ae8453] 35 | - Updated dependencies [15d9d7f] 36 | - Updated dependencies [4a50bdd] 37 | - @mod-protocol/core@0.0.2 38 | -------------------------------------------------------------------------------- /mods/giphy-picker/index.ts: -------------------------------------------------------------------------------- 1 | export { default as default } from "./src/manifest"; 2 | -------------------------------------------------------------------------------- /mods/giphy-picker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mods/giphy-picker", 3 | "main": "./index.ts", 4 | "types": "./index.ts", 5 | "version": "0.1.1", 6 | "private": true, 7 | "dependencies": { 8 | "@mod-protocol/core": "^0.2.0" 9 | } 10 | } -------------------------------------------------------------------------------- /mods/giphy-picker/src/action.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const action: ModElement[] = [ 4 | { 5 | type: "vertical-layout", 6 | onload: { 7 | ref: "mySearchQueryRequest", 8 | type: "GET", 9 | url: "{{api}}/giphy-picker?type=gifs&limit=24", 10 | onsuccess: "#success", 11 | onerror: "#error", 12 | onloading: "#loading", 13 | }, 14 | }, 15 | ]; 16 | 17 | export default action; 18 | -------------------------------------------------------------------------------- /mods/giphy-picker/src/error.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const error: ModElement[] = [ 4 | { 5 | type: "vertical-layout", 6 | elements: [ 7 | { 8 | ref: "giphyType", 9 | type: "tabs", 10 | values: ["gifs", "stickers"], 11 | names: ["GIFs", "Stickers"], 12 | onchange: { 13 | ref: "mySearchQueryRequest", 14 | type: "GET", 15 | url: "{{api}}/giphy-picker?q={{refs.myInput.value}}&type={{refs.giphyType.value}}&limit=24", 16 | onsuccess: "#success", 17 | onerror: "#error", 18 | onloading: "#loading", 19 | }, 20 | }, 21 | { 22 | ref: "myInput", 23 | type: "input", 24 | placeholder: "Search", 25 | isClearable: true, 26 | onchange: { 27 | ref: "mySearchQueryRequest", 28 | type: "GET", 29 | url: "{{api}}/giphy-picker?q={{refs.myInput.value}}&type={{refs.giphyType.value}}&limit=24", 30 | onsuccess: "#success", 31 | onerror: "#error", 32 | onloading: "#loading", 33 | }, 34 | }, 35 | { 36 | type: "alert", 37 | title: "Error", 38 | description: "{{refs.mySearchQueryRequest.error.statusText}}", 39 | variant: "error", 40 | }, 41 | ], 42 | }, 43 | ]; 44 | 45 | export default error; 46 | -------------------------------------------------------------------------------- /mods/giphy-picker/src/loading.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const loading: ModElement[] = [ 4 | { 5 | type: "vertical-layout", 6 | elements: [ 7 | { 8 | ref: "giphyType", 9 | type: "tabs", 10 | values: ["gifs", "stickers"], 11 | names: ["GIFs", "Stickers"], 12 | onchange: { 13 | ref: "mySearchQueryRequest", 14 | type: "GET", 15 | url: "{{api}}/giphy-picker?q={{refs.myInput.value}}&type={{refs.giphyType.value}}&limit=24", 16 | onsuccess: "#success", 17 | onerror: "#error", 18 | onloading: "#loading", 19 | }, 20 | }, 21 | { 22 | ref: "myInput", 23 | type: "input", 24 | placeholder: "Search", 25 | isClearable: true, 26 | onchange: { 27 | ref: "mySearchQueryRequest", 28 | type: "GET", 29 | url: "{{api}}/giphy-picker?q={{refs.myInput.value}}&type={{refs.giphyType.value}}&limit=24", 30 | onsuccess: "#success", 31 | onerror: "#error", 32 | onloading: "#loading", 33 | }, 34 | }, 35 | { 36 | ref: "imageGridList", 37 | type: "image-grid-list", 38 | loading: true, 39 | }, 40 | ], 41 | }, 42 | ]; 43 | 44 | export default loading; 45 | -------------------------------------------------------------------------------- /mods/giphy-picker/src/manifest.ts: -------------------------------------------------------------------------------- 1 | import { ModManifest } from "@mod-protocol/core"; 2 | import action from "./action"; 3 | import success from "./success"; 4 | import error from "./error"; 5 | import loading from "./loading"; 6 | 7 | const manifest: ModManifest = { 8 | slug: "giphy-picker", 9 | name: "Add GIF/Sticker", 10 | custodyAddress: "furlong.eth", 11 | custodyGithubUsername: "davidfurlong", 12 | logo: "https://i.imgur.com/vccxL2r.png", 13 | version: "0.0.1", 14 | creationEntrypoints: action, 15 | elements: { 16 | "#success": success, 17 | "#error": error, 18 | "#loading": loading, 19 | }, 20 | }; 21 | 22 | export default manifest; 23 | -------------------------------------------------------------------------------- /mods/giphy-picker/src/success.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const success: ModElement[] = [ 4 | { 5 | type: "vertical-layout", 6 | elements: [ 7 | { 8 | ref: "giphyType", 9 | type: "tabs", 10 | values: ["gifs", "stickers"], 11 | names: ["GIFs", "Stickers"], 12 | onchange: { 13 | ref: "mySearchQueryRequest", 14 | type: "GET", 15 | url: "{{api}}/giphy-picker?q={{refs.myInput.value}}&type={{refs.giphyType.value}}&limit=24", 16 | onsuccess: "#success", 17 | onerror: "#error", 18 | onloading: "#loading", 19 | }, 20 | }, 21 | { 22 | ref: "myInput", 23 | type: "input", 24 | placeholder: "Search", 25 | isClearable: true, 26 | onchange: { 27 | ref: "mySearchQueryRequest", 28 | type: "GET", 29 | url: "{{api}}/giphy-picker?q={{refs.myInput.value}}&type={{refs.giphyType.value}}&limit=24", 30 | onsuccess: "#success", 31 | onerror: "#error", 32 | onloading: "#loading", 33 | }, 34 | }, 35 | { 36 | ref: "imageGridList", 37 | type: "image-grid-list", 38 | imagesListRef: "refs.mySearchQueryRequest.response.data.list", 39 | onpick: { 40 | type: "ADDEMBED", 41 | url: "{{refs.imageGridList.url}}", 42 | name: "GIF", 43 | mimeType: "image/gif", 44 | onsuccess: { 45 | type: "EXIT", 46 | }, 47 | }, 48 | }, 49 | { 50 | type: "image", 51 | imageSrc: "https://i.imgur.com/rGB2Uev.png", 52 | }, 53 | ], 54 | }, 55 | ]; 56 | 57 | export default success; 58 | -------------------------------------------------------------------------------- /mods/giphy-picker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /mods/image-render/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mods/image-render 2 | 3 | ## 0.1.1 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [df98c06] 8 | - Updated dependencies [2901ee9] 9 | - Updated dependencies [2ff629a] 10 | - @mod-protocol/core@0.2.0 11 | 12 | ## 0.1.0 13 | 14 | ### Minor Changes 15 | 16 | - 63b4864: Renames Content rendering to Rich Embeds, Renames Mini-apps to Mods, updates docs 17 | 18 | ### Patch Changes 19 | 20 | - Updated dependencies [584ffc2] 21 | - Updated dependencies [f2141a9] 22 | - Updated dependencies [2b7c514] 23 | - Updated dependencies [f2141a9] 24 | - Updated dependencies [584ffc2] 25 | - Updated dependencies [69be77d] 26 | - Updated dependencies [63b4864] 27 | - @mod-protocol/core@0.1.0 28 | 29 | ## 0.0.2 30 | 31 | ### Patch Changes 32 | 33 | - 6e38b0c: fix: match image embeds based on file extension in url 34 | - Updated dependencies [8ae8453] 35 | - Updated dependencies [15d9d7f] 36 | - Updated dependencies [4a50bdd] 37 | - @mod-protocol/core@0.0.2 38 | -------------------------------------------------------------------------------- /mods/image-render/index.ts: -------------------------------------------------------------------------------- 1 | export { default as default } from "./src/manifest"; 2 | -------------------------------------------------------------------------------- /mods/image-render/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mods/image-render", 3 | "main": "./index.ts", 4 | "types": "./index.ts", 5 | "version": "0.1.1", 6 | "private": true, 7 | "dependencies": { 8 | "@mod-protocol/core": "^0.2.0" 9 | } 10 | } -------------------------------------------------------------------------------- /mods/image-render/src/manifest.ts: -------------------------------------------------------------------------------- 1 | import { ModManifest } from "@mod-protocol/core"; 2 | import view from "./view"; 3 | 4 | const manifest: ModManifest = { 5 | slug: "image-render", 6 | name: "View Images", 7 | custodyAddress: "furlong.eth", 8 | version: "0.0.1", 9 | logo: "https://i.imgur.com/75cFuT9.png", 10 | custodyGithubUsername: "davidfurlong", 11 | richEmbedEntrypoints: [ 12 | { 13 | if: { 14 | value: "{{embed.url}}", 15 | match: { 16 | OR: [ 17 | { equals: "{{embed.metadata.image.url}}" }, 18 | { endsWith: ".png" }, 19 | { endsWith: ".jpg" }, 20 | { endsWith: ".jpeg" }, 21 | { endsWith: ".gif" }, 22 | { endsWith: ".svg" }, 23 | { endsWith: ".webp" }, 24 | ], 25 | }, 26 | }, 27 | element: view, 28 | }, 29 | ], 30 | elements: { 31 | "#view": view, 32 | }, 33 | }; 34 | 35 | export default manifest; 36 | -------------------------------------------------------------------------------- /mods/image-render/src/view.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const view: ModElement[] = [ 4 | { 5 | type: "image", 6 | imageSrc: "{{embed.url}}", 7 | }, 8 | ]; 9 | 10 | export default view; 11 | -------------------------------------------------------------------------------- /mods/image-render/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": [ 4 | "." 5 | ], 6 | "exclude": [ 7 | "dist", 8 | "build", 9 | "node_modules" 10 | ] 11 | } -------------------------------------------------------------------------------- /mods/imgur-upload/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mods/imgur-upload 2 | 3 | ## 0.0.3 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [df98c06] 8 | - Updated dependencies [2901ee9] 9 | - Updated dependencies [2ff629a] 10 | - @mod-protocol/core@0.2.0 11 | 12 | ## 0.0.2 13 | 14 | ### Patch Changes 15 | 16 | - Updated dependencies [584ffc2] 17 | - Updated dependencies [f2141a9] 18 | - Updated dependencies [2b7c514] 19 | - Updated dependencies [f2141a9] 20 | - Updated dependencies [584ffc2] 21 | - Updated dependencies [69be77d] 22 | - Updated dependencies [63b4864] 23 | - @mod-protocol/core@0.1.0 24 | -------------------------------------------------------------------------------- /mods/imgur-upload/index.ts: -------------------------------------------------------------------------------- 1 | export { default as default } from "./src/manifest"; 2 | -------------------------------------------------------------------------------- /mods/imgur-upload/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mods/imgur-upload", 3 | "main": "./index.ts", 4 | "types": "./index.ts", 5 | "version": "0.0.3", 6 | "private": true, 7 | "dependencies": { 8 | "@mod-protocol/core": "^0.2.0" 9 | } 10 | } -------------------------------------------------------------------------------- /mods/imgur-upload/src/action.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const action: ModElement[] = [ 4 | { 5 | type: "vertical-layout", 6 | elements: [ 7 | { 8 | type: "button", 9 | label: "Choose a file", 10 | onclick: { 11 | ref: "myOpenFileAction", 12 | type: "OPENFILE", 13 | maxFiles: 1, 14 | accept: ["image/jpeg", "image/jpg", "image/png", "image/gif"], 15 | onsuccess: "#upload", 16 | onerror: "#error", 17 | oncancel: { 18 | type: "EXIT", 19 | }, 20 | }, 21 | }, 22 | ], 23 | }, 24 | ]; 25 | 26 | export default action; 27 | -------------------------------------------------------------------------------- /mods/imgur-upload/src/error.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const error: ModElement[] = [ 4 | { 5 | type: "text", 6 | label: "ERROR: {{refs.myFileUploadRequest.error.statusText}}", 7 | }, 8 | { 9 | type: "button", 10 | label: "Retry", 11 | onclick: "#action", 12 | }, 13 | ]; 14 | 15 | export default error; 16 | -------------------------------------------------------------------------------- /mods/imgur-upload/src/loading.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const loading: ModElement[] = [ 4 | { 5 | type: "text", 6 | label: "Uploading the file: {{refs.myFileUploadRequest.progress}}%", 7 | }, 8 | { 9 | type: "circular-progress", 10 | }, 11 | ]; 12 | 13 | export default loading; 14 | -------------------------------------------------------------------------------- /mods/imgur-upload/src/manifest.ts: -------------------------------------------------------------------------------- 1 | import { ModManifest } from "@mod-protocol/core"; 2 | import action from "./action"; 3 | import error from "./error"; 4 | import loading from "./loading"; 5 | import upload from "./upload"; 6 | 7 | const manifest: ModManifest = { 8 | slug: "imgur-upload", 9 | name: "Upload image to Imgur", 10 | custodyAddress: "stephancill.eth", 11 | logo: "https://imgur.com/favicon.ico", 12 | custodyGithubUsername: "stephancill", 13 | version: "0.0.1", 14 | creationEntrypoints: action, 15 | elements: { 16 | "#action": action, 17 | "#upload": upload, 18 | "#loading": loading, 19 | "#error": error, 20 | }, 21 | }; 22 | 23 | export default manifest; 24 | -------------------------------------------------------------------------------- /mods/imgur-upload/src/upload.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const upload: ModElement[] = [ 4 | { 5 | type: "vertical-layout", 6 | onload: { 7 | ref: "myFileUploadRequest", 8 | type: "POST", 9 | url: "{{api}}/imgur-upload", 10 | body: { 11 | formData: { 12 | file: { 13 | type: "blobRef", 14 | value: "refs.myOpenFileAction.files[0].blob", 15 | }, 16 | }, 17 | }, 18 | onsuccess: { 19 | type: "ADDEMBED", 20 | url: "{{refs.myFileUploadRequest.response.data.url}}", 21 | name: "{{refs.myOpenFileAction.files[0].name}}", 22 | mimeType: "{{refs.myOpenFileAction.files[0].mimeType}}", 23 | onsuccess: { 24 | type: "EXIT", 25 | }, 26 | }, 27 | onerror: "#error", 28 | onloading: "#loading", 29 | }, 30 | }, 31 | ]; 32 | 33 | export default upload; 34 | -------------------------------------------------------------------------------- /mods/imgur-upload/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /mods/infura-ipfs-upload/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mods/infura-ipfs-upload 2 | 3 | ## 0.1.1 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [df98c06] 8 | - Updated dependencies [2901ee9] 9 | - Updated dependencies [2ff629a] 10 | - @mod-protocol/core@0.2.0 11 | 12 | ## 0.1.0 13 | 14 | ### Minor Changes 15 | 16 | - 63b4864: Renames Content rendering to Rich Embeds, Renames Mini-apps to Mods, updates docs 17 | 18 | ### Patch Changes 19 | 20 | - Updated dependencies [584ffc2] 21 | - Updated dependencies [f2141a9] 22 | - Updated dependencies [2b7c514] 23 | - Updated dependencies [f2141a9] 24 | - Updated dependencies [584ffc2] 25 | - Updated dependencies [69be77d] 26 | - Updated dependencies [63b4864] 27 | - @mod-protocol/core@0.1.0 28 | 29 | ## 0.0.2 30 | 31 | ### Patch Changes 32 | 33 | - Updated dependencies [8ae8453] 34 | - Updated dependencies [15d9d7f] 35 | - Updated dependencies [4a50bdd] 36 | - @mod-protocol/core@0.0.2 37 | -------------------------------------------------------------------------------- /mods/infura-ipfs-upload/index.ts: -------------------------------------------------------------------------------- 1 | export { default as default } from "./src/manifest"; 2 | -------------------------------------------------------------------------------- /mods/infura-ipfs-upload/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mods/infura-ipfs-upload", 3 | "main": "./index.ts", 4 | "types": "./index.ts", 5 | "version": "0.1.1", 6 | "private": true, 7 | "dependencies": { 8 | "@mod-protocol/core": "^0.2.0" 9 | } 10 | } -------------------------------------------------------------------------------- /mods/infura-ipfs-upload/src/action.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const action: ModElement[] = [ 4 | { 5 | type: "vertical-layout", 6 | elements: [ 7 | { 8 | type: "button", 9 | label: "Choose a file", 10 | onclick: { 11 | ref: "myOpenFileAction", 12 | type: "OPENFILE", 13 | maxFiles: 1, 14 | accept: ["image/jpeg", "image/jpg"], 15 | onsuccess: "#upload", 16 | onerror: "#error", 17 | oncancel: { 18 | type: "EXIT", 19 | }, 20 | }, 21 | }, 22 | ], 23 | }, 24 | ]; 25 | 26 | export default action; 27 | -------------------------------------------------------------------------------- /mods/infura-ipfs-upload/src/error.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const error: ModElement[] = [ 4 | { 5 | type: "text", 6 | label: "ERROR: {{refs.myFileUploadRequest.error.statusText}}", 7 | }, 8 | { 9 | type: "button", 10 | label: "Retry", 11 | onclick: "#action", 12 | }, 13 | ]; 14 | 15 | export default error; 16 | -------------------------------------------------------------------------------- /mods/infura-ipfs-upload/src/loading.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const loading: ModElement[] = [ 4 | { 5 | type: "text", 6 | label: "Uploading the file: {{refs.myFileUploadRequest.progress}}%", 7 | }, 8 | { 9 | type: "circular-progress", 10 | }, 11 | ]; 12 | 13 | export default loading; 14 | -------------------------------------------------------------------------------- /mods/infura-ipfs-upload/src/manifest.ts: -------------------------------------------------------------------------------- 1 | import { ModManifest } from "@mod-protocol/core"; 2 | import action from "./action"; 3 | import error from "./error"; 4 | import loading from "./loading"; 5 | import upload from "./upload"; 6 | 7 | const manifest: ModManifest = { 8 | slug: "infura-ipfs-upload", 9 | name: "Add image", 10 | custodyAddress: "furlong.eth", 11 | logo: "https://i.imgur.com/FxgecX7.png", 12 | custodyGithubUsername: "davidfurlong", 13 | version: "0.0.1", 14 | creationEntrypoints: action, 15 | elements: { 16 | "#action": action, 17 | "#upload": upload, 18 | "#loading": loading, 19 | "#error": error, 20 | }, 21 | }; 22 | 23 | export default manifest; 24 | -------------------------------------------------------------------------------- /mods/infura-ipfs-upload/src/upload.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const upload: ModElement[] = [ 4 | { 5 | type: "vertical-layout", 6 | onload: { 7 | ref: "myFileUploadRequest", 8 | type: "POST", 9 | url: "{{api}}/infura-ipfs-upload", 10 | body: { 11 | formData: { 12 | file: { 13 | type: "blobRef", 14 | value: "refs.myOpenFileAction.files[0].blob", 15 | }, 16 | }, 17 | }, 18 | onsuccess: { 19 | type: "ADDEMBED", 20 | url: "{{refs.myFileUploadRequest.response.data.url}}", 21 | name: "{{refs.myOpenFileAction.files[0].name}}", 22 | mimeType: "{{refs.myOpenFileAction.files[0].mimeType}}", 23 | onsuccess: { 24 | type: "EXIT", 25 | }, 26 | }, 27 | onerror: "#error", 28 | onloading: "#loading", 29 | }, 30 | }, 31 | ]; 32 | 33 | export default upload; 34 | -------------------------------------------------------------------------------- /mods/infura-ipfs-upload/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /mods/livepeer-video/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mods/livepeer-video 2 | 3 | ## 0.1.1 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [df98c06] 8 | - Updated dependencies [2901ee9] 9 | - Updated dependencies [2ff629a] 10 | - @mod-protocol/core@0.2.0 11 | 12 | ## 0.1.0 13 | 14 | ### Minor Changes 15 | 16 | - 63b4864: Renames Content rendering to Rich Embeds, Renames Mini-apps to Mods, updates docs 17 | 18 | ### Patch Changes 19 | 20 | - Updated dependencies [584ffc2] 21 | - Updated dependencies [f2141a9] 22 | - Updated dependencies [2b7c514] 23 | - Updated dependencies [f2141a9] 24 | - Updated dependencies [584ffc2] 25 | - Updated dependencies [69be77d] 26 | - Updated dependencies [63b4864] 27 | - @mod-protocol/core@0.1.0 28 | 29 | ## 0.0.2 30 | 31 | ### Patch Changes 32 | 33 | - Updated dependencies [8ae8453] 34 | - Updated dependencies [15d9d7f] 35 | - Updated dependencies [4a50bdd] 36 | - @mod-protocol/core@0.0.2 37 | -------------------------------------------------------------------------------- /mods/livepeer-video/index.ts: -------------------------------------------------------------------------------- 1 | export { default as default } from "./src/manifest"; 2 | -------------------------------------------------------------------------------- /mods/livepeer-video/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mods/livepeer-video", 3 | "main": "./index.ts", 4 | "types": "./index.ts", 5 | "version": "0.1.1", 6 | "private": true, 7 | "dependencies": { 8 | "@mod-protocol/core": "^0.2.0" 9 | } 10 | } -------------------------------------------------------------------------------- /mods/livepeer-video/src/action.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const action: ModElement[] = [ 4 | { 5 | type: "vertical-layout", 6 | elements: [ 7 | { 8 | type: "button", 9 | label: "Choose a file", 10 | onclick: { 11 | ref: "myOpenFileAction", 12 | type: "OPENFILE", 13 | maxFiles: 1, 14 | accept: ["video/mp4"], 15 | onsuccess: "#upload", 16 | onerror: "#error", 17 | oncancel: { 18 | type: "EXIT", 19 | }, 20 | }, 21 | }, 22 | ], 23 | }, 24 | ]; 25 | 26 | export default action; 27 | -------------------------------------------------------------------------------- /mods/livepeer-video/src/error.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const error: ModElement[] = [ 4 | { 5 | type: "text", 6 | label: "ERROR: {{refs.myFileUploadRequest.error.statusText}}", 7 | }, 8 | { 9 | type: "button", 10 | label: "Retry", 11 | onclick: "#action", 12 | }, 13 | ]; 14 | 15 | export default error; 16 | -------------------------------------------------------------------------------- /mods/livepeer-video/src/loading.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const loading: ModElement[] = [ 4 | { 5 | type: "text", 6 | label: "Uploading the file: {{refs.myFileUploadRequest.progress}}%", 7 | }, 8 | { 9 | type: "circular-progress", 10 | }, 11 | ]; 12 | 13 | export default loading; 14 | -------------------------------------------------------------------------------- /mods/livepeer-video/src/manifest.ts: -------------------------------------------------------------------------------- 1 | import { ModManifest } from "@mod-protocol/core"; 2 | import action from "./action"; 3 | import error from "./error"; 4 | import loading from "./loading"; 5 | import upload from "./upload"; 6 | 7 | const manifest: ModManifest = { 8 | slug: "livepeer-video", 9 | name: "Add video", 10 | custodyAddress: "furlong.eth", 11 | logo: "https://i.imgur.com/89epIn5.png", 12 | custodyGithubUsername: "davidfurlong", 13 | version: "0.0.1", 14 | creationEntrypoints: action, 15 | elements: { 16 | "#action": action, 17 | "#upload": upload, 18 | "#loading": loading, 19 | "#error": error, 20 | }, 21 | }; 22 | 23 | export default manifest; 24 | -------------------------------------------------------------------------------- /mods/livepeer-video/src/upload.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const upload: ModElement[] = [ 4 | { 5 | type: "vertical-layout", 6 | onload: { 7 | ref: "myFileUploadRequest", 8 | type: "POST", 9 | url: "{{api}}/livepeer-video", 10 | body: { 11 | formData: { 12 | file: { 13 | type: "blobRef", 14 | value: "refs.myOpenFileAction.files[0].blob", 15 | }, 16 | }, 17 | }, 18 | onsuccess: { 19 | type: "ADDEMBED", 20 | url: "https://lp-playback.com/hls/{{refs.myFileUploadRequest.response.data.data.asset.playbackId}}/index.m3u8", 21 | name: "{{refs.myOpenFileAction.files[0].name}}", 22 | mimeType: "{{refs.myOpenFileAction.files[0].mimeType}}", 23 | onsuccess: { 24 | type: "EXIT", 25 | }, 26 | }, 27 | onerror: "#error", 28 | onloading: "#loading", 29 | }, 30 | }, 31 | ]; 32 | 33 | export default upload; 34 | -------------------------------------------------------------------------------- /mods/livepeer-video/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /mods/nft-minter/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mods/nft-minter 2 | 3 | ## 0.1.1 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [df98c06] 8 | - Updated dependencies [2901ee9] 9 | - Updated dependencies [2ff629a] 10 | - @mod-protocol/core@0.2.0 11 | 12 | ## 0.1.0 13 | 14 | ### Minor Changes 15 | 16 | - 63b4864: Renames Content rendering to Rich Embeds, Renames Mini-apps to Mods, updates docs 17 | 18 | ### Patch Changes 19 | 20 | - 2b7c514: Adds the combobox, select, textarea mod elements. Adds `Model Definitions` to Manifests. Adds the `ethPersonalSign` action. Adds `json-ld` indexing to metadata. Adds a `loadingLabel` prop to buttons 21 | - 69be77d: fix: support fallback mint URL 22 | - 1c90d92: fix: render embed image source instead of nft token image source 23 | - Updated dependencies [584ffc2] 24 | - Updated dependencies [f2141a9] 25 | - Updated dependencies [2b7c514] 26 | - Updated dependencies [f2141a9] 27 | - Updated dependencies [584ffc2] 28 | - Updated dependencies [69be77d] 29 | - Updated dependencies [63b4864] 30 | - @mod-protocol/core@0.1.0 31 | 32 | ## 0.0.2 33 | 34 | ### Patch Changes 35 | 36 | - 8ae8453: chore: normalize NFT metadata type 37 | - Updated dependencies [8ae8453] 38 | - Updated dependencies [15d9d7f] 39 | - Updated dependencies [4a50bdd] 40 | - @mod-protocol/core@0.0.2 41 | -------------------------------------------------------------------------------- /mods/nft-minter/index.ts: -------------------------------------------------------------------------------- 1 | export { default as default } from "./src/manifest"; 2 | -------------------------------------------------------------------------------- /mods/nft-minter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mods/nft-minter", 3 | "main": "./index.ts", 4 | "types": "./index.ts", 5 | "version": "0.1.1", 6 | "private": true, 7 | "dependencies": { 8 | "@mod-protocol/core": "^0.2.0" 9 | } 10 | } -------------------------------------------------------------------------------- /mods/nft-minter/src/manifest.ts: -------------------------------------------------------------------------------- 1 | import { ModManifest } from "@mod-protocol/core"; 2 | import view from "./view"; 3 | 4 | const manifest: ModManifest = { 5 | slug: "nft-minter", 6 | name: "Preview and mint NFTs", 7 | custodyAddress: "furlong.eth", 8 | version: "0.0.1", 9 | logo: "https://i.imgur.com/fuSkgoJ.png", 10 | custodyGithubUsername: "davidfurlong", 11 | richEmbedEntrypoints: [ 12 | { 13 | if: { 14 | value: "{{embed.metadata.nft.collection.name}}", 15 | match: { 16 | NOT: { 17 | equals: "", 18 | }, 19 | }, 20 | }, 21 | element: view, 22 | }, 23 | ], 24 | elements: { 25 | "#view": view, 26 | }, 27 | }; 28 | 29 | export default manifest; 30 | -------------------------------------------------------------------------------- /mods/nft-minter/src/view.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const view: ModElement[] = [ 4 | { 5 | type: "card", 6 | imageSrc: "{{embed.metadata.image.url}}", 7 | aspectRatio: 16 / 11, 8 | // fixme: may be undefined, in that case dont render. 9 | topLeftBadge: "{{embed.metadata.nft.collection.creator.username}}", 10 | onclick: { 11 | type: "OPENLINK", 12 | url: "{{embed.metadata.nft.collection.openSeaUrl}}", 13 | onsuccess: "#view", 14 | }, 15 | elements: [ 16 | { 17 | type: "horizontal-layout", 18 | elements: [ 19 | { 20 | type: "avatar", 21 | src: "{{api}}/nft-chain-logo?chain={{embed.metadata.nft.collection.chain}}", 22 | }, 23 | { 24 | type: "text", 25 | label: "{{embed.metadata.nft.collection.name}}", 26 | }, 27 | { 28 | if: { 29 | value: "{{embed.metadata.nft.mintUrl}}", 30 | match: { 31 | NOT: { 32 | equals: "", 33 | }, 34 | }, 35 | }, 36 | then: { 37 | type: "link", 38 | label: "Mint", 39 | url: "{{embed.metadata.nft.mintUrl}}", 40 | }, 41 | else: { 42 | type: "link", 43 | label: "View collection", 44 | url: "{{embed.metadata.nft.collection.openSeaUrl}}", 45 | }, 46 | }, 47 | ], 48 | }, 49 | ], 50 | }, 51 | ]; 52 | 53 | export default view; 54 | -------------------------------------------------------------------------------- /mods/nft-minter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /mods/url-render/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mods/url-render 2 | 3 | ## 0.1.1 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [df98c06] 8 | - Updated dependencies [2901ee9] 9 | - Updated dependencies [2ff629a] 10 | - @mod-protocol/core@0.2.0 11 | 12 | ## 0.1.0 13 | 14 | ### Minor Changes 15 | 16 | - 63b4864: Renames Content rendering to Rich Embeds, Renames Mini-apps to Mods, updates docs 17 | 18 | ### Patch Changes 19 | 20 | - f2141a9: feat: text component variants: `bold`, `secondary`, `regular` 21 | - Updated dependencies [584ffc2] 22 | - Updated dependencies [f2141a9] 23 | - Updated dependencies [2b7c514] 24 | - Updated dependencies [f2141a9] 25 | - Updated dependencies [584ffc2] 26 | - Updated dependencies [69be77d] 27 | - Updated dependencies [63b4864] 28 | - @mod-protocol/core@0.1.0 29 | 30 | ## 0.0.2 31 | 32 | ### Patch Changes 33 | 34 | - Updated dependencies [8ae8453] 35 | - Updated dependencies [15d9d7f] 36 | - Updated dependencies [4a50bdd] 37 | - @mod-protocol/core@0.0.2 38 | -------------------------------------------------------------------------------- /mods/url-render/index.ts: -------------------------------------------------------------------------------- 1 | export { default as default } from "./src/manifest"; 2 | -------------------------------------------------------------------------------- /mods/url-render/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mods/url-render", 3 | "main": "./index.ts", 4 | "types": "./index.ts", 5 | "version": "0.1.1", 6 | "private": true, 7 | "dependencies": { 8 | "@mod-protocol/core": "^0.2.0" 9 | } 10 | } -------------------------------------------------------------------------------- /mods/url-render/src/fullimage.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const fullimage: ModElement[] = [ 4 | { 5 | type: "card", 6 | aspectRatio: 16 / 8, 7 | imageSrc: "{{embed.metadata.image.url}}", 8 | onclick: { 9 | type: "OPENLINK", 10 | url: "{{embed.url}}", 11 | onsuccess: "#fullimage", 12 | }, 13 | elements: [ 14 | { 15 | type: "text", 16 | label: "{{embed.metadata.publisher}}", 17 | variant: "secondary", 18 | }, 19 | { 20 | type: "text", 21 | label: "{{embed.metadata.title}}", 22 | variant: "regular", 23 | }, 24 | { 25 | type: "text", 26 | label: "{{embed.metadata.description}}", 27 | variant: "secondary", 28 | }, 29 | ], 30 | }, 31 | ]; 32 | 33 | export default fullimage; 34 | -------------------------------------------------------------------------------- /mods/url-render/src/manifest.ts: -------------------------------------------------------------------------------- 1 | import { ModManifest } from "@mod-protocol/core"; 2 | import fullimage from "./fullimage"; 3 | // import smallimage from "./smallimage"; 4 | 5 | const manifest: ModManifest = { 6 | slug: "url-render", 7 | name: "View urls", 8 | custodyAddress: "furlong.eth", 9 | version: "0.0.1", 10 | logo: "https://i.imgur.com/E7PAMHH.png", 11 | custodyGithubUsername: "davidfurlong", 12 | richEmbedEntrypoints: [ 13 | { 14 | if: { 15 | value: "true", 16 | match: { 17 | equals: "true", 18 | }, 19 | }, 20 | element: fullimage, 21 | }, 22 | ], 23 | elements: { 24 | "#fullimage": fullimage, 25 | }, 26 | }; 27 | 28 | export default manifest; 29 | -------------------------------------------------------------------------------- /mods/url-render/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": [ 4 | "." 5 | ], 6 | "exclude": [ 7 | "dist", 8 | "build", 9 | "node_modules" 10 | ] 11 | } -------------------------------------------------------------------------------- /mods/video-render/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mods/video-render 2 | 3 | ## 0.1.1 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [df98c06] 8 | - Updated dependencies [2901ee9] 9 | - Updated dependencies [2ff629a] 10 | - @mod-protocol/core@0.2.0 11 | 12 | ## 0.1.0 13 | 14 | ### Minor Changes 15 | 16 | - 63b4864: Renames Content rendering to Rich Embeds, Renames Mini-apps to Mods, updates docs 17 | 18 | ### Patch Changes 19 | 20 | - Updated dependencies [584ffc2] 21 | - Updated dependencies [f2141a9] 22 | - Updated dependencies [2b7c514] 23 | - Updated dependencies [f2141a9] 24 | - Updated dependencies [584ffc2] 25 | - Updated dependencies [69be77d] 26 | - Updated dependencies [63b4864] 27 | - @mod-protocol/core@0.1.0 28 | 29 | ## 0.0.2 30 | 31 | ### Patch Changes 32 | 33 | - Updated dependencies [8ae8453] 34 | - Updated dependencies [15d9d7f] 35 | - Updated dependencies [4a50bdd] 36 | - @mod-protocol/core@0.0.2 37 | -------------------------------------------------------------------------------- /mods/video-render/index.ts: -------------------------------------------------------------------------------- 1 | export { default as default } from "./src/manifest"; 2 | -------------------------------------------------------------------------------- /mods/video-render/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mods/video-render", 3 | "main": "./index.ts", 4 | "types": "./index.ts", 5 | "version": "0.1.1", 6 | "private": true, 7 | "dependencies": { 8 | "@mod-protocol/core": "^0.2.0" 9 | } 10 | } -------------------------------------------------------------------------------- /mods/video-render/src/manifest.ts: -------------------------------------------------------------------------------- 1 | import { ModManifest } from "@mod-protocol/core"; 2 | import view from "./view"; 3 | 4 | const manifest: ModManifest = { 5 | slug: "video-render", 6 | name: "View Videos", 7 | custodyAddress: "furlong.eth", 8 | version: "0.0.1", 9 | logo: "https://i.imgur.com/RITeKTW.png", 10 | custodyGithubUsername: "davidfurlong", 11 | richEmbedEntrypoints: [ 12 | { 13 | if: { 14 | value: "{{embed.url}}", 15 | match: { 16 | regex: "^https?://.*.(?:mp4|webm|avi|mov|m3u8)$", 17 | }, 18 | }, 19 | element: view, 20 | }, 21 | ], 22 | elements: { 23 | "#view": view, 24 | }, 25 | }; 26 | 27 | export default manifest; 28 | -------------------------------------------------------------------------------- /mods/video-render/src/view.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const view: ModElement[] = [ 4 | { 5 | type: "video", 6 | videoSrc: "{{embed.url}}", 7 | }, 8 | ]; 9 | 10 | export default view; 11 | -------------------------------------------------------------------------------- /mods/video-render/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": [ 4 | "." 5 | ], 6 | "exclude": [ 7 | "dist", 8 | "build", 9 | "node_modules" 10 | ] 11 | } -------------------------------------------------------------------------------- /mods/zora-create/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mods/zora-create 2 | 3 | ## 0.0.3 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [df98c06] 8 | - Updated dependencies [2901ee9] 9 | - Updated dependencies [2ff629a] 10 | - @mod-protocol/core@0.2.0 11 | 12 | ## 0.0.2 13 | 14 | ### Patch Changes 15 | 16 | - 1b019e8: miniapp: zora create 17 | - Updated dependencies [1b019e8] 18 | - Updated dependencies [7053840] 19 | - @mod-protocol/core@0.1.2 20 | -------------------------------------------------------------------------------- /mods/zora-create/index.ts: -------------------------------------------------------------------------------- 1 | export { default as default } from "./src/manifest"; 2 | -------------------------------------------------------------------------------- /mods/zora-create/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mods/zora-create", 3 | "main": "./index.ts", 4 | "types": "./index.ts", 5 | "private": true, 6 | "version": "0.0.3", 7 | "dependencies": { 8 | "@mod-protocol/core": "^0.2.0" 9 | } 10 | } -------------------------------------------------------------------------------- /mods/zora-create/src/error.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const error: ModElement[] = [ 4 | { 5 | type: "text", 6 | label: "ERROR: {{refs.myZoraCreateRequest.error.statusText}}", 7 | }, 8 | { 9 | type: "button", 10 | label: "Retry", 11 | onclick: "#action", 12 | }, 13 | ]; 14 | 15 | export default error; 16 | -------------------------------------------------------------------------------- /mods/zora-create/src/loading.ts: -------------------------------------------------------------------------------- 1 | import { ModElement } from "@mod-protocol/core"; 2 | 3 | const loading: ModElement[] = [ 4 | { 5 | type: "text", 6 | label: "Uploading the file: {{refs.myZoraCreateRequest.progress}}%", 7 | }, 8 | { 9 | type: "circular-progress", 10 | }, 11 | ]; 12 | 13 | export default loading; 14 | -------------------------------------------------------------------------------- /mods/zora-create/src/manifest.ts: -------------------------------------------------------------------------------- 1 | import { ModManifest } from "@mod-protocol/core"; 2 | import action from "./action"; 3 | import error from "./error"; 4 | import loading from "./loading"; 5 | 6 | const manifest: ModManifest = { 7 | slug: "zora-create", 8 | name: "Add NFT via Zora Premint", 9 | custodyAddress: "stephancill.eth", 10 | logo: "https://i.imgur.com/rsfLOfD.png", 11 | custodyGithubUsername: "stephancill", 12 | version: "0.0.1", 13 | permissions: ["user.wallet.address"], 14 | creationEntrypoints: action, 15 | elements: { 16 | "#error": error, 17 | "#loading": loading, 18 | }, 19 | }; 20 | 21 | export default manifest; 22 | -------------------------------------------------------------------------------- /mods/zora-create/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /mods/zora-nft-minter/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mods/zora-nft-minter 2 | 3 | ## 0.1.1 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [df98c06] 8 | - Updated dependencies [2901ee9] 9 | - Updated dependencies [2ff629a] 10 | - @mod-protocol/core@0.2.0 11 | 12 | ## 0.1.0 13 | 14 | ### Minor Changes 15 | 16 | - 63b4864: Renames Content rendering to Rich Embeds, Renames Mini-apps to Mods, updates docs 17 | 18 | ### Patch Changes 19 | 20 | - 2b7c514: Adds the combobox, select, textarea mod elements. Adds `Model Definitions` to Manifests. Adds the `ethPersonalSign` action. Adds `json-ld` indexing to metadata. Adds a `loadingLabel` prop to buttons 21 | - 69be77d: fix: support fallback mint URL 22 | - 584ffc2: feat: add `SENDETHTRANSACTION` action 23 | - Updated dependencies [584ffc2] 24 | - Updated dependencies [f2141a9] 25 | - Updated dependencies [2b7c514] 26 | - Updated dependencies [f2141a9] 27 | - Updated dependencies [584ffc2] 28 | - Updated dependencies [69be77d] 29 | - Updated dependencies [63b4864] 30 | - @mod-protocol/core@0.1.0 31 | -------------------------------------------------------------------------------- /mods/zora-nft-minter/index.ts: -------------------------------------------------------------------------------- 1 | export { default as default } from "./src/manifest"; 2 | -------------------------------------------------------------------------------- /mods/zora-nft-minter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mods/zora-nft-minter", 3 | "main": "./index.ts", 4 | "types": "./index.ts", 5 | "version": "0.1.1", 6 | "private": true, 7 | "dependencies": { 8 | "@mod-protocol/core": "^0.2.0" 9 | } 10 | } -------------------------------------------------------------------------------- /mods/zora-nft-minter/src/manifest.ts: -------------------------------------------------------------------------------- 1 | import { ModManifest } from "@mod-protocol/core"; 2 | import view from "./view"; 3 | 4 | const manifest: ModManifest = { 5 | slug: "zora-nft-minter", 6 | name: "Preview and mint Zora NFTs", 7 | custodyAddress: "stephancill.eth", 8 | version: "0.0.1", 9 | logo: "https://i.imgur.com/fuSkgoJ.png", 10 | custodyGithubUsername: "stephancill", 11 | permissions: ["user.wallet.address"], 12 | richEmbedEntrypoints: [ 13 | { 14 | if: { 15 | value: "{{embed.url}}", 16 | match: { 17 | startsWith: "https://zora.co/collect", 18 | }, 19 | }, 20 | element: view, 21 | }, 22 | ], 23 | elements: { 24 | "#view": view, 25 | }, 26 | }; 27 | 28 | export default manifest; 29 | -------------------------------------------------------------------------------- /mods/zora-nft-minter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "build": "turbo run build", 5 | "dev": "turbo run dev --concurrency 40", 6 | "test": "echo \"no tests yet\"", 7 | "lint": "turbo run lint", 8 | "format": "prettier --write \"**/*.{ts,tsx,md}\"", 9 | "publish-packages": "turbo run build lint && changeset version && changeset publish && git push --follow-tags origin main", 10 | "postinstall": "patch-package" 11 | }, 12 | "devDependencies": { 13 | "@changesets/cli": "2.26.2", 14 | "@turbo/gen": "^1.9.7", 15 | "eslint": "^8.46.0", 16 | "eslint-config-custom": "*", 17 | "prettier": "^2.5.1", 18 | "react": "^18.2.0", 19 | "tsup": "^7.2.0", 20 | "turbo": "^1.10.12" 21 | }, 22 | "name": "mod", 23 | "license": "MIT", 24 | "packageManager": "yarn@1.22.19", 25 | "workspaces": [ 26 | "configs/*", 27 | "examples/*", 28 | "mods/*", 29 | "packages/*", 30 | "docs" 31 | ], 32 | "dependencies": { 33 | "patch-package": "^8.0.0", 34 | "postinstall-postinstall": "^2.1.0" 35 | } 36 | } -------------------------------------------------------------------------------- /packages/core/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | // This tells ESLint to load the config from the package `eslint-config-custom` 4 | extends: ["custom"], 5 | }; 6 | -------------------------------------------------------------------------------- /packages/core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mod-protocol/core 2 | 3 | ## 0.2.1 4 | 5 | ### Patch Changes 6 | 7 | - 3a7078a: fix: multi-step frames 8 | 9 | ## 0.2.0 10 | 11 | ### Minor Changes 12 | 13 | - 2901ee9: Adds Farcaster Frames 14 | - 2ff629a: Add Farcaster frames support, including a new action Mods can call, ADDFCFRAMEACTION 15 | 16 | ### Patch Changes 17 | 18 | - df98c06: add farcaster frames indexing 19 | - Updated dependencies [2901ee9] 20 | - Updated dependencies [2ff629a] 21 | - @mod-protocol/farcaster@0.2.0 22 | 23 | ## 0.1.2 24 | 25 | ### Patch Changes 26 | 27 | - 1b019e8: feat: add base64 encoded data to file selector action return value 28 | - 7053840: fix: onload events 29 | 30 | ## 0.1.1 31 | 32 | ### Patch Changes 33 | 34 | - Updated dependencies [cbfe215] 35 | - Updated dependencies [6ae4441] 36 | - @mod-protocol/farcaster@0.1.0 37 | 38 | ## 0.1.0 39 | 40 | ### Minor Changes 41 | 42 | - 584ffc2: feat(core): mod permissions 43 | - 2b7c514: Adds the combobox, select, textarea mod elements. Adds `Model Definitions` to Manifests. Adds the `ethPersonalSign` action. Adds `json-ld` indexing to metadata. Adds a `loadingLabel` prop to buttons 44 | - f2141a9: chore: users mentioned in NFT metadata can either be an FID or FarcasterUser object depending on context 45 | - 584ffc2: feat: add `SENDETHTRANSACTION` action 46 | - 69be77d: fix: optional mint URL opengraph response, mint URL for ERC-1155 tokens 47 | - 63b4864: Renames Content rendering to Rich Embeds, Renames Mini-apps to Mods, updates docs 48 | 49 | ### Patch Changes 50 | 51 | - f2141a9: feat: text component variants: `bold`, `secondary`, `regular` 52 | 53 | ## 0.0.2 54 | 55 | ### Patch Changes 56 | 57 | - 8ae8453: chore: normalize NFT metadata type 58 | - 15d9d7f: add variant property to buttons 59 | - 4a50bdd: feat: npm readiness 60 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # @mod-protocol/core 2 | 3 | This package of [Mod Protocol](https://github.com/mod-protocol/mod) is responsible for the core rendering engine of the Mod DSL 4 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mod-protocol/core", 3 | "version": "0.2.1", 4 | "main": "./dist/index.js", 5 | "module": "./dist/index.mjs", 6 | "types": "./dist/index.d.ts", 7 | "scripts": { 8 | "lint": "eslint \"**/*.ts*\"", 9 | "build": "tsup src/index.ts --format cjs,esm --dts", 10 | "dev": "npm run build -- --watch" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/mod-protocol/mod/tree/main/packages/core" 15 | }, 16 | "devDependencies": { 17 | "@types/lodash.get": "^4.4.7", 18 | "@types/lodash.isarray": "^4.0.7", 19 | "@types/lodash.isstring": "^4.0.7", 20 | "@types/lodash.mapvalues": "^4.6.7", 21 | "@types/lodash.set": "^4.3.7", 22 | "@types/lodash.tonumber": "^4.0.7", 23 | "@types/lodash.tostring": "^4.1.7", 24 | "@types/react": "^18.2.0", 25 | "@types/react-dom": "^18.2.0", 26 | "eslint": "^7.32.0", 27 | "eslint-config-custom": "*", 28 | "tsconfig": "*", 29 | "typescript": "^5.2.2" 30 | }, 31 | "dependencies": { 32 | "@mod-protocol/farcaster": "^0.2.0", 33 | "json-schema": "^0.4.0", 34 | "lodash.get": "^4.4.2", 35 | "lodash.isarray": "^4.0.0", 36 | "lodash.isstring": "^4.0.1", 37 | "lodash.mapvalues": "^4.6.0", 38 | "lodash.set": "^4.3.2", 39 | "lodash.tonumber": "^4.0.3", 40 | "lodash.tostring": "^4.1.4" 41 | } 42 | } -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | ModElement, 3 | ModAction, 4 | ModManifest, 5 | ModEvent, 6 | ModConditionalElement, 7 | } from "./manifest"; 8 | export type { 9 | ModElementRef, 10 | HttpActionResolver, 11 | HttpActionResolverInit, 12 | HttpActionResolverEvents, 13 | OpenFileActionResolver, 14 | OpenFileActionResolverInit, 15 | OpenFileActionResolverEvents, 16 | SetInputActionResolver, 17 | SetInputActionResolverInit, 18 | SetInputActionResolverEvents, 19 | AddEmbedActionResolver, 20 | AddEmbedActionResolverInit, 21 | AddEmbedActionResolverEvents, 22 | OpenLinkActionResolver, 23 | OpenLinkActionResolverInit, 24 | OpenLinkActionResolverEvents, 25 | EthPersonalSignActionResolver, 26 | EthPersonalSignActionResolverEvents, 27 | EthPersonalSignActionResolverInit, 28 | SendEthTransactionActionResolverInit, 29 | SendEthTransactionActionResolverEvents, 30 | SendEthTransactionActionResolver, 31 | SendFcFrameActionResolverInit, 32 | SendFcFrameActionResolverEvents, 33 | SendFcFrameActionResolver, 34 | ExitActionResolver, 35 | ContextType, 36 | RichEmbedContext, 37 | CreationContext, 38 | } from "./renderer"; 39 | export { Renderer, canRenderEntrypointWithContext } from "./renderer"; 40 | export * from "./embeds"; 41 | export * from "./web-handlers"; 42 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/farcaster/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mod-protocol/farcaster 2 | 3 | ## 0.2.0 4 | 5 | ### Minor Changes 6 | 7 | - 2901ee9: Adds Farcaster Frames 8 | - 2ff629a: Add Farcaster frames support, including a new action Mods can call, ADDFCFRAMEACTION 9 | 10 | ## 0.1.0 11 | 12 | ### Minor Changes 13 | 14 | - cbfe215: add channel mentions 15 | 16 | ### Patch Changes 17 | 18 | - 6ae4441: fix: fetch channels list from neynar API 19 | -------------------------------------------------------------------------------- /packages/farcaster/README.md: -------------------------------------------------------------------------------- 1 | # @mod-protocol/farcaster 2 | 3 | This package of [Mod Protocol](https://github.com/mod-protocol/mod) is responsible for Farcaster protocol specific helpers and types 4 | -------------------------------------------------------------------------------- /packages/farcaster/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mod-protocol/farcaster", 3 | "version": "0.2.0", 4 | "main": "./dist/index.js", 5 | "module": "./dist/index.mjs", 6 | "types": "./dist/index.d.ts", 7 | "scripts": { 8 | "lint": "eslint \"**/*.ts*\" && tsc --noEmit", 9 | "build": "tsup src/index.ts --format cjs,esm --dts", 10 | "dev": "npm run build -- --watch" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/mod-protocol/mod/tree/main/packages/farcaster" 15 | }, 16 | "devDependencies": { 17 | "eslint": "^7.32.0", 18 | "eslint-config-custom": "*", 19 | "tsconfig": "*", 20 | "typescript": "^5.2.2" 21 | }, 22 | "dependencies": {} 23 | } -------------------------------------------------------------------------------- /packages/farcaster/src/api-helpers.ts: -------------------------------------------------------------------------------- 1 | import { Channel } from "./channels"; 2 | import { FarcasterMention } from "./mentions"; 3 | 4 | export function getFarcasterMentions(api_url: string) { 5 | return async (query: string): Promise => { 6 | const req = await fetch( 7 | `${api_url}/farcaster/mentions?q=${encodeURIComponent(query)}` 8 | ); 9 | 10 | const reqJson = await req.json(); 11 | 12 | return reqJson.data; 13 | }; 14 | } 15 | 16 | export function getFarcasterChannels(api_url: string) { 17 | return async ( 18 | query: string, 19 | hideVirtualChannels?: boolean 20 | ): Promise => { 21 | const results = await fetch( 22 | `${api_url}/farcaster/channels/v2?q=${encodeURIComponent( 23 | query 24 | )}&hideVirtualChannels=${hideVirtualChannels}` 25 | ); 26 | 27 | const body = await results.json(); 28 | 29 | return body.channels; 30 | }; 31 | } 32 | 33 | export function getMentionFidsByUsernames(api_url: string) { 34 | return async (usernames: string[]): Promise => { 35 | const reqs = await Promise.all( 36 | usernames.map(async (username): Promise => { 37 | const req = await fetch( 38 | `${api_url}/farcaster/user-by-username?username=${encodeURIComponent( 39 | username 40 | )}` 41 | ); 42 | 43 | const reqJson = await req.json(); 44 | 45 | return reqJson.data; 46 | }) 47 | ); 48 | 49 | return reqs; 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /packages/farcaster/src/channels.ts: -------------------------------------------------------------------------------- 1 | // old, deprecated 2 | export type Channelv1 = { 3 | // null represents the default, "Home" channel 4 | channel_id: string | null; 5 | parent_url: string | null; 6 | name: string; 7 | image: string | null; 8 | description?: string | null; 9 | }; 10 | 11 | export type Channel = VirtualChannel | RealizedChannel; 12 | 13 | export type VirtualChannel = { 14 | id: string; 15 | description: string; 16 | name: string; 17 | object: "channel"; 18 | parent_url: string | null; 19 | image_url: string; 20 | channel_id: string; 21 | }; 22 | 23 | export const homeVirtualChannel: VirtualChannel = { 24 | id: "", 25 | description: "followers", 26 | name: "Home", 27 | object: "channel", 28 | parent_url: null, 29 | image_url: "https://warpcast.com/~/channel-images/home.png", 30 | channel_id: "home", 31 | } as const; 32 | 33 | // type is copied from Neynar response type 34 | export type RealizedChannel = { 35 | id: string; 36 | url: string; 37 | name: string; 38 | description: string; 39 | object: "channel"; 40 | image_url: string; 41 | created_at: number; 42 | parent_url: string; 43 | lead: { 44 | object: "user"; 45 | fid: number; 46 | username: string; 47 | display_name: string; 48 | pfp_url: string; 49 | profile: { 50 | bio: { 51 | text: string; 52 | }; 53 | }; 54 | follower_count: number; 55 | following_count: number; 56 | verifications: string[]; 57 | active_status: "active" | "inactive"; 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /packages/farcaster/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./structure-cast"; 2 | export * from "./format-cast-for-hub"; 3 | export * from "./channels"; 4 | export * from "./types"; 5 | export * from "./api-helpers"; 6 | // export * from "./regexps"; 7 | export * from "./mentions"; 8 | -------------------------------------------------------------------------------- /packages/farcaster/src/mentions.ts: -------------------------------------------------------------------------------- 1 | export type FarcasterMention = { 2 | fid: number; 3 | display_name: string; 4 | username: string; 5 | avatar_url: string; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/farcaster/src/regexps.ts: -------------------------------------------------------------------------------- 1 | /** https://github.com/farcasterxyz/protocol/discussions/90 **/ 2 | export const ENS_L1 = /^[a-z0-9][a-z0-9-]{0,15}$/; 3 | export const ENS_FNAME = /^[a-z0-9][a-z0-9-]{0,15}$/; 4 | -------------------------------------------------------------------------------- /packages/farcaster/src/types.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/farcasterxyz/protocol/blob/main/docs/SPECIFICATION.md#14-casts 2 | export type FarcasterUrlEmbed = { url: string }; 3 | export type FarcasterCastIdEmbed = { 4 | castId: { fid: number; hash: Uint8Array }; 5 | }; 6 | export type FarcasterEmbed = FarcasterCastIdEmbed | FarcasterUrlEmbed; 7 | 8 | export function isFarcasterUrlEmbed( 9 | embed: FarcasterEmbed 10 | ): embed is FarcasterUrlEmbed { 11 | return embed.hasOwnProperty("url"); 12 | } 13 | 14 | export function isFarcasterCastIdEmbed( 15 | embed: FarcasterEmbed 16 | ): embed is FarcasterCastIdEmbed { 17 | return embed.hasOwnProperty("castId"); 18 | } 19 | 20 | export const FARCASTER_MAX_EMBEDS = 2; 21 | -------------------------------------------------------------------------------- /packages/farcaster/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/mod-registry/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mod-protocol/mod-registry 2 | 3 | ## 0.2.2 4 | 5 | ### Patch Changes 6 | 7 | - Fix aspect ratio of frame mod 8 | 9 | ## 0.2.1 10 | 11 | ### Patch Changes 12 | 13 | - Fix aspect ratio 14 | 15 | ## 0.2.0 16 | 17 | ### Minor Changes 18 | 19 | - 2901ee9: Adds Farcaster Frames 20 | - 2ff629a: Add Farcaster frames support, including a new action Mods can call, ADDFCFRAMEACTION 21 | 22 | ### Patch Changes 23 | 24 | - Updated dependencies [df98c06] 25 | - Updated dependencies [2901ee9] 26 | - Updated dependencies [2ff629a] 27 | - @mod-protocol/core@0.2.0 28 | 29 | ## 0.1.1 30 | 31 | ### Patch Changes 32 | 33 | - 1b019e8: miniapp: zora create 34 | - Updated dependencies [1b019e8] 35 | - Updated dependencies [7053840] 36 | - @mod-protocol/core@0.1.2 37 | 38 | ## 0.1.0 39 | 40 | ### Minor Changes 41 | 42 | - 63b4864: Renames Content rendering to Rich Embeds, Renames Mini-apps to Mods, updates docs 43 | 44 | ### Patch Changes 45 | 46 | - 584ffc2: feat: add `SENDETHTRANSACTION` action 47 | - Updated dependencies [584ffc2] 48 | - Updated dependencies [f2141a9] 49 | - Updated dependencies [2b7c514] 50 | - Updated dependencies [f2141a9] 51 | - Updated dependencies [584ffc2] 52 | - Updated dependencies [69be77d] 53 | - Updated dependencies [63b4864] 54 | - @mod-protocol/core@0.1.0 55 | 56 | ## 0.0.2 57 | 58 | ### Patch Changes 59 | 60 | - 4a50bdd: feat: npm readiness 61 | - Updated dependencies [8ae8453] 62 | - Updated dependencies [15d9d7f] 63 | - Updated dependencies [4a50bdd] 64 | - @mod-protocol/core@0.0.2 65 | -------------------------------------------------------------------------------- /packages/mod-registry/README.md: -------------------------------------------------------------------------------- 1 | # @mod-protocol/mod-registry 2 | 3 | This package of [Mod Protocol](https://github.com/mod-protocol/mod) is responsible for exporting all reviewed Mods in the registries 4 | -------------------------------------------------------------------------------- /packages/mod-registry/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mod-protocol/mod-registry", 3 | "version": "0.2.2", 4 | "main": "./dist/index.js", 5 | "module": "./dist/index.mjs", 6 | "types": "./dist/index.d.ts", 7 | "scripts": { 8 | "lint": "eslint \"**/*.ts*\" && tsc --noEmit", 9 | "build": "tsup src/index.ts --format cjs,esm --dts", 10 | "dev": "npm run build -- --watch ../../mods" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/mod-protocol/mod/tree/main/packages/mod-registry" 15 | }, 16 | "dependencies": { 17 | "@mod-protocol/core": "*" 18 | }, 19 | "devDependencies": { 20 | "eslint": "^7.32.0", 21 | "eslint-config-custom": "*", 22 | "tsconfig": "*", 23 | "typescript": "^5.2.2" 24 | } 25 | } -------------------------------------------------------------------------------- /packages/mod-registry/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": [ 4 | "." 5 | ], 6 | "exclude": [ 7 | "dist", 8 | "build", 9 | "node_modules" 10 | ], 11 | "compilerOptions": { 12 | "baseUrl": ".", 13 | "paths": { 14 | "@mods/*": [ 15 | "../../mods/*" 16 | ] 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /packages/react-editor/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | // This tells ESLint to load the config from the package `eslint-config-custom` 4 | extends: ["custom"], 5 | }; 6 | -------------------------------------------------------------------------------- /packages/react-editor/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @mod-protocol/react-editor 2 | 3 | ## 0.1.0 4 | 5 | ### Minor Changes 6 | 7 | - cbfe215: add channel mentions 8 | 9 | ### Patch Changes 10 | 11 | - 7f7d4c9: fix: editor shouldn't take mentions into account when calculating remaining characters in cast text 12 | - Updated dependencies [cbfe215] 13 | - Updated dependencies [6ae4441] 14 | - @mod-protocol/farcaster@0.1.0 15 | - @mod-protocol/core@0.1.1 16 | 17 | ## 0.0.3 18 | 19 | ### Patch Changes 20 | 21 | - 9d4d7e1: feat: expose tiptap editor config 22 | - 2b7c514: Adds the combobox, select, textarea mod elements. Adds `Model Definitions` to Manifests. Adds the `ethPersonalSign` action. Adds `json-ld` indexing to metadata. Adds a `loadingLabel` prop to buttons 23 | - 9d4d7e1: fix: placeholder text 24 | - Updated dependencies [584ffc2] 25 | - Updated dependencies [f2141a9] 26 | - Updated dependencies [2b7c514] 27 | - Updated dependencies [f2141a9] 28 | - Updated dependencies [584ffc2] 29 | - Updated dependencies [69be77d] 30 | - Updated dependencies [63b4864] 31 | - @mod-protocol/core@0.1.0 32 | 33 | ## 0.0.2 34 | 35 | ### Patch Changes 36 | 37 | - 390b483: fix: improve URL embed loading by only showing the embed when the Open Graph data is fetched 38 | - 4a50bdd: feat: npm readiness 39 | - Updated dependencies [8ae8453] 40 | - Updated dependencies [15d9d7f] 41 | - Updated dependencies [4a50bdd] 42 | - @mod-protocol/core@0.0.2 43 | -------------------------------------------------------------------------------- /packages/react-editor/README.md: -------------------------------------------------------------------------------- 1 | # @mod-protocol/react-editor 2 | 3 | This package of [Mod Protocol](https://github.com/mod-protocol/mod) renders a Farcaster native text editor for creating Farcaster casts 4 | -------------------------------------------------------------------------------- /packages/react-editor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mod-protocol/react-editor", 3 | "version": "0.1.0", 4 | "main": "./dist/index.js", 5 | "module": "./dist/index.mjs", 6 | "types": "./dist/index.d.ts", 7 | "scripts": { 8 | "lint": "eslint \"**/*.ts*\" && tsc --noEmit", 9 | "build": "tsup src/index.tsx --format cjs,esm --dts", 10 | "dev": "npm run build -- --watch . --watch ../tiptap-extension-channel-mention --watch ../tiptap-extension-link" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/mod-protocol/mod/tree/main/package/react-editor" 15 | }, 16 | "dependencies": { 17 | "@tiptap/core": "^2.0.4", 18 | "@mod-protocol/core": "*", 19 | "@mod-protocol/farcaster": "*", 20 | "@tiptap/extension-document": "^2.0.4", 21 | "@tiptap/extension-hard-break": "^2.0.4", 22 | "@tiptap/extension-history": "^2.0.4", 23 | "@tiptap/extension-link": "^2.0.4", 24 | "@tiptap/extension-mention": "^2.0.4", 25 | "@tiptap/extension-paragraph": "^2.0.4", 26 | "@tiptap/extension-placeholder": "^2.0.4", 27 | "@tiptap/extension-text": "^2.0.4", 28 | "@tiptap/pm": "^2.0.4", 29 | "@tiptap/suggestion": "^2.0.4", 30 | "@tiptap/react": "^2.0.4", 31 | "prosemirror-model": "^1.19.3" 32 | }, 33 | "peerDependencies": { 34 | "react": "^18.2.0", 35 | "react-dom": "^18.2.0" 36 | }, 37 | "workspaces": [ 38 | "configs/*", 39 | "packages/*", 40 | "examples/*" 41 | ], 42 | "devDependencies": { 43 | "@types/react": "^18.2.0", 44 | "@types/react-dom": "^18.2.0", 45 | "eslint": "^7.32.0", 46 | "eslint-config-custom": "*", 47 | "tsconfig": "*", 48 | "typescript": "^5.2.2" 49 | } 50 | } -------------------------------------------------------------------------------- /packages/react-editor/src/errors.ts: -------------------------------------------------------------------------------- 1 | export const MAX_EMBEDS_REACHED_ERROR = { 2 | code: 1, 3 | message: 4 | "Max number of embeds reached. Please remove an embed to add another", 5 | }; 6 | 7 | export type ErrorType = typeof MAX_EMBEDS_REACHED_ERROR; 8 | -------------------------------------------------------------------------------- /packages/react-editor/src/extension-clipboard.tsx: -------------------------------------------------------------------------------- 1 | import { Slice, Fragment, Node as ProseMirrorNode } from "prosemirror-model"; 2 | 3 | // https://github.com/ueberdosis/tiptap/issues/775 4 | export function clipboardTextParser(text: any, context: any, plain: any): any { 5 | const blocks = text.replace().split(/(?:\r\n?|\n)/); 6 | const nodes = []; 7 | // console.log(blocks, text, context) 8 | let content: any[] = []; 9 | blocks.forEach((line: any, i: number) => { 10 | if (i !== 0) content.push({ type: "hardBreak" }); 11 | if (line.length > 0) content.push({ type: "text", text: line }); 12 | }); 13 | 14 | let node = ProseMirrorNode.fromJSON(context.doc.type.schema, { 15 | type: "paragraph", 16 | content, 17 | }); 18 | nodes.push(node); 19 | 20 | const fragment = Fragment.fromArray(nodes); 21 | return Slice.maxOpen(fragment); 22 | } 23 | 24 | export function transformPastedHTML(str: string): string { 25 | const transformed = str 26 | // replace


with
since the previous will falsely double new line. 27 | .replace(/]*>\s*]*>\s*<\/p>/gi, "

") 28 | // replace

with
and

with empty 29 | .replace(/<\/p>\s*]*>/gi, "
"); 30 | return transformed; 31 | } 32 | -------------------------------------------------------------------------------- /packages/react-editor/src/extension-savedraft.tsx: -------------------------------------------------------------------------------- 1 | // async function handleSaveDraft(e?: FormEvent | null) { 2 | // e?.preventDefault(); 3 | // const fullText = getText(); 4 | // const submission = await onSaveDraft?.({ text: fullText }); 5 | 6 | // return submission; 7 | // } 8 | -------------------------------------------------------------------------------- /packages/react-editor/src/extension-warnonnavigate.tsx: -------------------------------------------------------------------------------- 1 | // import { useEffect } from "react"; 2 | 3 | // export const useWarnIfUnsavedChanges = ( 4 | // unsavedChanges: boolean, 5 | // callback: () => boolean 6 | // ) => { 7 | // const router = useRouter(); 8 | // useEffect(() => { 9 | // if (unsavedChanges) { 10 | // const routeChangeStart = (url: any, { shallow }: any) => { 11 | // if (url !== router.asPath) { 12 | // const ok = callback(); 13 | // if (!ok) { 14 | // Router.events.emit("routeChangeError"); 15 | // throw "Abort route change. Please ignore this error."; 16 | // } 17 | // } 18 | // }; 19 | // Router.events.on("routeChangeStart", routeChangeStart); 20 | 21 | // return () => { 22 | // Router.events.off("routeChangeStart", routeChangeStart); 23 | // }; 24 | // } 25 | // }, [unsavedChanges]); 26 | // }; 27 | 28 | // useWarnIfUnsavedChanges(!editor?.isEmpty && !isSubmitting, warnOnNavigate); 29 | -------------------------------------------------------------------------------- /packages/react-editor/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEditor } from "./use-editor"; 2 | import { useTextLength } from "./use-text-length"; 3 | import { EditorContent } from "@tiptap/react"; 4 | export type { Editor } from "@tiptap/core"; 5 | 6 | export { useEditor, useTextLength, EditorContent }; 7 | -------------------------------------------------------------------------------- /packages/react-editor/src/use-key-press.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useRef } from "react"; 2 | 3 | export const useKeyPress = ({ 4 | metaKey, 5 | shiftKey, 6 | keys, 7 | callback, 8 | node = null, 9 | }: { 10 | metaKey?: boolean; 11 | shiftKey?: boolean; 12 | keys: string[]; 13 | callback: (ev: KeyboardEvent) => void; 14 | node?: HTMLElement | null; 15 | }) => { 16 | // implement the callback ref pattern 17 | const callbackRef = useRef(callback); 18 | useEffect(() => { 19 | callbackRef.current = callback; 20 | }, [callback]); 21 | 22 | // handle what happens on key press 23 | const handleKeyPress = useCallback( 24 | (event: KeyboardEvent) => { 25 | // check if one of the key is part of the ones we want 26 | if ( 27 | keys.every((key) => event.key === key) && 28 | (!metaKey || event.metaKey || event.ctrlKey) && 29 | (!shiftKey || event.shiftKey) 30 | ) { 31 | callbackRef.current(event); 32 | } 33 | }, 34 | [keys, metaKey, shiftKey] 35 | ); 36 | 37 | useEffect(() => { 38 | // target is either the provided node or the document 39 | const targetNode = node ?? document; 40 | // attach the event listener 41 | targetNode && targetNode.addEventListener("keydown", handleKeyPress as any); 42 | 43 | // remove the event listener 44 | return () => 45 | targetNode && 46 | targetNode.removeEventListener("keydown", handleKeyPress as any); 47 | }, [handleKeyPress, node]); 48 | }; 49 | -------------------------------------------------------------------------------- /packages/react-editor/src/use-text-length.tsx: -------------------------------------------------------------------------------- 1 | import { convertCastPlainTextToStructured } from "@mod-protocol/farcaster"; 2 | 3 | export function useTextLength({ 4 | getText, 5 | maxByteLength, 6 | }: { 7 | getText: () => string; 8 | maxByteLength: number; 9 | }) { 10 | const text = getText(); 11 | 12 | // Mentions don't occupy space in the cast, so we need to ignore them for our length calculation 13 | const structuredTextUnits = convertCastPlainTextToStructured({ text }); 14 | const textWithoutMentions = structuredTextUnits.reduce((acc, unit) => { 15 | if (unit.type !== "mention") acc += unit.serializedContent; 16 | return acc; 17 | }, ""); 18 | 19 | const lengthInBytes = new TextEncoder().encode(textWithoutMentions).length; 20 | 21 | const eightyFivePercentComplete = maxByteLength * 0.85; 22 | 23 | return { 24 | length: lengthInBytes, 25 | isValid: lengthInBytes <= maxByteLength, 26 | tailwindColor: 27 | lengthInBytes > maxByteLength 28 | ? "red" 29 | : lengthInBytes > eightyFivePercentComplete 30 | ? `orange.${ 31 | 2 + Math.floor((lengthInBytes - eightyFivePercentComplete) / 10) 32 | }00` 33 | : "gray", 34 | label: 35 | lengthInBytes > maxByteLength 36 | ? `-${lengthInBytes - maxByteLength} characters left` 37 | : lengthInBytes > eightyFivePercentComplete 38 | ? `${maxByteLength - lengthInBytes} characters left` 39 | : ``, 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /packages/react-editor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": [ 4 | "." 5 | ], 6 | "exclude": [ 7 | "dist", 8 | "build", 9 | "node_modules" 10 | ], 11 | "compilerOptions": { 12 | "paths": { 13 | "@mod-protocol/tiptap-extension-link": [ 14 | "../tiptap-extension-link/src" 15 | ], 16 | "@mod-protocol/tiptap-extension-channel-mention": [ 17 | "../tiptap-extension-channel-mention/src" 18 | ], 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /packages/react-ui-shadcn/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | // This tells ESLint to load the config from the package `eslint-config-custom` 4 | extends: ["custom"], 5 | }; 6 | -------------------------------------------------------------------------------- /packages/react-ui-shadcn/README.md: -------------------------------------------------------------------------------- 1 | # @mod-protocol/react-ui-shadcn 2 | 3 | This package of [Mod Protocol](https://github.com/mod-protocol/mod) is a UI implementation of the Mod Elements using [Shadcn](https://ui.shadcn.com/) which uses [Tailwind CSS](https://tailwindcss.com/) and [Radix UI](https://www.radix-ui.com/) under the hood. 4 | -------------------------------------------------------------------------------- /packages/react-ui-shadcn/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "src/components", 14 | "utils": "src/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /packages/react-ui-shadcn/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mod-protocol/react-ui-shadcn", 3 | "version": "0.3.1", 4 | "license": "MIT", 5 | "scripts": { 6 | "lint": "eslint \"**/*.ts*\" && tsc --noEmit", 7 | "build": "tsup `find ./src \\( -name '*.ts' -o -name '*.tsx' \\)` --format cjs --dts --onSuccess \"cp -a src/public/. dist/public\"", 8 | "dev": "npm run build -- --watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/mod-protocol/mod/tree/main/packages/react-ui-shadcn" 13 | }, 14 | "dependencies": { 15 | "@mod-protocol/core": "^0.2.0", 16 | "@mod-protocol/farcaster": "^0.2.0", 17 | "@mod-protocol/react": "^0.2.0", 18 | "@mod-protocol/react-editor": "^0.1.0", 19 | "@mod-protocol/mod-registry": "*", 20 | "@primer/octicons-react": "^19.5.0", 21 | "@radix-ui/react-aspect-ratio": "^1.0.3", 22 | "@radix-ui/react-avatar": "^1.0.4", 23 | "@radix-ui/react-dialog": "^1.0.5", 24 | "@radix-ui/react-icons": "^1.3.0", 25 | "@radix-ui/react-popover": "^1.0.6", 26 | "@radix-ui/react-scroll-area": "^1.0.4", 27 | "@radix-ui/react-select": "^2.0.0", 28 | "@radix-ui/react-slot": "^1.0.2", 29 | "@radix-ui/react-tabs": "^1.0.4", 30 | "class-variance-authority": "^0.7.0", 31 | "clsx": "^2.0.0", 32 | "cmdk": "^0.2.0", 33 | "tailwind-merge": "^1.14.0", 34 | "tailwindcss-animate": "^1.0.6", 35 | "video.js": "^8.0.4" 36 | }, 37 | "peerDependencies": { 38 | "react": "^18.2.0", 39 | "react-dom": "^18.2.0" 40 | }, 41 | "devDependencies": { 42 | "@types/react": "^18.2.0", 43 | "@types/react-dom": "^18.2.0", 44 | "eslint": "^7.32.0", 45 | "eslint-config-custom": "*", 46 | "postcss": "^8.4.31", 47 | "tailwindcss": "^3.3.3", 48 | "tsconfig": "*", 49 | "typescript": "^5.2.2" 50 | } 51 | } -------------------------------------------------------------------------------- /packages/react-ui-shadcn/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/react-ui-shadcn/src/components/cast-length-ui-indicator.tsx: -------------------------------------------------------------------------------- 1 | import { useTextLength } from "@mod-protocol/react-editor"; 2 | 3 | type Props = { 4 | getText: () => string; 5 | }; 6 | 7 | export function CastLengthUIIndicator({ getText }: Props) { 8 | const { length, label, tailwindColor } = useTextLength({ 9 | getText, 10 | maxByteLength: 320, 11 | }); 12 | return
{label}
; 13 | } 14 | -------------------------------------------------------------------------------- /packages/react-ui-shadcn/src/components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 5 | import { type ThemeProviderProps } from "next-themes/dist/types"; 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children}; 9 | } 10 | 11 | export { useTheme } from "next-themes"; 12 | -------------------------------------------------------------------------------- /packages/react-ui-shadcn/src/components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" 4 | 5 | const AspectRatio = AspectRatioPrimitive.Root 6 | 7 | export { AspectRatio } 8 | -------------------------------------------------------------------------------- /packages/react-ui-shadcn/src/components/ui/circular-progress.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { cn } from "lib/utils"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | const circularProgressVariants = cva( 6 | "inline-block animate-spin rounded-full border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]", 7 | { 8 | variants: { 9 | size: { 10 | default: "h-8 w-8 border-4", 11 | sm: "h-4 w-4 border-2", 12 | lg: "h-12 w-12 border-6", 13 | }, 14 | }, 15 | defaultVariants: { 16 | size: "default", 17 | }, 18 | } 19 | ); 20 | 21 | const CircularProgress = React.forwardRef< 22 | HTMLDivElement, 23 | React.HTMLAttributes & 24 | VariantProps 25 | >(({ className, size, ...props }, ref) => ( 26 |
32 | 33 | Loading... 34 | 35 |
36 | )); 37 | 38 | CircularProgress.displayName = "CircularProgress"; 39 | 40 | export { CircularProgress }; 41 | -------------------------------------------------------------------------------- /packages/react-ui-shadcn/src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "lib/utils"; 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ); 21 | } 22 | ); 23 | Input.displayName = "Input"; 24 | 25 | export { Input }; 26 | -------------------------------------------------------------------------------- /packages/react-ui-shadcn/src/components/ui/link.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { type VariantProps } from "class-variance-authority"; 3 | 4 | import { cn } from "lib/utils"; 5 | import { buttonVariants } from "./button"; 6 | 7 | export interface LinkProps 8 | extends React.AnchorHTMLAttributes, 9 | VariantProps { 10 | asChild?: boolean; 11 | } 12 | 13 | const Link = React.forwardRef( 14 | ({ className, variant = "link", size, asChild = false, ...props }, ref) => { 15 | return ( 16 | 21 | ); 22 | } 23 | ); 24 | Link.displayName = "Link"; 25 | 26 | export { Link }; 27 | -------------------------------------------------------------------------------- /packages/react-ui-shadcn/src/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as PopoverPrimitive from "@radix-ui/react-popover"; 5 | 6 | import { cn } from "lib/utils"; 7 | 8 | const Popover = PopoverPrimitive.Root; 9 | 10 | const PopoverTrigger = PopoverPrimitive.Trigger; 11 | 12 | const PopoverContent = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 16 | 17 | 27 | 28 | )); 29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName; 30 | 31 | export { Popover, PopoverTrigger, PopoverContent }; 32 | -------------------------------------------------------------------------------- /packages/react-ui-shadcn/src/components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; 5 | 6 | import { cn } from "lib/utils"; 7 | 8 | const ScrollArea = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, children, ...props }, ref) => ( 12 | 17 | 18 | {children} 19 | 20 | 21 | 22 | 23 | )); 24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; 25 | 26 | const ScrollBar = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, orientation = "vertical", ...props }, ref) => ( 30 | 43 | 44 | 45 | )); 46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; 47 | 48 | export { ScrollArea, ScrollBar }; 49 | -------------------------------------------------------------------------------- /packages/react-ui-shadcn/src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "lib/utils"; 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ); 13 | } 14 | 15 | export { Skeleton }; 16 | -------------------------------------------------------------------------------- /packages/react-ui-shadcn/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "lib/utils"; 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |