├── .editorconfig ├── .eslintrc.prepublish.js ├── .github └── workflows │ ├── ci.yml │ └── npm-publish_and_release.yml ├── .gitignore ├── .prettierrc.js ├── .vscode └── extensions.json ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── __tests__ ├── Mastodon.test.ts ├── account.test.ts ├── bookmarks.test.ts ├── lists.test.ts ├── rate-limit-queue.test.ts ├── status.test.ts └── timeline.test.ts ├── credentials ├── Mastodon.svg └── MastodonOAuth2Api.credentials.ts ├── eslint.config.js ├── examples ├── Mastodon_example_workflow.json └── Mastodon_node_template.json ├── gulpfile.js ├── index.js ├── jest.config.js ├── jest.setup.js ├── nodes └── Mastodon │ ├── Mastodon.node.ts │ ├── Mastodon.svg │ ├── Mastodon_Methods.ts │ ├── Mastodon_Properties.ts │ ├── account │ ├── AccountInterfaces.ts │ ├── AccountMethods.ts │ ├── AccountProperties.ts │ ├── RoleInterface.ts │ └── index.ts │ ├── admin │ ├── AdminInterfaces.ts │ ├── canonical-email-blocks │ │ ├── CanonicalEmailBlockMethods.ts │ │ └── CanonicalEmailBlockProperties.ts │ ├── cohorts │ │ ├── CohortMethods.ts │ │ └── CohortProperties.ts │ ├── dimensions │ │ ├── DimensionMethods.ts │ │ └── DimensionProperties.ts │ └── ips │ │ ├── IpMethods.ts │ │ └── IpProperties.ts │ ├── announcements │ ├── AnnouncementInterfaces.ts │ ├── AnnouncementMethods.ts │ ├── AnnouncementProperties.ts │ └── index.ts │ ├── apps │ ├── AppsMethods.ts │ ├── AppsProperties.ts │ └── index.ts │ ├── authentication │ ├── AuthenticationMethods.ts │ ├── AuthenticationProperties.ts │ └── index.ts │ ├── blocks │ ├── BlocksMethods.ts │ ├── BlocksProperties.ts │ └── index.ts │ ├── bookmarks │ ├── BookmarksMethods.ts │ ├── BookmarksProperties.ts │ └── index.ts │ ├── conversations │ ├── ConversationInterfaces.ts │ ├── ConversationMethods.ts │ ├── ConversationProperties.ts │ └── index.ts │ ├── customEmojis │ ├── CustomEmojisInterfaces.ts │ ├── CustomEmojisMethods.ts │ ├── CustomEmojisProperties.ts │ └── index.ts │ ├── directory │ ├── DirectoryInterfaces.ts │ ├── DirectoryMethods.ts │ ├── DirectoryProperties.ts │ └── index.ts │ ├── domainBlocks │ ├── DomainBlocksMethods.ts │ ├── DomainBlocksProperties.ts │ └── index.ts │ ├── domains │ ├── allowed │ │ ├── AllowedDomainMethods.ts │ │ └── AllowedDomainProperties.ts │ ├── blocked │ │ ├── BlockedDomainMethods.ts │ │ └── BlockedDomainProperties.ts │ ├── email-blocked │ │ ├── EmailBlockedDomainMethods.ts │ │ └── EmailBlockedDomainProperties.ts │ └── index.ts │ ├── emails │ ├── EmailsMethods.ts │ └── EmailsProperties.ts │ ├── endorsements │ ├── EndorsementMethods.ts │ ├── EndorsementProperties.ts │ └── index.ts │ ├── favourites │ ├── FavouritesMethods.ts │ ├── FavouritesProperties.ts │ └── index.ts │ ├── featuredTags │ ├── FeaturedTagInterfaces.ts │ ├── FeaturedTagMethods.ts │ ├── FeaturedTagProperties.ts │ └── index.ts │ ├── filters │ ├── FiltersMethods.ts │ ├── FiltersProperties.ts │ └── index.ts │ ├── followRequests │ ├── FollowRequestMethods.ts │ ├── FollowRequestProperties.ts │ └── index.ts │ ├── followedTags │ ├── FollowedTagsMethods.ts │ ├── FollowedTagsProperties.ts │ └── index.ts │ ├── instance │ ├── InstanceInterfaces.ts │ ├── InstanceMethods.ts │ ├── InstanceProperties.ts │ └── index.ts │ ├── lists │ ├── ListInterfaces.ts │ ├── ListMethods.ts │ ├── ListProperties.ts │ └── index.ts │ ├── markers │ ├── MarkerInterfaces.ts │ ├── MarkerMethods.ts │ ├── MarkersInterfaces.ts │ ├── MarkersProperties.ts │ └── index.ts │ ├── measures │ ├── MeasureMethods.ts │ ├── MeasureProperties.ts │ └── index.ts │ ├── media │ ├── MediaMethods.ts │ ├── MediaProperties.ts │ └── index.ts │ ├── mutes │ ├── MutesMethods.ts │ ├── MutesProperties.ts │ └── index.ts │ ├── n8n.png │ ├── n8n.svg │ ├── notifications │ ├── NotificationInterfaces.ts │ ├── NotificationMethods.ts │ ├── NotificationProperties.ts │ └── index.ts │ ├── oembed │ ├── OEmbedMethods.ts │ ├── OEmbedProperties.ts │ └── index.ts │ ├── polls │ ├── PollMethods.ts │ ├── PollProperties.ts │ └── index.ts │ ├── preferences │ ├── PreferenceMethods.ts │ ├── PreferenceProperties.ts │ └── index.ts │ ├── profile │ ├── ProfileMethods.ts │ ├── ProfileProperties.ts │ └── index.ts │ ├── proofs │ ├── ProofMethods.ts │ ├── ProofProperties.ts │ └── index.ts │ ├── push │ ├── PushInterfaces.ts │ ├── PushMethods.ts │ ├── PushProperties.ts │ └── index.ts │ ├── relationship │ └── RelationshipInterfaces.ts │ ├── reports │ ├── ReportMethods.ts │ ├── ReportProperties.ts │ └── index.ts │ ├── retention │ ├── RetentionMethods.ts │ ├── RetentionProperties.ts │ └── index.ts │ ├── scheduledStatuses │ ├── ScheduledStatusMethods.ts │ ├── ScheduledStatusProperties.ts │ └── index.ts │ ├── search │ ├── SearchInterfaces.ts │ ├── SearchMethods.ts │ ├── SearchProperties.ts │ └── index.ts │ ├── status │ ├── StatusInterface.ts │ ├── StatusMethods.ts │ ├── StatusMethodsTypes.ts │ ├── StatusProperties.ts │ └── index.ts │ ├── streaming │ ├── StreamingInterfaces.ts │ ├── StreamingMethods.ts │ ├── StreamingProperties.ts │ └── index.ts │ ├── suggestions │ ├── SuggestionMethods.ts │ ├── SuggestionProperties.ts │ └── index.ts │ ├── tags │ ├── TagMethods.ts │ ├── TagProperties.ts │ └── index.ts │ └── timeline │ ├── TimelineInterfaces.ts │ ├── TimelineMethods.ts │ ├── TimelineProperties.ts │ └── index.ts ├── package-lock.json ├── package.json ├── src └── utils │ └── pkceWrapper.ts ├── test-instructions.md ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [package.json] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | 18 | [*.yml] 19 | indent_style = space 20 | indent_size = 2 21 | -------------------------------------------------------------------------------- /.eslintrc.prepublish.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: './.eslintrc.js', 3 | overrides: [ 4 | { 5 | files: ['package.json'], 6 | plugins: ['eslint-plugin-n8n-nodes-base'], 7 | rules: { 8 | // Enforce that the package.json name field is not the default value 9 | 'n8n-nodes-base/community-package-json-name-still-default': 'error', 10 | }, 11 | }, 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Set up Node.js 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: '20' 20 | 21 | - name: Install dependencies 22 | run: npm install 23 | 24 | - name: Run lint 25 | run: npm run lint 26 | 27 | - name: Run tests 28 | run: npm test 29 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish_and_release.yml: -------------------------------------------------------------------------------- 1 | name: Monthly Release and Publish 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | tags: 9 | - 'v*.*.*' 10 | schedule: 11 | - cron: '0 0 1 * *' # 1st of every month at midnight UTC 12 | 13 | permissions: 14 | contents: write # Required to push tags and create releases 15 | 16 | jobs: 17 | release-and-publish: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 # Fetch all history and tags 25 | 26 | - name: Set up Node.js 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: '20' 30 | registry-url: 'https://registry.npmjs.org/' 31 | 32 | - name: Install dependencies 33 | run: npm install 34 | 35 | - name: Run tests 36 | run: npm test 37 | 38 | - name: Fetch and increment version 39 | id: fetch_tag 40 | run: | 41 | echo "Fetching latest tag..." 42 | TAG=$(curl -s -H "Accept: application/vnd.github+json" \ 43 | -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ 44 | https://api.github.com/repos/${{ github.repository }}/tags \ 45 | | jq -r '.[0].name') 46 | 47 | TAG_NAME=${TAG#v} 48 | if [ -z "$TAG_NAME" ] || [ "$TAG_NAME" = "null" ]; then 49 | TAG_NAME="0.0.1" 50 | else 51 | TAG_NAME=$(echo "$TAG_NAME" | awk -F. -v OFS=. '{$NF++; print}') 52 | fi 53 | 54 | echo "New tag will be: v$TAG_NAME" 55 | echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV 56 | 57 | - name: Update package.json version 58 | run: | 59 | CURRENT_VERSION=$(jq -r .version package.json) 60 | TARGET_VERSION="${{ env.TAG_NAME }}" 61 | if [ "$CURRENT_VERSION" != "$TARGET_VERSION" ]; then 62 | echo "Updating version to $TARGET_VERSION" 63 | jq --arg v "$TARGET_VERSION" '.version = $v' package.json > tmp.json && mv tmp.json package.json 64 | else 65 | echo "package.json already up-to-date" 66 | fi 67 | 68 | - name: Commit version update 69 | run: | 70 | git config user.name "GitHub Actions" 71 | git config user.email "actions@github.com" 72 | git add package.json 73 | git commit -m "Release version v${{ env.TAG_NAME }}" || echo "No changes to commit" 74 | 75 | - name: Create tag and push 76 | run: | 77 | git tag "v${{ env.TAG_NAME }}" 78 | git push origin "v${{ env.TAG_NAME }}" 79 | 80 | - name: Publish to npm 81 | run: npm publish 82 | env: 83 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 84 | 85 | - name: Create GitHub release 86 | uses: softprops/action-gh-release@v1 87 | with: 88 | tag_name: v${{ env.TAG_NAME }} 89 | name: Release v${{ env.TAG_NAME }} 90 | body: | 91 | Automated release for version v${{ env.TAG_NAME }} 92 | env: 93 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .tmp 4 | tmp 5 | dist 6 | npm-debug.log* 7 | yarn.lock 8 | .vscode/launch.json 9 | 10 | # Environment variables 11 | .env 12 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 3 | * https://prettier.io/docs/en/options.html#semicolons 4 | */ 5 | semi: true, 6 | 7 | /** 8 | * https://prettier.io/docs/en/options.html#trailing-commas 9 | */ 10 | trailingComma: 'all', 11 | 12 | /** 13 | * https://prettier.io/docs/en/options.html#bracket-spacing 14 | */ 15 | bracketSpacing: true, 16 | 17 | /** 18 | * https://prettier.io/docs/en/options.html#tabs 19 | */ 20 | useTabs: true, 21 | 22 | /** 23 | * https://prettier.io/docs/en/options.html#tab-width 24 | */ 25 | tabWidth: 2, 26 | 27 | /** 28 | * https://prettier.io/docs/en/options.html#arrow-function-parentheses 29 | */ 30 | arrowParens: 'always', 31 | 32 | /** 33 | * https://prettier.io/docs/en/options.html#quotes 34 | */ 35 | singleQuote: true, 36 | 37 | /** 38 | * https://prettier.io/docs/en/options.html#quote-props 39 | */ 40 | quoteProps: 'as-needed', 41 | 42 | /** 43 | * https://prettier.io/docs/en/options.html#end-of-line 44 | */ 45 | endOfLine: 'lf', 46 | 47 | /** 48 | * https://prettier.io/docs/en/options.html#print-width 49 | */ 50 | printWidth: 100, 51 | }; 52 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "EditorConfig.EditorConfig", 5 | "esbenp.prettier-vscode", 6 | "ms-vscode.vscode-typescript-tslint-plugin" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Commitment 4 | 5 | We are committed to fostering an inclusive, welcoming, and harassment-free environment for everyone. As contributors and maintainers of this project, we pledge to uphold a community where individuals are treated with respect, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual orientation. 6 | 7 | ## Standards of Behavior 8 | 9 | To create a positive and professional environment, we expect all participants to adhere to the following standards: 10 | 11 | ### Positive Contributions 12 | 13 | - Use welcoming and inclusive language. 14 | - Respect differing viewpoints and experiences. 15 | - Accept constructive feedback gracefully. 16 | - Prioritize the well-being of the community. 17 | - Demonstrate empathy and kindness towards others. 18 | 19 | ### Unacceptable Behavior 20 | 21 | - Use of sexualized language, imagery, or unwelcome advances. 22 | - Trolling, insulting, or derogatory comments. 23 | - Harassment, whether public or private. 24 | - Publishing private information without explicit consent. 25 | - Any behavior that could reasonably be considered inappropriate in a professional setting. 26 | 27 | ## Responsibilities of Maintainers 28 | 29 | Project maintainers are responsible for defining and enforcing standards of acceptable behavior. They are empowered to: 30 | 31 | - Remove, edit, or reject comments, commits, code, or other contributions that do not align with this Code of Conduct. 32 | - Ban contributors who engage in harmful or inappropriate behavior, either temporarily or permanently. 33 | 34 | ## Scope of Application 35 | 36 | This Code of Conduct applies to all project spaces, including but not limited to: 37 | 38 | - Online platforms such as GitHub repositories, forums, and chat channels. 39 | - Public spaces where individuals represent the project, such as conferences or social media. 40 | 41 | Representation of the project includes using official email addresses, posting via official accounts, or acting as an appointed representative. 42 | 43 | ## Reporting Violations 44 | 45 | Instances of unacceptable behavior can be reported by contacting the project team at jan@n8n.io. All reports will be reviewed and investigated promptly and fairly. The project team is committed to maintaining confidentiality and ensuring the safety of the reporter. 46 | 47 | ## Enforcement 48 | 49 | Violations of this Code of Conduct will be addressed on a case-by-case basis. Actions may include: 50 | 51 | - A warning to the offender. 52 | - Temporary or permanent ban from the project. 53 | - Other actions deemed appropriate by the project leadership. 54 | 55 | ## Acknowledgments 56 | 57 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1. For more information, visit [https://www.contributor-covenant.org](https://www.contributor-covenant.org). 58 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2022 n8n 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /__tests__/account.test.ts: -------------------------------------------------------------------------------- 1 | import { Mastodon } from '../nodes/Mastodon/Mastodon.node'; 2 | import { IExecuteFunctions, NodeOperationError } from 'n8n-workflow'; 3 | 4 | describe('Mastodon Node - Account', () => { 5 | let node: Mastodon; 6 | let ctx: Partial; 7 | 8 | beforeEach(() => { 9 | node = new Mastodon(); 10 | ctx = { 11 | getNodeParameter: jest.fn(), 12 | getInputData: jest.fn().mockReturnValue([{ json: {} }]), 13 | helpers: { requestOAuth2: jest.fn() } as any, 14 | getCredentials: jest.fn().mockResolvedValue({ 15 | baseUrl: 'https://mastodon.social', 16 | oauth2: { accessToken: 'test-token' }, 17 | }), 18 | continueOnFail: () => false, 19 | getNode: () => 20 | ({ 21 | id: '1', 22 | type: 'mastodon', 23 | name: 'Mastodon', 24 | typeVersion: 1, 25 | position: [0, 0], 26 | parameters: {}, 27 | }) as any, 28 | }; 29 | }); 30 | 31 | it('should follow an account', async () => { 32 | (ctx.getNodeParameter as jest.Mock).mockImplementation((param) => { 33 | if (param === 'resource') return 'account'; 34 | if (param === 'operation') return 'follow'; 35 | if (param === 'accountId') return '42'; 36 | return undefined; 37 | }); 38 | const mockResponse = { id: '42', following: true }; 39 | (ctx.helpers!.requestOAuth2 as jest.Mock).mockResolvedValue(mockResponse); 40 | 41 | const result = await node.execute.call(ctx as IExecuteFunctions); 42 | expect(ctx.helpers!.requestOAuth2).toHaveBeenCalledWith( 43 | 'mastodonOAuth2Api', 44 | expect.objectContaining({ 45 | method: 'POST', 46 | uri: expect.stringContaining('/api/v1/accounts/42/follow'), 47 | }), 48 | ); 49 | expect(result?.[0]?.[0].json).toEqual(mockResponse); 50 | }); 51 | 52 | it('should throw if accountId is missing for unfollow', async () => { 53 | (ctx.getNodeParameter as jest.Mock).mockImplementation((param) => { 54 | if (param === 'resource') return 'account'; 55 | if (param === 'operation') return 'unfollow'; 56 | return undefined; 57 | }); 58 | try { 59 | await node.execute.call(ctx as IExecuteFunctions); 60 | expect(true).toBe(false); 61 | } catch (error) { 62 | expect(error).toBeInstanceOf(Error); 63 | } 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /__tests__/bookmarks.test.ts: -------------------------------------------------------------------------------- 1 | import { Mastodon } from '../nodes/Mastodon/Mastodon.node'; 2 | import { IExecuteFunctions, NodeOperationError } from 'n8n-workflow'; 3 | 4 | describe('Mastodon Node - Bookmarks', () => { 5 | let node: Mastodon; 6 | let ctx: Partial; 7 | 8 | beforeEach(() => { 9 | node = new Mastodon(); 10 | ctx = { 11 | getNodeParameter: jest.fn(), 12 | getInputData: jest.fn().mockReturnValue([{ json: {} }]), 13 | helpers: { requestOAuth2: jest.fn() } as any, 14 | getCredentials: jest.fn().mockResolvedValue({ 15 | baseUrl: 'https://mastodon.social', 16 | oauth2: { accessToken: 'test-token' }, 17 | }), 18 | continueOnFail: () => false, 19 | getNode: () => 20 | ({ 21 | id: '1', 22 | type: 'mastodon', 23 | name: 'Mastodon', 24 | typeVersion: 1, 25 | position: [0, 0], 26 | parameters: {}, 27 | }) as any, 28 | }; 29 | }); 30 | 31 | it('should add a bookmark', async () => { 32 | (ctx.getNodeParameter as jest.Mock).mockImplementation((param) => 33 | param === 'resource' 34 | ? 'bookmarks' 35 | : param === 'operation' 36 | ? 'addBookmark' 37 | : param === 'statusId' 38 | ? '123' 39 | : undefined, 40 | ); 41 | (ctx.helpers!.requestOAuth2 as jest.Mock).mockResolvedValue({ id: '123', bookmarked: true }); 42 | 43 | const result = await node.execute.call(ctx as IExecuteFunctions); 44 | expect(ctx.helpers!.requestOAuth2).toHaveBeenCalledWith( 45 | 'mastodonOAuth2Api', 46 | expect.objectContaining({ 47 | method: 'POST', 48 | uri: expect.stringContaining('/api/v1/statuses/123/bookmark'), 49 | }), 50 | ); 51 | }); 52 | 53 | it('should remove a bookmark', async () => { 54 | (ctx.getNodeParameter as jest.Mock).mockImplementation((param) => 55 | param === 'resource' 56 | ? 'bookmarks' 57 | : param === 'operation' 58 | ? 'removeBookmark' 59 | : param === 'statusId' 60 | ? '123' 61 | : undefined, 62 | ); 63 | (ctx.helpers!.requestOAuth2 as jest.Mock).mockResolvedValue({ id: '123', bookmarked: false }); 64 | 65 | const result = await node.execute.call(ctx as IExecuteFunctions); 66 | expect(ctx.helpers!.requestOAuth2).toHaveBeenCalledWith( 67 | 'mastodonOAuth2Api', 68 | expect.objectContaining({ 69 | method: 'POST', 70 | uri: expect.stringContaining('/api/v1/statuses/123/unbookmark'), 71 | }), 72 | ); 73 | }); 74 | 75 | it('should throw on missing statusId', async () => { 76 | (ctx.getNodeParameter as jest.Mock).mockImplementation((param) => { 77 | if (param === 'resource') return 'bookmarks'; 78 | if (param === 'operation') return 'addBookmark'; 79 | return undefined; 80 | }); 81 | 82 | // Handle the error directly to avoid timeout issues 83 | try { 84 | await node.execute.call(ctx as IExecuteFunctions); 85 | // If we reach this point, the test should fail as an error was expected 86 | expect(true).toBe(false); 87 | } catch (error) { 88 | expect(error).toBeInstanceOf(NodeOperationError); 89 | } 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /__tests__/lists.test.ts: -------------------------------------------------------------------------------- 1 | import { Mastodon } from '../nodes/Mastodon/Mastodon.node'; 2 | import { IExecuteFunctions } from 'n8n-workflow'; 3 | 4 | describe('Mastodon Node - Lists', () => { 5 | let node: Mastodon; 6 | let ctx: Partial; 7 | 8 | beforeEach(() => { 9 | node = new Mastodon(); 10 | ctx = { 11 | getNodeParameter: jest.fn(), 12 | getInputData: jest.fn().mockReturnValue([{ json: {} }]), 13 | helpers: { 14 | requestOAuth2: jest.fn(), 15 | createBinarySignedUrl: jest.fn() 16 | } as any, 17 | getCredentials: jest.fn().mockResolvedValue({ 18 | baseUrl: 'https://mastodon.social', 19 | oauth2: { accessToken: 'test-token' }, 20 | }), 21 | continueOnFail: () => false, 22 | getNode: () => 23 | ({ 24 | id: '1', 25 | type: 'mastodon', 26 | name: 'Mastodon', 27 | typeVersion: 1, 28 | position: [0, 0], 29 | parameters: {}, 30 | }) as any, 31 | }; 32 | }); 33 | 34 | it('should get all lists', async () => { 35 | (ctx.getNodeParameter as jest.Mock).mockImplementation((param) => { 36 | if (param === 'resource') return 'lists'; 37 | if (param === 'operation') return 'getLists'; 38 | return undefined; 39 | }); 40 | const mockResponse = { id: '1', title: 'My List' }; 41 | (ctx.helpers!.requestOAuth2 as jest.Mock).mockResolvedValue(mockResponse); 42 | 43 | const result = await node.execute.call(ctx as IExecuteFunctions); 44 | expect(ctx.helpers!.requestOAuth2).toHaveBeenCalledWith( 45 | 'mastodonOAuth2Api', 46 | expect.objectContaining({ method: 'GET', uri: expect.stringContaining('/api/v1/lists') }), 47 | ); 48 | expect(result?.[0]?.[0].json).toEqual(mockResponse); 49 | }); 50 | 51 | it('should create and delete a list', async () => { 52 | // create 53 | (ctx.getNodeParameter as jest.Mock).mockImplementation((param) => { 54 | if (param === 'resource') return 'lists'; 55 | if (param === 'operation') return 'createList'; 56 | if (param === 'title') return 'New'; 57 | return undefined; 58 | }); 59 | const createMockResponse = { id: '2', title: 'New' }; 60 | (ctx.helpers!.requestOAuth2 as jest.Mock).mockResolvedValue(createMockResponse); 61 | let result = await node.execute.call(ctx as IExecuteFunctions); 62 | expect(ctx.helpers!.requestOAuth2).toHaveBeenCalledWith( 63 | 'mastodonOAuth2Api', 64 | expect.objectContaining({ method: 'POST', uri: expect.stringContaining('/api/v1/lists') }), 65 | ); 66 | expect(result?.[0]?.[0].json).toEqual(createMockResponse); 67 | // delete 68 | (ctx.getNodeParameter as jest.Mock).mockImplementation((param) => { 69 | if (param === 'resource') return 'lists'; 70 | if (param === 'operation') return 'deleteList'; 71 | if (param === 'listId') return '2'; 72 | return undefined; 73 | }); 74 | const deleteMockResponse = {}; 75 | (ctx.helpers!.requestOAuth2 as jest.Mock).mockResolvedValue(deleteMockResponse); 76 | result = await node.execute.call(ctx as IExecuteFunctions); 77 | expect(ctx.helpers!.requestOAuth2).toHaveBeenCalledWith( 78 | 'mastodonOAuth2Api', 79 | expect.objectContaining({ 80 | method: 'DELETE', 81 | uri: expect.stringContaining('/api/v1/lists/2'), 82 | }), 83 | ); 84 | expect(result?.[0]?.[0].json).toEqual(deleteMockResponse); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /__tests__/rate-limit-queue.test.ts: -------------------------------------------------------------------------------- 1 | import { RequestQueue } from '../nodes/Mastodon/Mastodon_Methods'; 2 | 3 | // Mock Logger to avoid import issues in tests 4 | jest.mock('n8n-workflow', () => ({ 5 | LoggerProxy: { 6 | debug: jest.fn(), 7 | info: jest.fn(), 8 | warn: jest.fn(), 9 | error: jest.fn(), 10 | }, 11 | })); 12 | 13 | describe('RequestQueue Rate Limit Handling', () => { 14 | let queue: any; 15 | 16 | beforeEach(() => { 17 | // Reset singleton instance 18 | (RequestQueue as any).instance = undefined; 19 | queue = RequestQueue.getInstance(); 20 | // Clear any existing timers 21 | jest.clearAllTimers(); 22 | }); 23 | 24 | afterEach(() => { 25 | // Properly destroy the queue instance to clean up timers 26 | if (queue && typeof queue.destroy === 'function') { 27 | queue.destroy(); 28 | } 29 | // Clear any existing timers 30 | jest.clearAllTimers(); 31 | jest.useRealTimers(); 32 | }); 33 | 34 | afterAll(() => { 35 | // Final cleanup - ensure no lingering timers or instances 36 | if (queue && typeof queue.destroy === 'function') { 37 | queue.destroy(); 38 | } 39 | (RequestQueue as any).instance = undefined; 40 | jest.clearAllTimers(); 41 | jest.useRealTimers(); 42 | }); 43 | 44 | it('should handle rate limit properly', async () => { 45 | // Set rate limit to 0 with reset in 2 seconds 46 | const resetTime = Math.floor(Date.now() / 1000) + 2; 47 | queue.updateRateLimits(0, resetTime); 48 | 49 | const startTime = Date.now(); 50 | 51 | // This request should wait for rate limit reset 52 | await queue.add(async () => { 53 | return { success: true }; 54 | }); 55 | 56 | const endTime = Date.now(); 57 | const waitTime = endTime - startTime; 58 | 59 | // Should have waited at least 1 second (allowing for timing variance) 60 | expect(waitTime).toBeGreaterThan(1000); 61 | }); 62 | 63 | it('should process requests sequentially', async () => { 64 | const results: number[] = []; 65 | const promises = []; 66 | 67 | // Add multiple requests 68 | for (let i = 0; i < 5; i++) { 69 | promises.push( 70 | queue.add(async () => { 71 | await new Promise(resolve => setTimeout(resolve, 100)); 72 | results.push(i); 73 | return i; 74 | }) 75 | ); 76 | } 77 | 78 | await Promise.all(promises); 79 | 80 | // Results should be in order (sequential processing) 81 | expect(results).toEqual([0, 1, 2, 3, 4]); 82 | }); 83 | 84 | it('should track rate limit usage correctly', () => { 85 | queue.updateRateLimits(10, Math.floor(Date.now() / 1000) + 300); 86 | 87 | const status = queue.getStatus(); 88 | expect(status.rateLimitRemaining).toBe(10); 89 | expect(status.queueLength).toBe(0); 90 | expect(status.processing).toBe(false); 91 | }); 92 | 93 | it('should have queue overflow protection', () => { 94 | // Test that the queue has a maximum size limit 95 | const MAX_QUEUE_SIZE = (queue as any).MAX_QUEUE_SIZE; 96 | expect(MAX_QUEUE_SIZE).toBe(1000); 97 | 98 | // Test that the queue tracks its length 99 | const status = queue.getStatus(); 100 | expect(status.queueLength).toBeDefined(); 101 | expect(typeof status.queueLength).toBe('number'); 102 | }); 103 | 104 | it('should have timeout mechanism for queued requests', () => { 105 | // Test that the REQUEST_TIMEOUT is properly defined 106 | const REQUEST_TIMEOUT = (queue as any).REQUEST_TIMEOUT; 107 | expect(REQUEST_TIMEOUT).toBe(60000); // 60 seconds 108 | 109 | // Test that cleanup method exists 110 | expect(typeof (queue as any).cleanupExpiredRequests).toBe('function'); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /credentials/Mastodon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | const tsParser = require('@typescript-eslint/parser'); 2 | const tsPlugin = require('@typescript-eslint/eslint-plugin'); 3 | const n8nNodesBase = require('eslint-plugin-n8n-nodes-base'); 4 | const globals = require('globals'); 5 | 6 | module.exports = [ 7 | { 8 | ignores: ['.eslintrc.js', '**/*.js', '**/node_modules/**', '**/dist/**'], 9 | }, 10 | { 11 | languageOptions: { 12 | parser: tsParser, 13 | parserOptions: { 14 | project: ['./tsconfig.json'], 15 | sourceType: 'module', 16 | extraFileExtensions: ['.json'], 17 | }, 18 | ecmaVersion: 2020, 19 | globals: { 20 | ...globals.browser, 21 | ...globals.es2020, 22 | ...globals.node, 23 | }, 24 | }, 25 | // env property removed as it is not supported in flat config 26 | }, 27 | { 28 | files: ['package.json'], 29 | plugins: { 30 | 'n8n-nodes-base': n8nNodesBase, 31 | }, 32 | rules: { 33 | 'n8n-nodes-base/community-package-json-name-still-default': 'off', 34 | }, 35 | }, 36 | { 37 | files: ['./credentials/**/*.ts'], 38 | plugins: { 39 | 'n8n-nodes-base': n8nNodesBase, 40 | }, 41 | rules: { 42 | 'n8n-nodes-base/cred-class-field-documentation-url-missing': 'off', 43 | 'n8n-nodes-base/cred-class-field-documentation-url-miscased': 'off', 44 | }, 45 | }, 46 | { 47 | files: ['./nodes/**/*.ts'], 48 | plugins: { 49 | 'n8n-nodes-base': n8nNodesBase, 50 | }, 51 | rules: { 52 | 'n8n-nodes-base/node-execute-block-missing-continue-on-fail': 'off', 53 | 'n8n-nodes-base/node-resource-description-filename-against-convention': 'off', 54 | 'n8n-nodes-base/node-param-fixed-collection-type-unsorted-items': 'off', 55 | 'n8n-nodes-base/node-execute-block-operation-missing-singular-pairing': 'off', 56 | 'n8n-nodes-base/node-execute-block-operation-missing-plural-pairing': 'off', 57 | }, 58 | }, 59 | ]; 60 | -------------------------------------------------------------------------------- /examples/Mastodon_example_workflow.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Mastodon Example Workflow", 3 | "nodes": [ 4 | { 5 | "parameters": { 6 | "resource": "status", 7 | "operation": "create", 8 | "url": "=https://mastodon.example", 9 | "text": "Hello from n8n!", 10 | "additionalFields": {} 11 | }, 12 | "id": "37912c74-5a1e-42c1-9978-a671cd228935", 13 | "name": "Mastodon", 14 | "type": "n8n-nodes-mastodon", 15 | "typeVersion": 1, 16 | "position": [885, 335] 17 | }, 18 | { 19 | "parameters": { 20 | "keepOnlySet": true, 21 | "values": { 22 | "string": [ 23 | { 24 | "name": "text", 25 | "value": "Hello from n8n!" 26 | } 27 | ] 28 | }, 29 | "options": {} 30 | }, 31 | "id": "89e7e1c7-3e74-4525-9f5a-06f01e45ddac", 32 | "name": "Set", 33 | "type": "n8n-nodes-base.set", 34 | "typeVersion": 2, 35 | "position": [660, 335] 36 | }, 37 | { 38 | "parameters": {}, 39 | "id": "b825b174-de06-4716-b3ea-6bb0019fccd5", 40 | "name": "Start", 41 | "type": "n8n-nodes-base.start", 42 | "typeVersion": 1, 43 | "position": [455, 335] 44 | } 45 | ], 46 | "connections": { 47 | "Set": { 48 | "main": [ 49 | [ 50 | { 51 | "node": "Mastodon", 52 | "type": "main", 53 | "index": 0 54 | } 55 | ] 56 | ] 57 | }, 58 | "Start": { 59 | "main": [ 60 | [ 61 | { 62 | "node": "Set", 63 | "type": "main", 64 | "index": 0 65 | } 66 | ] 67 | ] 68 | } 69 | }, 70 | "active": false, 71 | "settings": { 72 | "executionOrder": "v1" 73 | }, 74 | "tags": [ 75 | { 76 | "name": "example", 77 | "color": "#55ff55" 78 | } 79 | ], 80 | "pinData": {}, 81 | "versionId": "b825b174-de06-4716-b3ea-6bb0019fccd5", 82 | "meta": { 83 | "instanceId": "b825b174-de06-4716-b3ea-6bb0019fccd5" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /examples/Mastodon_node_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": [ 3 | { 4 | "parameters": { 5 | "url": "https://mastodon.social", 6 | "resource": "status", 7 | "operation": "create", 8 | "text": "Test post from n8n Mastodon node! #n8n #automation", 9 | "additionalFields": { 10 | "language": "en", 11 | "spoilerText": "Test post" 12 | } 13 | }, 14 | "name": "Create Mastodon Post", 15 | "type": "n8n-nodes-mastodon", 16 | "typeVersion": 1, 17 | "position": [500, 300], 18 | "credentials": { 19 | "mastodonOAuth2Api": { 20 | "id": null, 21 | "name": "Mastodon account" 22 | } 23 | } 24 | }, 25 | { 26 | "parameters": { 27 | "url": "https://mastodon.social", 28 | "resource": "timeline", 29 | "operation": "home" 30 | }, 31 | "name": "Get Home Timeline", 32 | "type": "n8n-nodes-mastodon", 33 | "typeVersion": 1, 34 | "position": [700, 300], 35 | "credentials": { 36 | "mastodonOAuth2Api": { 37 | "id": null, 38 | "name": "Mastodon account" 39 | } 40 | } 41 | }, 42 | { 43 | "parameters": { 44 | "url": "https://mastodon.social", 45 | "resource": "status", 46 | "operation": "search", 47 | "searchText": "#n8n", 48 | "returnAll": false, 49 | "limit": 5 50 | }, 51 | "name": "Search Posts", 52 | "type": "n8n-nodes-mastodon", 53 | "typeVersion": 1, 54 | "position": [900, 300], 55 | "credentials": { 56 | "mastodonOAuth2Api": { 57 | "id": null, 58 | "name": "Mastodon account" 59 | } 60 | } 61 | } 62 | ], 63 | "connections": { 64 | "Create Mastodon Post": { 65 | "main": [ 66 | [ 67 | { 68 | "node": "Get Home Timeline", 69 | "type": "main", 70 | "index": 0 71 | } 72 | ] 73 | ] 74 | }, 75 | "Get Home Timeline": { 76 | "main": [ 77 | [ 78 | { 79 | "node": "Search Posts", 80 | "type": "main", 81 | "index": 0 82 | } 83 | ] 84 | ] 85 | } 86 | }, 87 | "meta": { 88 | "templateCredsSetupLink": "https://docs.n8n.io/integrations/builtin/credentials/mastodon/" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const path = require('path'); 3 | const { src, dest } = gulp; 4 | 5 | const { task } = require('gulp'); 6 | 7 | task('build:icons', copyIcons); 8 | 9 | function copyIcons() { 10 | const nodeSource = path.resolve('nodes', '**', '*.{png,svg,json}'); 11 | const nodeDestination = path.resolve('dist', 'nodes'); 12 | 13 | src(nodeSource).pipe(dest(nodeDestination)); 14 | 15 | const credSource = path.resolve('credentials', '**', '*.{png,svg,json}'); 16 | const credDestination = path.resolve('dist', 'credentials'); 17 | 18 | return src(credSource).pipe(dest(credDestination)); 19 | } 20 | 21 | exports.default = gulp.series('build:icons'); 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | nodeClasses: { 3 | Mastodon: require('./dist/nodes/Mastodon/Mastodon.node').default, 4 | }, 5 | credentialClasses: { 6 | MastodonOAuth2Api: require('./dist/credentials/MastodonOAuth2Api.credentials') 7 | .MastodonOAuth2Api, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testMatch: ['**/__tests__/**/*.test.ts'], 5 | moduleFileExtensions: ['ts', 'js'], 6 | setupFilesAfterEnv: ['/jest.setup.js'], 7 | transform: { 8 | '^.+\\.ts$': [ 9 | 'ts-jest', 10 | { 11 | tsconfig: 'tsconfig.json', 12 | }, 13 | ], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | // Global Jest setup to handle RequestQueue cleanup 2 | const { RequestQueue } = require('./nodes/Mastodon/Mastodon_Methods'); 3 | 4 | // Clean up RequestQueue instances after each test to prevent Jest hanging 5 | afterEach(() => { 6 | try { 7 | const queue = RequestQueue.getInstance(); 8 | if (queue && typeof queue.destroy === 'function') { 9 | queue.destroy(); 10 | } 11 | } catch (e) { 12 | // Queue might not exist or already be destroyed 13 | } 14 | }); 15 | 16 | // Final cleanup after all tests 17 | afterAll(() => { 18 | try { 19 | const queue = RequestQueue.getInstance(); 20 | if (queue && typeof queue.destroy === 'function') { 21 | queue.destroy(); 22 | } 23 | } catch (e) { 24 | // Queue might not exist or already be destroyed 25 | } 26 | // Clear singleton instance 27 | if (RequestQueue.instance !== undefined) { 28 | RequestQueue.instance = undefined; 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /nodes/Mastodon/Mastodon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /nodes/Mastodon/account/AccountInterfaces.ts: -------------------------------------------------------------------------------- 1 | // Account-related interfaces for Mastodon node 2 | import { Role } from './RoleInterface'; 3 | 4 | export interface IAccount { 5 | id: string; 6 | username: string; 7 | acct: string; 8 | display_name: string; 9 | note: string; 10 | url: string; 11 | avatar: string; 12 | header: string; 13 | locked: boolean; 14 | bot: boolean; 15 | created_at: string; 16 | statuses_count: number; 17 | followers_count: number; 18 | following_count: number; 19 | } 20 | 21 | export interface IAccountWarning { 22 | id: string; 23 | account_id: string; 24 | content: string; 25 | created_at: string; 26 | expires_at: string | null; 27 | acknowledged: boolean; 28 | } 29 | 30 | export interface IAdminAccount { 31 | id: string; 32 | username: string; 33 | domain: string | null; 34 | created_at: string; 35 | email: string; 36 | ip: string; 37 | role: Role; 38 | confirmed: boolean; 39 | approved: boolean; 40 | disabled: boolean; 41 | silenced: boolean; 42 | suspended: boolean; 43 | } 44 | -------------------------------------------------------------------------------- /nodes/Mastodon/account/RoleInterface.ts: -------------------------------------------------------------------------------- 1 | // Role interface for Mastodon admin accounts 2 | export interface Role { 3 | id: number; 4 | name: string; 5 | color: string; 6 | position: number; 7 | permissions: number; 8 | highlighted: boolean; 9 | created_at: string; 10 | updated_at: string; 11 | } 12 | -------------------------------------------------------------------------------- /nodes/Mastodon/account/index.ts: -------------------------------------------------------------------------------- 1 | // Aggregated account module for Mastodon node 2 | import { INodeProperties } from 'n8n-workflow'; 3 | import * as Props from './AccountProperties'; 4 | import * as Methods from './AccountMethods'; 5 | 6 | export const accountProperties: INodeProperties[] = [ 7 | Props.accountOperations as INodeProperties, 8 | ...Props.accountFields, 9 | ]; 10 | 11 | export const accountMethods = { 12 | follow: Methods.follow, 13 | unfollow: Methods.unfollow, 14 | block: Methods.block, 15 | mute: Methods.mute, 16 | verifyCredentials: Methods.verifyCredentials, 17 | viewProfile: Methods.viewProfile, 18 | getAccountWarnings: Methods.getAccountWarnings, 19 | getAdminAccountInfo: Methods.getAdminAccountInfo, 20 | addNoteToAccount: Methods.addNoteToAccount, 21 | getAccountFollowers: Methods.getAccountFollowers, 22 | getAccountFollowing: Methods.getAccountFollowing, 23 | getAccountLists: Methods.getAccountLists, 24 | getAccountStatuses: Methods.getAccountStatuses, 25 | getAccountFeaturedTags: Methods.getAccountFeaturedTags, 26 | pinAccount: Methods.pinAccount, 27 | removeAccountFromFollowers: Methods.removeAccountFromFollowers, 28 | searchAccounts: Methods.searchAccounts, 29 | unmuteAccount: Methods.unmuteAccount, 30 | unpinAccount: Methods.unpinAccount, 31 | registerAccount: Methods.registerAccount, 32 | updateCredentials: Methods.updateCredentials, 33 | getMultipleAccounts: Methods.getMultipleAccounts, 34 | getRelationships: Methods.getRelationships, 35 | getFamiliarFollowers: Methods.getFamiliarFollowers, 36 | lookupAccount: Methods.lookupAccount, 37 | }; 38 | -------------------------------------------------------------------------------- /nodes/Mastodon/admin/AdminInterfaces.ts: -------------------------------------------------------------------------------- 1 | // Admin-related interfaces for Mastodon node 2 | export interface ICanonicalEmailBlock { 3 | id: string; 4 | canonical_email_hash: string; 5 | } 6 | 7 | export interface ICohortDataPoint { 8 | date: string; 9 | rate: number; 10 | value: string; 11 | } 12 | 13 | export interface ICohort { 14 | period: string; 15 | frequency: 'day' | 'month'; 16 | data: ICohortDataPoint[]; 17 | } 18 | 19 | export interface IDimension { 20 | id: string; 21 | name: string; 22 | created_at: string; 23 | updated_at: string; 24 | } 25 | 26 | export interface IAdminDomainAllow { 27 | id: string; 28 | domain: string; 29 | created_at: string; 30 | } 31 | 32 | export interface IAdminDomainBlock { 33 | id: string; 34 | domain: string; 35 | digest: string; 36 | created_at: string; 37 | severity: 'noop' | 'silence' | 'suspend'; 38 | reject_media: boolean; 39 | reject_reports: boolean; 40 | private_comment: string; 41 | public_comment: string; 42 | obfuscate: boolean; 43 | } 44 | 45 | export interface IEmailDomainBlockHistory { 46 | day: number; 47 | accounts: number; 48 | uses: number; 49 | } 50 | 51 | export interface IAdminEmailDomainBlock { 52 | id: string; 53 | domain: string; 54 | created_at: string; 55 | history: IEmailDomainBlockHistory[]; 56 | } 57 | 58 | export interface IAdminIp { 59 | id: string; 60 | ip: string; 61 | used_at: string; 62 | account_id: string; 63 | } 64 | 65 | export interface IAdminIpBlock { 66 | id: string; 67 | ip: string; 68 | severity: 'noop' | 'sign_up_requires_approval' | 'sign_up_block' | 'block'; 69 | comment: string | null; 70 | created_at: string; 71 | } 72 | 73 | export interface IAdminRole { 74 | id: string; 75 | name: string; 76 | permissions: number; 77 | color: string; 78 | highlighted: boolean; 79 | position: number; 80 | created_at: string; 81 | updated_at: string; 82 | } 83 | 84 | export interface IAdminRule { 85 | id: string; 86 | text: string; 87 | created_at: string; 88 | updated_at: string; 89 | } 90 | 91 | export interface IAdminAnnouncement { 92 | id: string; 93 | content: string; 94 | starts_at: string; 95 | ends_at: string; 96 | published: boolean; 97 | all_day: boolean; 98 | created_at: string; 99 | updated_at: string; 100 | } 101 | 102 | export interface IAdminScheduledStatus { 103 | id: string; 104 | scheduled_at: string; 105 | params: Record; 106 | media_attachments: Array>; 107 | } 108 | -------------------------------------------------------------------------------- /nodes/Mastodon/admin/canonical-email-blocks/CanonicalEmailBlockMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../../Mastodon_Methods'; 3 | import { ICanonicalEmailBlock } from '../AdminInterfaces'; 4 | 5 | export async function listBlocks( 6 | this: IExecuteFunctions, 7 | baseUrl: string, 8 | items: INodeExecutionData[], 9 | i: number, 10 | ): Promise { 11 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 12 | const qs: IDataObject = {}; 13 | 14 | if (additionalFields.limit) { 15 | qs.limit = additionalFields.limit; 16 | } 17 | if (additionalFields.max_id) { 18 | qs.max_id = additionalFields.max_id; 19 | } 20 | if (additionalFields.since_id) { 21 | qs.since_id = additionalFields.since_id; 22 | } 23 | if (additionalFields.min_id) { 24 | qs.min_id = additionalFields.min_id; 25 | } 26 | 27 | return await handleApiRequest.call( 28 | this, 29 | 'GET', 30 | `${baseUrl}/api/v1/admin/canonical_email_blocks`, 31 | {}, 32 | qs, 33 | ); 34 | } 35 | 36 | export async function getBlock( 37 | this: IExecuteFunctions, 38 | baseUrl: string, 39 | items: INodeExecutionData[], 40 | i: number, 41 | ): Promise { 42 | const blockId = this.getNodeParameter('blockId', i) as string; 43 | return await handleApiRequest.call( 44 | this, 45 | 'GET', 46 | `${baseUrl}/api/v1/admin/canonical_email_blocks/${blockId}`, 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /nodes/Mastodon/admin/canonical-email-blocks/CanonicalEmailBlockProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const canonicalEmailBlockOperations = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['canonicalEmailBlocks'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'List Blocks', 17 | value: 'listBlocks', 18 | description: 'Get a list of canonical email blocks', 19 | action: 'List canonical email blocks', 20 | }, 21 | { 22 | name: 'Get Block', 23 | value: 'getBlock', 24 | description: 'Get a specific canonical email block', 25 | action: 'Get a canonical email block', 26 | }, 27 | ], 28 | default: 'listBlocks', 29 | }, 30 | ] as INodeProperties[]; 31 | 32 | export const canonicalEmailBlockFields = [ 33 | { 34 | displayName: 'Block ID', 35 | name: 'blockId', 36 | type: 'string', 37 | required: true, 38 | default: '', 39 | displayOptions: { 40 | show: { 41 | resource: ['canonicalEmailBlocks'], 42 | operation: ['getBlock'], 43 | }, 44 | }, 45 | description: 'The ID of the canonical email block', 46 | }, 47 | { 48 | displayName: 'Additional Fields', 49 | name: 'additionalFields', 50 | type: 'collection', 51 | placeholder: 'Add Field', 52 | default: {}, 53 | displayOptions: { 54 | show: { 55 | resource: ['canonicalEmailBlocks'], 56 | operation: ['listBlocks'], 57 | }, 58 | }, 59 | options: [ 60 | { 61 | displayName: 'Limit', 62 | name: 'limit', 63 | type: 'number', 64 | default: 100, 65 | description: 'Maximum number of results to return (default 100, max 200)', 66 | }, 67 | { 68 | displayName: 'Max ID', 69 | name: 'max_id', 70 | type: 'string', 71 | default: '', 72 | description: 'Return results older than this ID', 73 | }, 74 | { 75 | displayName: 'Since ID', 76 | name: 'since_id', 77 | type: 'string', 78 | default: '', 79 | description: 'Return results newer than this ID', 80 | }, 81 | { 82 | displayName: 'Min ID', 83 | name: 'min_id', 84 | type: 'string', 85 | default: '', 86 | description: 'Return results immediately newer than this ID', 87 | }, 88 | ], 89 | }, 90 | ] as INodeProperties[]; 91 | -------------------------------------------------------------------------------- /nodes/Mastodon/admin/cohorts/CohortMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../../Mastodon_Methods'; 3 | import { ICohort } from '../AdminInterfaces'; 4 | 5 | export async function getRetentionData( 6 | this: IExecuteFunctions, 7 | baseUrl: string, 8 | items: INodeExecutionData[], 9 | i: number, 10 | ): Promise { 11 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 12 | const qs: IDataObject = {}; 13 | 14 | if (additionalFields.frequency) { 15 | qs.frequency = additionalFields.frequency; 16 | } 17 | if (additionalFields.startDate) { 18 | qs.start_at = additionalFields.startDate; 19 | } 20 | 21 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/admin/retention`, {}, qs); 22 | } 23 | -------------------------------------------------------------------------------- /nodes/Mastodon/admin/cohorts/CohortProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const cohortOperations = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['cohorts'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'Get Retention Data', 17 | value: 'getRetentionData', 18 | description: 'Get user retention metrics', 19 | action: 'Get user retention metrics', 20 | }, 21 | ], 22 | default: 'getRetentionData', 23 | }, 24 | ] as INodeProperties[]; 25 | 26 | export const cohortFields = [ 27 | { 28 | displayName: 'Additional Fields', 29 | name: 'additionalFields', 30 | type: 'collection', 31 | placeholder: 'Add Field', 32 | default: {}, 33 | displayOptions: { 34 | show: { 35 | resource: ['cohorts'], 36 | operation: ['getRetentionData'], 37 | }, 38 | }, 39 | options: [ 40 | { 41 | displayName: 'Frequency', 42 | name: 'frequency', 43 | type: 'options', 44 | options: [ 45 | { 46 | name: 'Day', 47 | value: 'day', 48 | }, 49 | { 50 | name: 'Month', 51 | value: 'month', 52 | }, 53 | ], 54 | default: 'day', 55 | description: 'Granularity of the retention data', 56 | }, 57 | { 58 | displayName: 'Start Date', 59 | name: 'startDate', 60 | type: 'dateTime', 61 | default: '', 62 | description: 'Start date for retention data', 63 | }, 64 | ], 65 | }, 66 | ] as INodeProperties[]; 67 | -------------------------------------------------------------------------------- /nodes/Mastodon/admin/dimensions/DimensionMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../../Mastodon_Methods'; 3 | import { IDimension } from '../AdminInterfaces'; 4 | 5 | export async function listAll(this: IExecuteFunctions, baseUrl: string): Promise { 6 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/admin/dimensions`); 7 | } 8 | 9 | export async function get( 10 | this: IExecuteFunctions, 11 | baseUrl: string, 12 | items: INodeExecutionData[], 13 | i: number, 14 | ): Promise { 15 | const dimensionId = this.getNodeParameter('dimensionId', i) as string; 16 | return await handleApiRequest.call( 17 | this, 18 | 'GET', 19 | `${baseUrl}/api/v1/admin/dimensions/${dimensionId}`, 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /nodes/Mastodon/admin/dimensions/DimensionProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const dimensionOperations = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['dimensions'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'List All', 17 | value: 'listAll', 18 | description: 'Get all custom dimensions', 19 | action: 'List all dimensions', 20 | }, 21 | { 22 | name: 'Get', 23 | value: 'get', 24 | description: 'Get a specific dimension', 25 | action: 'Get a dimension', 26 | }, 27 | ], 28 | default: 'listAll', 29 | }, 30 | ] as INodeProperties[]; 31 | 32 | export const dimensionFields = [ 33 | { 34 | displayName: 'Dimension ID', 35 | name: 'dimensionId', 36 | type: 'string', 37 | required: true, 38 | default: '', 39 | displayOptions: { 40 | show: { 41 | resource: ['dimensions'], 42 | operation: ['get'], 43 | }, 44 | }, 45 | description: 'ID of the dimension to retrieve', 46 | }, 47 | ] as INodeProperties[]; 48 | -------------------------------------------------------------------------------- /nodes/Mastodon/admin/ips/IpMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../../Mastodon_Methods'; 3 | import { IAdminIp } from '../AdminInterfaces'; 4 | 5 | export async function listIps( 6 | this: IExecuteFunctions, 7 | baseUrl: string, 8 | items: INodeExecutionData[], 9 | i: number, 10 | ): Promise { 11 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 12 | const qs: IDataObject = {}; 13 | if (additionalFields.limit) qs.limit = additionalFields.limit; 14 | if (additionalFields.max_id) qs.max_id = additionalFields.max_id; 15 | if (additionalFields.since_id) qs.since_id = additionalFields.since_id; 16 | if (additionalFields.min_id) qs.min_id = additionalFields.min_id; 17 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/admin/ips`, {}, qs); 18 | } 19 | 20 | export async function getIp( 21 | this: IExecuteFunctions, 22 | baseUrl: string, 23 | items: INodeExecutionData[], 24 | i: number, 25 | ): Promise { 26 | const ipRecordId = this.getNodeParameter('ipRecordId', i) as string; 27 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/admin/ips/${ipRecordId}`); 28 | } 29 | -------------------------------------------------------------------------------- /nodes/Mastodon/admin/ips/IpProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const ipOperations = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { show: { resource: ['ips'] } }, 10 | options: [ 11 | { 12 | name: 'List IPs', 13 | value: 'listIps', 14 | description: 'List IP records', 15 | action: 'List IP records', 16 | }, 17 | { 18 | name: 'Get IP', 19 | value: 'getIp', 20 | description: 'Get a single IP record by ID', 21 | action: 'Get an IP record', 22 | }, 23 | ], 24 | default: 'listIps', 25 | }, 26 | ] as INodeProperties[]; 27 | 28 | export const ipFields = [ 29 | { 30 | displayName: 'Record ID', 31 | name: 'ipRecordId', 32 | type: 'string', 33 | required: true, 34 | default: '', 35 | displayOptions: { show: { resource: ['ips'], operation: ['getIp'] } }, 36 | description: 'ID of the IP record', 37 | }, 38 | { 39 | displayName: 'Additional Fields', 40 | name: 'additionalFields', 41 | type: 'collection', 42 | placeholder: 'Add Field', 43 | default: {}, 44 | displayOptions: { show: { resource: ['ips'], operation: ['listIps'] } }, 45 | options: [ 46 | { 47 | displayName: 'Limit', 48 | name: 'limit', 49 | type: 'number', 50 | default: 100, 51 | description: 'Max results to return (default 100, max 200)', 52 | }, 53 | { 54 | displayName: 'Max ID', 55 | name: 'max_id', 56 | type: 'string', 57 | default: '', 58 | description: 'Return results older than this ID', 59 | }, 60 | { 61 | displayName: 'Since ID', 62 | name: 'since_id', 63 | type: 'string', 64 | default: '', 65 | description: 'Return results newer than this ID', 66 | }, 67 | { 68 | displayName: 'Min ID', 69 | name: 'min_id', 70 | type: 'string', 71 | default: '', 72 | description: 'Return results immediately newer than this ID', 73 | }, 74 | ], 75 | }, 76 | ] as INodeProperties[]; 77 | -------------------------------------------------------------------------------- /nodes/Mastodon/announcements/AnnouncementInterfaces.ts: -------------------------------------------------------------------------------- 1 | // Announcement-related interfaces for Mastodon node 2 | export interface IAnnouncement { 3 | id: string; 4 | content: string; 5 | starts_at: string | null; 6 | ends_at: string | null; 7 | all_day: boolean; 8 | published: boolean; 9 | read?: boolean; 10 | } 11 | -------------------------------------------------------------------------------- /nodes/Mastodon/announcements/AnnouncementMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | import { IAnnouncement } from './AnnouncementInterfaces'; 4 | 5 | /** 6 | * View Announcements 7 | * GET /api/v1/announcements 8 | */ 9 | export async function list( 10 | this: IExecuteFunctions, 11 | baseUrl: string, 12 | items: INodeExecutionData[], 13 | i: number, 14 | ): Promise { 15 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/announcements`); 16 | } 17 | -------------------------------------------------------------------------------- /nodes/Mastodon/announcements/AnnouncementProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const announcementOperations: INodeProperties = { 4 | displayName: 'Operation', 5 | name: 'operation', 6 | type: 'options', 7 | noDataExpression: true, 8 | displayOptions: { 9 | show: { 10 | resource: ['announcements'], 11 | }, 12 | }, 13 | options: [ 14 | { 15 | name: 'List', 16 | value: 'list', 17 | description: 'Get list of active announcements', 18 | action: 'List announcements', 19 | }, 20 | ], 21 | default: 'list', 22 | }; 23 | 24 | // No additional fields needed for announcements as it's a simple GET request 25 | export const announcementFields: INodeProperties[] = []; 26 | -------------------------------------------------------------------------------- /nodes/Mastodon/announcements/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './AnnouncementMethods'; 3 | import * as Properties from './AnnouncementProperties'; 4 | 5 | export const announcementProperties: INodeProperties[] = [ 6 | Properties.announcementOperations, 7 | ...Properties.announcementFields, 8 | ]; 9 | 10 | export const announcementMethods = { 11 | list: Methods.list, 12 | }; 13 | -------------------------------------------------------------------------------- /nodes/Mastodon/apps/AppsMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | 4 | export async function registerApp( 5 | this: IExecuteFunctions, 6 | baseUrl: string, 7 | items: INodeExecutionData[], 8 | i: number, 9 | ): Promise { 10 | const clientName = this.getNodeParameter('clientName', i) as string; 11 | const redirectUris = this.getNodeParameter('redirectUris', i) as string; 12 | const scopes = this.getNodeParameter('scopes', i) as string; 13 | const website = this.getNodeParameter('website', i) as string; 14 | 15 | const body: IDataObject = { 16 | client_name: clientName, 17 | redirect_uris: redirectUris, 18 | scopes, 19 | website, 20 | }; 21 | 22 | return await handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/apps`, body); 23 | } 24 | 25 | export async function verifyAppCredentials( 26 | this: IExecuteFunctions, 27 | baseUrl: string, 28 | ): Promise { 29 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/apps/verify_credentials`); 30 | } 31 | -------------------------------------------------------------------------------- /nodes/Mastodon/apps/AppsProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const appsOperations: INodeProperties = { 4 | displayName: 'Operation', 5 | name: 'operation', 6 | type: 'options', 7 | noDataExpression: true, 8 | displayOptions: { show: { resource: ['apps'] } }, 9 | options: [ 10 | { 11 | name: 'Register Application', 12 | value: 'registerApp', 13 | description: 'Register a new OAuth application', 14 | action: 'Register application', 15 | }, 16 | { 17 | name: 'Verify Credentials', 18 | value: 'verifyCredentials', 19 | description: "Verify application's OAuth credentials", 20 | action: 'Verify application credentials', 21 | }, 22 | ], 23 | default: 'registerApp', 24 | }; 25 | 26 | export const appsFields: INodeProperties[] = [ 27 | { 28 | displayName: 'Client Name', 29 | name: 'clientName', 30 | type: 'string', 31 | required: true, 32 | default: '', 33 | displayOptions: { show: { resource: ['apps'], operation: ['registerApp'] } }, 34 | description: 'Name of your application', 35 | }, 36 | { 37 | displayName: 'Redirect URIs', 38 | name: 'redirectUris', 39 | type: 'string', 40 | required: true, 41 | default: 'urn:ietf:wg:oauth:2.0:oob', 42 | displayOptions: { show: { resource: ['apps'], operation: ['registerApp'] } }, 43 | description: 'Where the user will be redirected after authorization', 44 | }, 45 | { 46 | displayName: 'Scopes', 47 | name: 'scopes', 48 | type: 'string', 49 | required: true, 50 | default: 'read write push', 51 | displayOptions: { show: { resource: ['apps'], operation: ['registerApp'] } }, 52 | description: 'Space-separated list of scopes to request', 53 | }, 54 | { 55 | displayName: 'Website', 56 | name: 'website', 57 | type: 'string', 58 | default: '', 59 | displayOptions: { show: { resource: ['apps'], operation: ['registerApp'] } }, 60 | description: 'Website URL of your application', 61 | }, 62 | ]; 63 | -------------------------------------------------------------------------------- /nodes/Mastodon/apps/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './AppsMethods'; 3 | import { appsOperations, appsFields } from './AppsProperties'; 4 | 5 | export const appsProperties: INodeProperties[] = [appsOperations, ...appsFields]; 6 | 7 | export const appsMethods = { 8 | registerApp: Methods.registerApp, 9 | verifyCredentials: Methods.verifyAppCredentials, 10 | }; 11 | -------------------------------------------------------------------------------- /nodes/Mastodon/authentication/AuthenticationProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const authenticationOperations = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { show: { resource: ['authentication'] } }, 10 | options: [ 11 | { 12 | name: 'Register Application', 13 | value: 'registerApp', 14 | description: 'Register a new OAuth application', 15 | action: 'Register application', 16 | }, 17 | { 18 | name: 'Obtain Token', 19 | value: 'obtainToken', 20 | description: 'Exchange authorization code for access token', 21 | action: 'Obtain access token', 22 | }, 23 | { 24 | name: 'Revoke Token', 25 | value: 'revokeToken', 26 | description: 'Revoke an OAuth access token', 27 | action: 'Revoke access token', 28 | }, 29 | ], 30 | default: 'registerApp', 31 | }, 32 | ] as INodeProperties[]; 33 | 34 | export const authenticationFields = [ 35 | { 36 | displayName: 'Client Name', 37 | name: 'clientName', 38 | type: 'string', 39 | required: true, 40 | default: '', 41 | displayOptions: { show: { resource: ['authentication'], operation: ['registerApp'] } }, 42 | description: 'Name of your application', 43 | }, 44 | { 45 | displayName: 'Redirect URIs', 46 | name: 'redirectUris', 47 | type: 'string', 48 | required: true, 49 | default: 'urn:ietf:wg:oauth:2.0:oob', 50 | displayOptions: { show: { resource: ['authentication'], operation: ['registerApp'] } }, 51 | description: 'Where the user should be redirected after authorization', 52 | }, 53 | { 54 | displayName: 'Scopes', 55 | name: 'scopes', 56 | type: 'string', 57 | required: true, 58 | default: 'read write', 59 | displayOptions: { show: { resource: ['authentication'], operation: ['registerApp'] } }, 60 | description: 'Space-separated list of scopes', 61 | }, 62 | { 63 | displayName: 'Website', 64 | name: 'website', 65 | type: 'string', 66 | required: false, 67 | default: '', 68 | displayOptions: { show: { resource: ['authentication'], operation: ['registerApp'] } }, 69 | description: 'URL of your website', 70 | }, 71 | { 72 | displayName: 'Client ID', 73 | name: 'clientId', 74 | type: 'string', 75 | required: true, 76 | default: '', 77 | displayOptions: { 78 | show: { resource: ['authentication'], operation: ['obtainToken', 'revokeToken'] }, 79 | }, 80 | description: 'OAuth client ID', 81 | }, 82 | { 83 | displayName: 'Client Secret', 84 | name: 'clientSecret', 85 | type: 'string', 86 | typeOptions: { password: true }, 87 | required: true, 88 | default: '', 89 | displayOptions: { 90 | show: { resource: ['authentication'], operation: ['obtainToken', 'revokeToken'] }, 91 | }, 92 | description: 'OAuth client secret', 93 | }, 94 | { 95 | displayName: 'Authorization Code', 96 | name: 'code', 97 | type: 'string', 98 | required: true, 99 | default: '', 100 | displayOptions: { show: { resource: ['authentication'], operation: ['obtainToken'] } }, 101 | description: 'Code received from OAuth authorization', 102 | }, 103 | { 104 | displayName: 'Redirect URI', 105 | name: 'redirectUri', 106 | type: 'string', 107 | required: true, 108 | default: 'urn:ietf:wg:oauth:2.0:oob', 109 | displayOptions: { show: { resource: ['authentication'], operation: ['obtainToken'] } }, 110 | description: 'Redirect URI matching app registration', 111 | }, 112 | { 113 | displayName: 'Token', 114 | name: 'token', 115 | type: 'string', 116 | required: true, 117 | default: '', 118 | displayOptions: { show: { resource: ['authentication'], operation: ['revokeToken'] } }, 119 | description: 'OAuth token to revoke', 120 | }, 121 | ] as INodeProperties[]; 122 | -------------------------------------------------------------------------------- /nodes/Mastodon/authentication/index.ts: -------------------------------------------------------------------------------- 1 | import { authenticationMethods } from './AuthenticationMethods'; 2 | import { authenticationOperations, authenticationFields } from './AuthenticationProperties'; 3 | import { INodeProperties } from 'n8n-workflow'; 4 | 5 | export const authenticationProperties: INodeProperties[] = [ 6 | ...authenticationOperations, 7 | ...authenticationFields, 8 | ]; 9 | 10 | export { authenticationMethods }; 11 | -------------------------------------------------------------------------------- /nodes/Mastodon/blocks/BlocksMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | 4 | /** 5 | * Blocks an account by ID 6 | */ 7 | export async function block( 8 | this: IExecuteFunctions, 9 | baseUrl: string, 10 | items: INodeExecutionData[], 11 | i: number, 12 | ): Promise { 13 | const accountId = this.getNodeParameter('accountId', i) as string; 14 | return await handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/accounts/${accountId}/block`); 15 | } 16 | 17 | /** 18 | * Unblocks an account by ID 19 | */ 20 | export async function unblock( 21 | this: IExecuteFunctions, 22 | baseUrl: string, 23 | items: INodeExecutionData[], 24 | i: number, 25 | ): Promise { 26 | const accountId = this.getNodeParameter('accountId', i) as string; 27 | return await handleApiRequest.call( 28 | this, 29 | 'POST', 30 | `${baseUrl}/api/v1/accounts/${accountId}/unblock`, 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /nodes/Mastodon/blocks/BlocksProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const blocksOperations: INodeProperties = { 4 | displayName: 'Operation', 5 | name: 'operation', 6 | type: 'options', 7 | noDataExpression: true, 8 | displayOptions: { show: { resource: ['blocks'] } }, 9 | options: [ 10 | { 11 | name: 'Block Account', 12 | value: 'block', 13 | description: 'Block an account', 14 | action: 'Block an account', 15 | }, 16 | { 17 | name: 'Unblock Account', 18 | value: 'unblock', 19 | description: 'Unblock an account', 20 | action: 'Unblock an account', 21 | }, 22 | ], 23 | default: 'block', 24 | }; 25 | 26 | export const blocksFields: INodeProperties[] = [ 27 | { 28 | displayName: 'Account ID', 29 | name: 'accountId', 30 | type: 'string', 31 | required: true, 32 | default: '', 33 | displayOptions: { show: { resource: ['blocks'], operation: ['block', 'unblock'] } }, 34 | description: 'ID of the account to block/unblock', 35 | }, 36 | ]; 37 | -------------------------------------------------------------------------------- /nodes/Mastodon/blocks/index.ts: -------------------------------------------------------------------------------- 1 | // Aggregated blocks module for Mastodon node 2 | import { INodeProperties } from 'n8n-workflow'; 3 | import * as Methods from './BlocksMethods'; 4 | import * as Properties from './BlocksProperties'; 5 | 6 | export const blocksProperties: INodeProperties[] = [ 7 | Properties.blocksOperations, 8 | ...Properties.blocksFields, 9 | ]; 10 | 11 | export const blocksMethods = { 12 | block: Methods.block, 13 | unblock: Methods.unblock, 14 | }; 15 | -------------------------------------------------------------------------------- /nodes/Mastodon/bookmarks/BookmarksMethods.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IExecuteFunctions, 3 | INodeExecutionData, 4 | IDataObject, 5 | NodeOperationError, 6 | } from 'n8n-workflow'; 7 | import { handleApiRequest } from '../Mastodon_Methods'; 8 | 9 | export async function getBookmarks( 10 | this: IExecuteFunctions, 11 | baseUrl: string, 12 | items: INodeExecutionData[], 13 | i: number, 14 | ): Promise { 15 | const qs: IDataObject = {}; 16 | const maxId = this.getNodeParameter('max_id', i, '') as string; 17 | const sinceId = this.getNodeParameter('since_id', i, '') as string; 18 | const minId = this.getNodeParameter('min_id', i, '') as string; 19 | const limit = this.getNodeParameter('limit', i, 20) as number; 20 | 21 | if (maxId) qs.max_id = maxId; 22 | if (sinceId) qs.since_id = sinceId; 23 | if (minId) qs.min_id = minId; 24 | if (limit) qs.limit = Math.min(limit, 40); 25 | 26 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/bookmarks`, {}, qs); 27 | } 28 | 29 | export async function addBookmark( 30 | this: IExecuteFunctions, 31 | baseUrl: string, 32 | items: INodeExecutionData[], 33 | i: number, 34 | ): Promise { 35 | const statusId = this.getNodeParameter('statusId', i) as string; 36 | if (!statusId) { 37 | throw new NodeOperationError(this.getNode(), 'Status ID is required to add a bookmark'); 38 | } 39 | return await handleApiRequest.call( 40 | this, 41 | 'POST', 42 | `${baseUrl}/api/v1/statuses/${statusId}/bookmark`, 43 | ); 44 | } 45 | 46 | export async function removeBookmark( 47 | this: IExecuteFunctions, 48 | baseUrl: string, 49 | items: INodeExecutionData[], 50 | i: number, 51 | ): Promise { 52 | const statusId = this.getNodeParameter('statusId', i) as string; 53 | if (!statusId) { 54 | throw new NodeOperationError(this.getNode(), 'Status ID is required to remove a bookmark'); 55 | } 56 | return await handleApiRequest.call( 57 | this, 58 | 'POST', 59 | `${baseUrl}/api/v1/statuses/${statusId}/unbookmark`, 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /nodes/Mastodon/bookmarks/BookmarksProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const bookmarksOperations: INodeProperties = { 4 | displayName: 'Operation', 5 | name: 'operation', 6 | type: 'options', 7 | noDataExpression: true, 8 | displayOptions: { show: { resource: ['bookmarks'] } }, 9 | options: [ 10 | { 11 | name: 'Get Bookmarked Statuses', 12 | value: 'get', 13 | description: 'Fetch bookmarked statuses', 14 | action: 'Get bookmarked statuses', 15 | }, 16 | { 17 | name: 'Add Bookmark', 18 | value: 'addBookmark', 19 | description: 'Bookmark a status', 20 | action: 'Add bookmark', 21 | }, 22 | { 23 | name: 'Remove Bookmark', 24 | value: 'removeBookmark', 25 | description: 'Remove bookmark from a status', 26 | action: 'Remove bookmark', 27 | }, 28 | ], 29 | default: 'get', 30 | }; 31 | 32 | export const bookmarksFields: INodeProperties[] = [ 33 | // For get 34 | { 35 | displayName: 'Max ID', 36 | name: 'max_id', 37 | type: 'string', 38 | default: '', 39 | displayOptions: { show: { resource: ['bookmarks'], operation: ['get'] } }, 40 | description: 'Return results older than this ID', 41 | }, 42 | { 43 | displayName: 'Since ID', 44 | name: 'since_id', 45 | type: 'string', 46 | default: '', 47 | displayOptions: { show: { resource: ['bookmarks'], operation: ['get'] } }, 48 | description: 'Return results newer than this ID', 49 | }, 50 | { 51 | displayName: 'Min ID', 52 | name: 'min_id', 53 | type: 'string', 54 | default: '', 55 | displayOptions: { show: { resource: ['bookmarks'], operation: ['get'] } }, 56 | description: 'Return results immediately newer than this ID', 57 | }, 58 | { 59 | displayName: 'Limit', 60 | name: 'limit', 61 | type: 'number', 62 | default: 20, 63 | typeOptions: { minValue: 1, maxValue: 40 }, 64 | displayOptions: { show: { resource: ['bookmarks'], operation: ['get'] } }, 65 | description: 'Max results to return', 66 | }, 67 | // For add/remove 68 | { 69 | displayName: 'Status ID', 70 | name: 'statusId', 71 | type: 'string', 72 | required: true, 73 | default: '', 74 | displayOptions: { 75 | show: { resource: ['bookmarks'], operation: ['addBookmark', 'removeBookmark'] }, 76 | }, 77 | description: 'ID of the status to (un)bookmark', 78 | }, 79 | ]; 80 | -------------------------------------------------------------------------------- /nodes/Mastodon/bookmarks/index.ts: -------------------------------------------------------------------------------- 1 | // Aggregated bookmarks module for Mastodon node 2 | import { INodeProperties } from 'n8n-workflow'; 3 | import * as Props from './BookmarksProperties'; 4 | import * as Methods from './BookmarksMethods'; 5 | 6 | export const bookmarksProperties: INodeProperties[] = [ 7 | Props.bookmarksOperations as INodeProperties, 8 | ...Props.bookmarksFields, 9 | ]; 10 | 11 | export const bookmarksMethods = { 12 | getBookmarks: Methods.getBookmarks, 13 | addBookmark: Methods.addBookmark, 14 | removeBookmark: Methods.removeBookmark, 15 | }; 16 | -------------------------------------------------------------------------------- /nodes/Mastodon/conversations/ConversationInterfaces.ts: -------------------------------------------------------------------------------- 1 | export interface IConversation { 2 | id: string; 3 | accounts: any[]; // Replace with IAccount[] if needed 4 | unread: boolean; 5 | last_status?: any; // Replace with IStatus if needed 6 | } 7 | -------------------------------------------------------------------------------- /nodes/Mastodon/conversations/ConversationMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | import { IConversation } from './ConversationInterfaces'; 4 | 5 | /** 6 | * Get all conversations 7 | * GET /api/v1/conversations 8 | * OAuth Scope: read:statuses 9 | */ 10 | export async function getConversations( 11 | this: IExecuteFunctions, 12 | baseUrl: string, 13 | items: INodeExecutionData[], 14 | i: number, 15 | ): Promise { 16 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 17 | const qs: IDataObject = {}; 18 | 19 | if (additionalFields.max_id) { 20 | qs.max_id = additionalFields.max_id; 21 | } 22 | if (additionalFields.since_id) { 23 | qs.since_id = additionalFields.since_id; 24 | } 25 | if (additionalFields.min_id) { 26 | qs.min_id = additionalFields.min_id; 27 | } 28 | if (additionalFields.limit) { 29 | qs.limit = Math.min(additionalFields.limit as number, 40); 30 | } 31 | 32 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/conversations`, {}, qs); 33 | } 34 | 35 | /** 36 | * Remove conversation 37 | * DELETE /api/v1/conversations/:id 38 | * OAuth Scope: write:conversations 39 | */ 40 | export async function removeConversation( 41 | this: IExecuteFunctions, 42 | baseUrl: string, 43 | items: INodeExecutionData[], 44 | i: number, 45 | ): Promise<{}> { 46 | const conversationId = this.getNodeParameter('conversationId', i) as string; 47 | return await handleApiRequest.call( 48 | this, 49 | 'DELETE', 50 | `${baseUrl}/api/v1/conversations/${conversationId}`, 51 | ); 52 | } 53 | 54 | /** 55 | * Mark conversation as read 56 | * POST /api/v1/conversations/:id/read 57 | * OAuth Scope: write:conversations 58 | */ 59 | export async function markAsRead( 60 | this: IExecuteFunctions, 61 | baseUrl: string, 62 | items: INodeExecutionData[], 63 | i: number, 64 | ): Promise<{}> { 65 | const conversationId = this.getNodeParameter('conversationId', i) as string; 66 | return await handleApiRequest.call( 67 | this, 68 | 'POST', 69 | `${baseUrl}/api/v1/conversations/${conversationId}/read`, 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /nodes/Mastodon/conversations/ConversationProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const conversationOperations: INodeProperties[] = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['conversations'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'Get All Conversations', 17 | value: 'getConversations', 18 | description: 'Get all conversations', 19 | action: 'Get all conversations', 20 | }, 21 | { 22 | name: 'Remove Conversation', 23 | value: 'removeConversation', 24 | description: 'Remove a conversation', 25 | action: 'Remove a conversation', 26 | }, 27 | { 28 | name: 'Mark as Read', 29 | value: 'markAsRead', 30 | description: 'Mark a conversation as read', 31 | action: 'Mark a conversation as read', 32 | }, 33 | ], 34 | default: 'getConversations', 35 | }, 36 | ]; 37 | 38 | export const conversationFields: INodeProperties[] = [ 39 | { 40 | displayName: 'Conversation ID', 41 | name: 'conversationId', 42 | type: 'string', 43 | required: true, 44 | displayOptions: { 45 | show: { 46 | resource: ['conversations'], 47 | operation: ['removeConversation', 'markAsRead'], 48 | }, 49 | }, 50 | default: '', 51 | description: 'The ID of the conversation', 52 | }, 53 | { 54 | displayName: 'Additional Fields', 55 | name: 'additionalFields', 56 | type: 'collection', 57 | placeholder: 'Add Field', 58 | default: {}, 59 | displayOptions: { 60 | show: { 61 | resource: ['conversations'], 62 | operation: ['getConversations'], 63 | }, 64 | }, 65 | options: [ 66 | { 67 | displayName: 'Max ID', 68 | name: 'max_id', 69 | type: 'string', 70 | default: '', 71 | description: 'Return results older than this ID', 72 | }, 73 | { 74 | displayName: 'Since ID', 75 | name: 'since_id', 76 | type: 'string', 77 | default: '', 78 | description: 'Return results newer than this ID', 79 | }, 80 | { 81 | displayName: 'Min ID', 82 | name: 'min_id', 83 | type: 'string', 84 | default: '', 85 | description: 'Return results immediately newer than this ID', 86 | }, 87 | { 88 | displayName: 'Limit', 89 | name: 'limit', 90 | type: 'number', 91 | default: 20, 92 | description: 'Maximum number of results to return (Max: 40)', 93 | }, 94 | ], 95 | }, 96 | ]; 97 | -------------------------------------------------------------------------------- /nodes/Mastodon/conversations/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './ConversationMethods'; 3 | import * as Properties from './ConversationProperties'; 4 | 5 | export const conversationProperties: INodeProperties[] = [ 6 | ...Properties.conversationOperations, 7 | ...Properties.conversationFields, 8 | ]; 9 | 10 | export const conversationMethods = { 11 | getConversations: Methods.getConversations, 12 | removeConversation: Methods.removeConversation, 13 | markAsRead: Methods.markAsRead, 14 | }; 15 | -------------------------------------------------------------------------------- /nodes/Mastodon/customEmojis/CustomEmojisInterfaces.ts: -------------------------------------------------------------------------------- 1 | // Custom Emojis-related interfaces for Mastodon node 2 | export interface ICustomEmoji { 3 | shortcode: string; 4 | url: string; 5 | static_url: string; 6 | visible_in_picker: boolean; 7 | category?: string; 8 | } 9 | -------------------------------------------------------------------------------- /nodes/Mastodon/customEmojis/CustomEmojisMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | import { ICustomEmoji } from './CustomEmojisInterfaces'; 4 | 5 | /** 6 | * List Custom Emojis 7 | * GET /api/v1/custom_emojis 8 | */ 9 | export async function list( 10 | this: IExecuteFunctions, 11 | baseUrl: string, 12 | items: INodeExecutionData[], 13 | i: number, 14 | ): Promise { 15 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/custom_emojis`); 16 | } 17 | -------------------------------------------------------------------------------- /nodes/Mastodon/customEmojis/CustomEmojisProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const customEmojisOperations: INodeProperties = { 4 | displayName: 'Operation', 5 | name: 'operation', 6 | type: 'options', 7 | noDataExpression: true, 8 | displayOptions: { 9 | show: { 10 | resource: ['customEmojis'], 11 | }, 12 | }, 13 | options: [ 14 | { 15 | name: 'List', 16 | value: 'list', 17 | description: 'Get list of custom emojis', 18 | action: 'List custom emojis', 19 | }, 20 | ], 21 | default: 'list', 22 | }; 23 | 24 | // No additional fields needed for custom emojis as it's a simple GET request 25 | export const customEmojisFields: INodeProperties[] = []; 26 | -------------------------------------------------------------------------------- /nodes/Mastodon/customEmojis/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './CustomEmojisMethods'; 3 | import * as Properties from './CustomEmojisProperties'; 4 | 5 | export const customEmojisProperties: INodeProperties[] = [ 6 | Properties.customEmojisOperations, 7 | ...Properties.customEmojisFields, 8 | ]; 9 | 10 | export const customEmojisMethods = { 11 | list: Methods.list, 12 | }; 13 | -------------------------------------------------------------------------------- /nodes/Mastodon/directory/DirectoryInterfaces.ts: -------------------------------------------------------------------------------- 1 | // Directory-related interfaces for Mastodon node 2 | import { IAccount } from '../account/AccountInterfaces'; 3 | 4 | export interface IDirectoryParams { 5 | limit?: number; 6 | offset?: number; 7 | order?: 'active' | 'new'; 8 | local?: boolean; 9 | [key: string]: any; 10 | } 11 | 12 | export type IDirectory = IAccount[]; 13 | -------------------------------------------------------------------------------- /nodes/Mastodon/directory/DirectoryMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | import { IAccount } from '../account/AccountInterfaces'; 4 | 5 | /** 6 | * View accounts in directory 7 | * GET /api/v1/directory 8 | */ 9 | export async function view( 10 | this: IExecuteFunctions, 11 | baseUrl: string, 12 | items: INodeExecutionData[], 13 | i: number, 14 | ): Promise { 15 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 16 | const qs: IDataObject = {}; 17 | 18 | if (additionalFields.limit) { 19 | qs.limit = Math.min(additionalFields.limit as number, 80); 20 | } 21 | if (additionalFields.offset) { 22 | qs.offset = additionalFields.offset; 23 | } 24 | if (additionalFields.order) { 25 | qs.order = additionalFields.order; 26 | } 27 | if (additionalFields.local !== undefined) { 28 | qs.local = additionalFields.local; 29 | } 30 | 31 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/directory`, {}, qs); 32 | } 33 | -------------------------------------------------------------------------------- /nodes/Mastodon/directory/DirectoryProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const directoryOperations: INodeProperties = { 4 | displayName: 'Operation', 5 | name: 'operation', 6 | type: 'options', 7 | noDataExpression: true, 8 | displayOptions: { 9 | show: { 10 | resource: ['directory'], 11 | }, 12 | }, 13 | options: [ 14 | { 15 | name: 'View', 16 | value: 'view', 17 | description: 'View accounts in directory', 18 | action: 'View directory accounts', 19 | }, 20 | ], 21 | default: 'view', 22 | }; 23 | 24 | export const directoryFields: INodeProperties[] = [ 25 | { 26 | displayName: 'Additional Fields', 27 | name: 'additionalFields', 28 | type: 'collection', 29 | placeholder: 'Add Field', 30 | default: {}, 31 | displayOptions: { 32 | show: { 33 | resource: ['directory'], 34 | operation: ['view'], 35 | }, 36 | }, 37 | options: [ 38 | { 39 | displayName: 'Limit', 40 | name: 'limit', 41 | type: 'number', 42 | default: 40, 43 | description: 'Maximum number of results to return (max: 80)', 44 | }, 45 | { 46 | displayName: 'Offset', 47 | name: 'offset', 48 | type: 'number', 49 | default: 0, 50 | description: 'Number of accounts to skip before returning results', 51 | }, 52 | { 53 | displayName: 'Order', 54 | name: 'order', 55 | type: 'options', 56 | options: [ 57 | { 58 | name: 'Active', 59 | value: 'active', 60 | description: 'Order by activity', 61 | }, 62 | { 63 | name: 'New', 64 | value: 'new', 65 | description: 'Order by new accounts', 66 | }, 67 | ], 68 | default: 'active', 69 | description: 'Order of results', 70 | }, 71 | { 72 | displayName: 'Local Only', 73 | name: 'local', 74 | type: 'boolean', 75 | default: false, 76 | description: 'Return only local accounts', 77 | }, 78 | ], 79 | }, 80 | ]; 81 | 82 | export interface IDirectoryParams { 83 | limit?: number; 84 | offset?: number; 85 | order?: 'active' | 'new'; 86 | local?: boolean; 87 | [key: string]: any; 88 | } 89 | -------------------------------------------------------------------------------- /nodes/Mastodon/directory/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './DirectoryMethods'; 3 | import * as Properties from './DirectoryProperties'; 4 | 5 | export const directoryProperties: INodeProperties[] = [ 6 | Properties.directoryOperations, 7 | ...Properties.directoryFields, 8 | ]; 9 | 10 | export const directoryMethods = { 11 | view: Methods.view, 12 | }; 13 | -------------------------------------------------------------------------------- /nodes/Mastodon/domainBlocks/DomainBlocksMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | 4 | /** 5 | * Blocks a domain 6 | * POST /api/v1/domain_blocks 7 | */ 8 | export async function block( 9 | this: IExecuteFunctions, 10 | baseUrl: string, 11 | items: INodeExecutionData[], 12 | i: number, 13 | ): Promise<{}> { 14 | const domain = this.getNodeParameter('domain', i) as string; 15 | return await handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/domain_blocks`, { domain }); 16 | } 17 | 18 | /** 19 | * Unblocks a domain 20 | * DELETE /api/v1/domain_blocks 21 | */ 22 | export async function unblock( 23 | this: IExecuteFunctions, 24 | baseUrl: string, 25 | items: INodeExecutionData[], 26 | i: number, 27 | ): Promise<{}> { 28 | const domain = this.getNodeParameter('domain', i) as string; 29 | return await handleApiRequest.call(this, 'DELETE', `${baseUrl}/api/v1/domain_blocks`, { domain }); 30 | } 31 | -------------------------------------------------------------------------------- /nodes/Mastodon/domainBlocks/DomainBlocksProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const domainBlocksOperations = { 4 | displayName: 'Operation', 5 | name: 'operation', 6 | type: 'options', 7 | noDataExpression: true, 8 | displayOptions: { show: { resource: ['domainBlocks'] } }, 9 | options: [ 10 | { 11 | name: 'Block Domain', 12 | value: 'block', 13 | description: 'Block a domain', 14 | action: 'Block a domain', 15 | }, 16 | { 17 | name: 'Unblock Domain', 18 | value: 'unblock', 19 | description: 'Unblock a domain', 20 | action: 'Unblock a domain', 21 | }, 22 | ], 23 | default: 'block', 24 | } as INodeProperties; 25 | 26 | export const domainBlocksFields = [ 27 | { 28 | displayName: 'Domain', 29 | name: 'domain', 30 | type: 'string', 31 | required: true, 32 | default: '', 33 | displayOptions: { show: { resource: ['domainBlocks'], operation: ['block', 'unblock'] } }, 34 | description: 'The domain to block or unblock', 35 | }, 36 | ] as INodeProperties[]; 37 | -------------------------------------------------------------------------------- /nodes/Mastodon/domainBlocks/index.ts: -------------------------------------------------------------------------------- 1 | // Aggregated domain blocks module for Mastodon node 2 | import { INodeProperties } from 'n8n-workflow'; 3 | import * as Methods from './DomainBlocksMethods'; 4 | import * as Properties from './DomainBlocksProperties'; 5 | 6 | export const domainBlocksProperties: INodeProperties[] = [ 7 | Properties.domainBlocksOperations, 8 | ...Properties.domainBlocksFields, 9 | ]; 10 | 11 | export const domainBlocksMethods = { 12 | block: Methods.block, 13 | unblock: Methods.unblock, 14 | }; 15 | -------------------------------------------------------------------------------- /nodes/Mastodon/domains/allowed/AllowedDomainMethods.ts: -------------------------------------------------------------------------------- 1 | // Modularized Allowed Domain methods for Mastodon node 2 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 3 | import { handleApiRequest } from '../../Mastodon_Methods'; 4 | import { IAdminDomainAllow } from '../../admin/AdminInterfaces'; 5 | 6 | export async function listAllowedDomains( 7 | this: IExecuteFunctions, 8 | baseUrl: string, 9 | items: INodeExecutionData[], 10 | i: number, 11 | ): Promise { 12 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 13 | const qs: IDataObject = {}; 14 | 15 | if (additionalFields.limit) { 16 | qs.limit = additionalFields.limit; 17 | } 18 | if (additionalFields.max_id) { 19 | qs.max_id = additionalFields.max_id; 20 | } 21 | if (additionalFields.since_id) { 22 | qs.since_id = additionalFields.since_id; 23 | } 24 | if (additionalFields.min_id) { 25 | qs.min_id = additionalFields.min_id; 26 | } 27 | 28 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/admin/domain_allows`, {}, qs); 29 | } 30 | 31 | export async function getAllowedDomain( 32 | this: IExecuteFunctions, 33 | baseUrl: string, 34 | items: INodeExecutionData[], 35 | i: number, 36 | ): Promise { 37 | const domainId = this.getNodeParameter('domainId', i) as string; 38 | return await handleApiRequest.call( 39 | this, 40 | 'GET', 41 | `${baseUrl}/api/v1/admin/domain_allows/${domainId}`, 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /nodes/Mastodon/domains/allowed/AllowedDomainProperties.ts: -------------------------------------------------------------------------------- 1 | // Modularized Allowed Domain properties for Mastodon node 2 | import { INodeProperties } from 'n8n-workflow'; 3 | 4 | export const allowedDomainOperations = [ 5 | { 6 | displayName: 'Operation', 7 | name: 'operation', 8 | type: 'options', 9 | noDataExpression: true, 10 | displayOptions: { 11 | show: { 12 | resource: ['allowedDomains'], 13 | }, 14 | }, 15 | options: [ 16 | { 17 | name: 'List Allowed Domains', 18 | value: 'listAllowedDomains', 19 | description: 'Get a list of domains allowed to federate', 20 | action: 'List allowed domains', 21 | }, 22 | { 23 | name: 'Get Allowed Domain', 24 | value: 'getAllowedDomain', 25 | description: 'Get a specific allowed domain by ID', 26 | action: 'Get an allowed domain', 27 | }, 28 | ], 29 | default: 'listAllowedDomains', 30 | }, 31 | ] as INodeProperties[]; 32 | 33 | export const allowedDomainFields = [ 34 | { 35 | displayName: 'Domain ID', 36 | name: 'domainId', 37 | type: 'string', 38 | required: true, 39 | default: '', 40 | displayOptions: { 41 | show: { 42 | resource: ['allowedDomains'], 43 | operation: ['getAllowedDomain'], 44 | }, 45 | }, 46 | description: 'The ID of the allowed domain', 47 | }, 48 | { 49 | displayName: 'Additional Fields', 50 | name: 'additionalFields', 51 | type: 'collection', 52 | placeholder: 'Add Field', 53 | default: {}, 54 | displayOptions: { 55 | show: { 56 | resource: ['allowedDomains'], 57 | operation: ['listAllowedDomains'], 58 | }, 59 | }, 60 | options: [ 61 | { 62 | displayName: 'Limit', 63 | name: 'limit', 64 | type: 'number', 65 | default: 100, 66 | description: 'Maximum number of results to return (default 100, max 200)', 67 | }, 68 | { 69 | displayName: 'Max ID', 70 | name: 'max_id', 71 | type: 'string', 72 | default: '', 73 | description: 'Return results older than this ID', 74 | }, 75 | { 76 | displayName: 'Since ID', 77 | name: 'since_id', 78 | type: 'string', 79 | default: '', 80 | description: 'Return results newer than this ID', 81 | }, 82 | { 83 | displayName: 'Min ID', 84 | name: 'min_id', 85 | type: 'string', 86 | default: '', 87 | description: 'Return results immediately newer than this ID', 88 | }, 89 | ], 90 | }, 91 | ] as INodeProperties[]; 92 | -------------------------------------------------------------------------------- /nodes/Mastodon/domains/blocked/BlockedDomainMethods.ts: -------------------------------------------------------------------------------- 1 | // Modularized Blocked Domain methods for Mastodon node 2 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 3 | import { handleApiRequest } from '../../Mastodon_Methods'; 4 | import { IAdminDomainBlock } from '../../admin/AdminInterfaces'; 5 | 6 | export async function listBlockedDomains( 7 | this: IExecuteFunctions, 8 | baseUrl: string, 9 | items: INodeExecutionData[], 10 | i: number, 11 | ): Promise { 12 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 13 | const qs: IDataObject = {}; 14 | 15 | if (additionalFields.limit) { 16 | qs.limit = additionalFields.limit; 17 | } 18 | if (additionalFields.max_id) { 19 | qs.max_id = additionalFields.max_id; 20 | } 21 | if (additionalFields.since_id) { 22 | qs.since_id = additionalFields.since_id; 23 | } 24 | if (additionalFields.min_id) { 25 | qs.min_id = additionalFields.min_id; 26 | } 27 | 28 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/admin/domain_blocks`, {}, qs); 29 | } 30 | 31 | export async function getBlockedDomain( 32 | this: IExecuteFunctions, 33 | baseUrl: string, 34 | items: INodeExecutionData[], 35 | i: number, 36 | ): Promise { 37 | const domainId = this.getNodeParameter('domainId', i) as string; 38 | return await handleApiRequest.call( 39 | this, 40 | 'GET', 41 | `${baseUrl}/api/v1/admin/domain_blocks/${domainId}`, 42 | ); 43 | } 44 | 45 | export async function blockDomain( 46 | this: IExecuteFunctions, 47 | baseUrl: string, 48 | items: INodeExecutionData[], 49 | i: number, 50 | ): Promise { 51 | const domain = this.getNodeParameter('domain', i) as string; 52 | const blockOptions = this.getNodeParameter('blockOptions', i) as IDataObject; 53 | 54 | const body: IDataObject = { domain }; 55 | 56 | if (blockOptions.severity) { 57 | body.severity = blockOptions.severity; 58 | } 59 | if (blockOptions.reject_media !== undefined) { 60 | body.reject_media = blockOptions.reject_media; 61 | } 62 | if (blockOptions.reject_reports !== undefined) { 63 | body.reject_reports = blockOptions.reject_reports; 64 | } 65 | if (blockOptions.private_comment) { 66 | body.private_comment = blockOptions.private_comment; 67 | } 68 | if (blockOptions.public_comment) { 69 | body.public_comment = blockOptions.public_comment; 70 | } 71 | if (blockOptions.obfuscate !== undefined) { 72 | body.obfuscate = blockOptions.obfuscate; 73 | } 74 | 75 | return await handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/admin/domain_blocks`, body); 76 | } 77 | -------------------------------------------------------------------------------- /nodes/Mastodon/domains/email-blocked/EmailBlockedDomainMethods.ts: -------------------------------------------------------------------------------- 1 | // Modularized Email Blocked Domain methods for Mastodon node 2 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 3 | import { handleApiRequest } from '../../Mastodon_Methods'; 4 | import { IAdminEmailDomainBlock } from '../../admin/AdminInterfaces'; 5 | 6 | export async function listEmailBlockedDomains( 7 | this: IExecuteFunctions, 8 | baseUrl: string, 9 | items: INodeExecutionData[], 10 | i: number, 11 | ): Promise { 12 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 13 | const qs: IDataObject = {}; 14 | 15 | if (additionalFields.limit) { 16 | qs.limit = additionalFields.limit; 17 | } 18 | if (additionalFields.max_id) { 19 | qs.max_id = additionalFields.max_id; 20 | } 21 | if (additionalFields.since_id) { 22 | qs.since_id = additionalFields.since_id; 23 | } 24 | if (additionalFields.min_id) { 25 | qs.min_id = additionalFields.min_id; 26 | } 27 | 28 | return await handleApiRequest.call( 29 | this, 30 | 'GET', 31 | `${baseUrl}/api/v1/admin/email_domain_blocks`, 32 | {}, 33 | qs, 34 | ); 35 | } 36 | 37 | export async function getEmailBlockedDomain( 38 | this: IExecuteFunctions, 39 | baseUrl: string, 40 | items: INodeExecutionData[], 41 | i: number, 42 | ): Promise { 43 | const domainId = this.getNodeParameter('domainId', i) as string; 44 | return await handleApiRequest.call( 45 | this, 46 | 'GET', 47 | `${baseUrl}/api/v1/admin/email_domain_blocks/${domainId}`, 48 | ); 49 | } 50 | 51 | export async function blockEmailDomain( 52 | this: IExecuteFunctions, 53 | baseUrl: string, 54 | items: INodeExecutionData[], 55 | i: number, 56 | ): Promise { 57 | const domain = this.getNodeParameter('domain', i) as string; 58 | 59 | return await handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/admin/email_domain_blocks`, { 60 | domain, 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /nodes/Mastodon/domains/email-blocked/EmailBlockedDomainProperties.ts: -------------------------------------------------------------------------------- 1 | // Modularized Email Blocked Domain properties for Mastodon node 2 | import { INodeProperties } from 'n8n-workflow'; 3 | 4 | export const emailBlockedDomainOperations = [ 5 | { 6 | displayName: 'Operation', 7 | name: 'operation', 8 | type: 'options', 9 | noDataExpression: true, 10 | displayOptions: { 11 | show: { 12 | resource: ['emailBlockedDomains'], 13 | }, 14 | }, 15 | options: [ 16 | { 17 | name: 'List Email Blocked Domains', 18 | value: 'listEmailBlockedDomains', 19 | description: 'Get a list of email domains blocked from signups', 20 | action: 'List email blocked domains', 21 | }, 22 | { 23 | name: 'Get Email Blocked Domain', 24 | value: 'getEmailBlockedDomain', 25 | description: 'Get a specific email blocked domain by ID', 26 | action: 'Get an email blocked domain', 27 | }, 28 | { 29 | name: 'Block Email Domain', 30 | value: 'blockEmailDomain', 31 | description: 'Block an email domain from user registration', 32 | action: 'Block an email domain', 33 | }, 34 | ], 35 | default: 'listEmailBlockedDomains', 36 | }, 37 | ] as INodeProperties[]; 38 | 39 | export const emailBlockedDomainFields = [ 40 | { 41 | displayName: 'Domain ID', 42 | name: 'domainId', 43 | type: 'string', 44 | required: true, 45 | default: '', 46 | displayOptions: { 47 | show: { 48 | resource: ['emailBlockedDomains'], 49 | operation: ['getEmailBlockedDomain'], 50 | }, 51 | }, 52 | description: 'The ID of the email blocked domain', 53 | }, 54 | { 55 | displayName: 'Domain', 56 | name: 'domain', 57 | type: 'string', 58 | required: true, 59 | default: '', 60 | displayOptions: { 61 | show: { 62 | resource: ['emailBlockedDomains'], 63 | operation: ['blockEmailDomain'], 64 | }, 65 | }, 66 | description: 'The email domain to block from registration', 67 | }, 68 | { 69 | displayName: 'Additional Fields', 70 | name: 'additionalFields', 71 | type: 'collection', 72 | placeholder: 'Add Field', 73 | default: {}, 74 | displayOptions: { 75 | show: { 76 | resource: ['emailBlockedDomains'], 77 | operation: ['listEmailBlockedDomains'], 78 | }, 79 | }, 80 | options: [ 81 | { 82 | displayName: 'Limit', 83 | name: 'limit', 84 | type: 'number', 85 | default: 100, 86 | description: 'Maximum number of results to return (default 100, max 200)', 87 | }, 88 | { 89 | displayName: 'Max ID', 90 | name: 'max_id', 91 | type: 'string', 92 | default: '', 93 | description: 'Return results older than this ID', 94 | }, 95 | { 96 | displayName: 'Since ID', 97 | name: 'since_id', 98 | type: 'string', 99 | default: '', 100 | description: 'Return results newer than this ID', 101 | }, 102 | { 103 | displayName: 'Min ID', 104 | name: 'min_id', 105 | type: 'string', 106 | default: '', 107 | description: 'Return results immediately newer than this ID', 108 | }, 109 | ], 110 | }, 111 | ] as INodeProperties[]; 112 | -------------------------------------------------------------------------------- /nodes/Mastodon/domains/index.ts: -------------------------------------------------------------------------------- 1 | // Aggregated domains module for Mastodon node 2 | import { INodeProperties } from 'n8n-workflow'; 3 | import * as AllowedProps from './allowed/AllowedDomainProperties'; 4 | import * as AllowedMethods from './allowed/AllowedDomainMethods'; 5 | import * as BlockedProps from './blocked/BlockedDomainProperties'; 6 | import * as BlockedMethods from './blocked/BlockedDomainMethods'; 7 | import * as EmailBlockedProps from './email-blocked/EmailBlockedDomainProperties'; 8 | import * as EmailBlockedMethods from './email-blocked/EmailBlockedDomainMethods'; 9 | 10 | export const domainProperties: INodeProperties[] = [ 11 | ...AllowedProps.allowedDomainOperations, 12 | ...AllowedProps.allowedDomainFields, 13 | ...BlockedProps.blockedDomainOperations, 14 | ...BlockedProps.blockedDomainFields, 15 | ...EmailBlockedProps.emailBlockedDomainOperations, 16 | ...EmailBlockedProps.emailBlockedDomainFields, 17 | ]; 18 | 19 | export const domainMethods = { 20 | // Allowed Domains 21 | listAllowedDomains: AllowedMethods.listAllowedDomains, 22 | getAllowedDomain: AllowedMethods.getAllowedDomain, 23 | 24 | // Blocked Domains 25 | listBlockedDomains: BlockedMethods.listBlockedDomains, 26 | getBlockedDomain: BlockedMethods.getBlockedDomain, 27 | 28 | // Email Blocked Domains 29 | listEmailBlockedDomains: EmailBlockedMethods.listEmailBlockedDomains, 30 | getEmailBlockedDomain: EmailBlockedMethods.getEmailBlockedDomain, 31 | }; 32 | -------------------------------------------------------------------------------- /nodes/Mastodon/emails/EmailsMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | 4 | export async function resendConfirmation(this: IExecuteFunctions, baseUrl: string): Promise { 5 | return await handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/emails/confirmation`); 6 | } 7 | -------------------------------------------------------------------------------- /nodes/Mastodon/emails/EmailsProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const emailsOperations: INodeProperties = { 4 | displayName: 'Operation', 5 | name: 'operation', 6 | type: 'options', 7 | noDataExpression: true, 8 | displayOptions: { show: { resource: ['emails'] } }, 9 | options: [ 10 | { 11 | name: 'Resend Confirmation', 12 | value: 'resendConfirmation', 13 | description: 'Resend confirmation email', 14 | action: 'Resend confirmation email', 15 | }, 16 | ], 17 | default: 'resendConfirmation', 18 | }; 19 | 20 | export const emailsFields: INodeProperties[] = []; 21 | -------------------------------------------------------------------------------- /nodes/Mastodon/endorsements/EndorsementMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | import { IAccount } from '../account/AccountInterfaces'; 4 | 5 | /** 6 | * Gets a list of featured accounts 7 | * GET /api/v1/endorsements 8 | * OAuth Scope: read:accounts 9 | */ 10 | export async function list( 11 | this: IExecuteFunctions, 12 | baseUrl: string, 13 | items: INodeExecutionData[], 14 | i: number, 15 | ): Promise { 16 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/endorsements`); 17 | } 18 | -------------------------------------------------------------------------------- /nodes/Mastodon/endorsements/EndorsementProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const endorsementOperations = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['endorsements'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'Get Endorsements', 17 | value: 'list', 18 | description: 'Get a list of featured accounts', 19 | action: 'Get endorsements', 20 | }, 21 | ], 22 | default: 'list', 23 | }, 24 | ] as INodeProperties[]; 25 | 26 | export const endorsementFields = [] as INodeProperties[]; 27 | -------------------------------------------------------------------------------- /nodes/Mastodon/endorsements/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as EndorsementMethods from './EndorsementMethods'; 3 | import { endorsementOperations, endorsementFields } from './EndorsementProperties'; 4 | 5 | export const endorsementProperties: INodeProperties[] = [ 6 | ...endorsementOperations, 7 | ...endorsementFields, 8 | ]; 9 | 10 | export const endorsementMethods = { 11 | list: EndorsementMethods.list, 12 | }; 13 | -------------------------------------------------------------------------------- /nodes/Mastodon/favourites/FavouritesMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | 4 | export async function favourite( 5 | this: IExecuteFunctions, 6 | baseUrl: string, 7 | items: INodeExecutionData[], 8 | i: number, 9 | ) { 10 | const statusId = this.getNodeParameter('statusId', i) as string; 11 | return handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/statuses/${statusId}/favourite`); 12 | } 13 | 14 | export async function unfavourite( 15 | this: IExecuteFunctions, 16 | baseUrl: string, 17 | items: INodeExecutionData[], 18 | i: number, 19 | ) { 20 | const statusId = this.getNodeParameter('statusId', i) as string; 21 | return handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/statuses/${statusId}/unfavourite`); 22 | } 23 | -------------------------------------------------------------------------------- /nodes/Mastodon/favourites/FavouritesProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const favouritesOperations: INodeProperties = { 4 | displayName: 'Operation', 5 | name: 'operation', 6 | type: 'options', 7 | noDataExpression: true, 8 | displayOptions: { show: { resource: ['favourites'] } }, 9 | options: [ 10 | { 11 | name: 'Favourite', 12 | value: 'favourite', 13 | description: 'Add a status to favourites', 14 | action: 'Favourite status', 15 | }, 16 | { 17 | name: 'Unfavourite', 18 | value: 'unfavourite', 19 | description: 'Remove a status from favourites', 20 | action: 'Unfavourite status', 21 | }, 22 | ], 23 | default: 'favourite', 24 | }; 25 | 26 | export const favouritesFields: INodeProperties[] = [ 27 | { 28 | displayName: 'Status ID', 29 | name: 'statusId', 30 | type: 'string', 31 | required: true, 32 | default: '', 33 | displayOptions: { show: { resource: ['favourites'], operation: ['favourite', 'unfavourite'] } }, 34 | description: 'ID of the status', 35 | }, 36 | ]; 37 | -------------------------------------------------------------------------------- /nodes/Mastodon/favourites/index.ts: -------------------------------------------------------------------------------- 1 | // Aggregated favourites module for Mastodon node 2 | import { INodeProperties } from 'n8n-workflow'; 3 | import * as Props from './FavouritesProperties'; 4 | import * as Methods from './FavouritesMethods'; 5 | 6 | export const favouritesProperties: INodeProperties[] = [ 7 | Props.favouritesOperations, 8 | ...Props.favouritesFields, 9 | ]; 10 | 11 | export const favouritesMethods = { 12 | favourite: Methods.favourite, 13 | unfavourite: Methods.unfavourite, 14 | }; 15 | -------------------------------------------------------------------------------- /nodes/Mastodon/featuredTags/FeaturedTagInterfaces.ts: -------------------------------------------------------------------------------- 1 | export interface IFeaturedTag { 2 | id: string; 3 | name: string; 4 | url: string; 5 | statuses_count: number; 6 | last_status_at: string; 7 | } 8 | -------------------------------------------------------------------------------- /nodes/Mastodon/featuredTags/FeaturedTagMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | import { IFeaturedTag } from './FeaturedTagInterfaces'; 4 | 5 | /** 6 | * Gets a list of featured tags 7 | * GET /api/v1/featured_tags 8 | * OAuth Scope: read:accounts 9 | */ 10 | export async function list( 11 | this: IExecuteFunctions, 12 | baseUrl: string, 13 | items: INodeExecutionData[], 14 | i: number, 15 | ): Promise { 16 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/featured_tags`); 17 | } 18 | 19 | /** 20 | * Features a tag 21 | * POST /api/v1/featured_tags 22 | * OAuth Scope: write:accounts 23 | */ 24 | export async function feature( 25 | this: IExecuteFunctions, 26 | baseUrl: string, 27 | items: INodeExecutionData[], 28 | i: number, 29 | ): Promise { 30 | const name = this.getNodeParameter('name', i) as string; 31 | return await handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/featured_tags`, { name }); 32 | } 33 | 34 | /** 35 | * Unfeatures a tag 36 | * DELETE /api/v1/featured_tags/:id 37 | * OAuth Scope: write:accounts 38 | */ 39 | export async function unfeature( 40 | this: IExecuteFunctions, 41 | baseUrl: string, 42 | items: INodeExecutionData[], 43 | i: number, 44 | ): Promise<{}> { 45 | const tagId = this.getNodeParameter('tagId', i) as string; 46 | return await handleApiRequest.call(this, 'DELETE', `${baseUrl}/api/v1/featured_tags/${tagId}`); 47 | } 48 | -------------------------------------------------------------------------------- /nodes/Mastodon/featuredTags/FeaturedTagProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const featuredTagOperations = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['featuredTags'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'Get Featured Tags', 17 | value: 'list', 18 | description: 'Get a list of featured hashtags', 19 | action: 'Get featured tags', 20 | }, 21 | { 22 | name: 'Feature Tag', 23 | value: 'feature', 24 | description: 'Feature a hashtag', 25 | action: 'Feature a tag', 26 | }, 27 | { 28 | name: 'Unfeature Tag', 29 | value: 'unfeature', 30 | description: 'Unfeature a hashtag', 31 | action: 'Unfeature a tag', 32 | }, 33 | ], 34 | default: 'list', 35 | }, 36 | ] as INodeProperties[]; 37 | 38 | export const featuredTagFields = [ 39 | { 40 | displayName: 'Tag Name', 41 | name: 'name', 42 | type: 'string', 43 | required: true, 44 | default: '', 45 | displayOptions: { 46 | show: { 47 | resource: ['featuredTags'], 48 | operation: ['feature'], 49 | }, 50 | }, 51 | description: 'The name of the hashtag to feature', 52 | }, 53 | { 54 | displayName: 'Tag ID', 55 | name: 'tagId', 56 | type: 'string', 57 | required: true, 58 | default: '', 59 | displayOptions: { 60 | show: { 61 | resource: ['featuredTags'], 62 | operation: ['unfeature'], 63 | }, 64 | }, 65 | description: 'The ID of the featured tag to remove', 66 | }, 67 | ] as INodeProperties[]; 68 | -------------------------------------------------------------------------------- /nodes/Mastodon/featuredTags/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as FeaturedTagMethods from './FeaturedTagMethods'; 3 | import { featuredTagOperations, featuredTagFields } from './FeaturedTagProperties'; 4 | 5 | export const featuredTagProperties: INodeProperties[] = [ 6 | ...featuredTagOperations, 7 | ...featuredTagFields, 8 | ]; 9 | 10 | export const featuredTagMethods = { 11 | list: FeaturedTagMethods.list, 12 | feature: FeaturedTagMethods.feature, 13 | unfeature: FeaturedTagMethods.unfeature, 14 | }; 15 | -------------------------------------------------------------------------------- /nodes/Mastodon/filters/FiltersMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | 4 | /** 5 | * Creates a new filter 6 | * POST /api/v1/filters 7 | */ 8 | export async function create( 9 | this: IExecuteFunctions, 10 | baseUrl: string, 11 | items: INodeExecutionData[], 12 | i: number, 13 | ): Promise { 14 | const phrase = this.getNodeParameter('phrase', i) as string; 15 | const context = this.getNodeParameter('context', i) as string[]; 16 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 17 | 18 | const body: IDataObject = { 19 | phrase, 20 | context, 21 | }; 22 | 23 | if (additionalFields.irreversible !== undefined) { 24 | body.irreversible = additionalFields.irreversible as boolean; 25 | } 26 | if (additionalFields.whole_word !== undefined) { 27 | body.whole_word = additionalFields.whole_word as boolean; 28 | } 29 | 30 | return await handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/filters`, body); 31 | } 32 | 33 | /** 34 | * Updates an existing filter 35 | * PUT /api/v1/filters/:id 36 | */ 37 | export async function update( 38 | this: IExecuteFunctions, 39 | baseUrl: string, 40 | items: INodeExecutionData[], 41 | i: number, 42 | ): Promise { 43 | const filterId = this.getNodeParameter('filterId', i) as string; 44 | const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; 45 | 46 | return await handleApiRequest.call( 47 | this, 48 | 'PUT', 49 | `${baseUrl}/api/v1/filters/${filterId}`, 50 | updateFields, 51 | ); 52 | } 53 | 54 | /** 55 | * Deletes a filter 56 | * DELETE /api/v1/filters/:id 57 | */ 58 | export async function remove( 59 | this: IExecuteFunctions, 60 | baseUrl: string, 61 | items: INodeExecutionData[], 62 | i: number, 63 | ): Promise { 64 | const filterId = this.getNodeParameter('filterId', i) as string; 65 | return await handleApiRequest.call(this, 'DELETE', `${baseUrl}/api/v1/filters/${filterId}`); 66 | } 67 | -------------------------------------------------------------------------------- /nodes/Mastodon/filters/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as FilterMethods from './FiltersMethods'; 3 | import { filtersOperations, filtersFields } from './FiltersProperties'; 4 | 5 | export const filterProperties: INodeProperties[] = [...filtersOperations, ...filtersFields]; 6 | 7 | export const filterMethods = { 8 | create: FilterMethods.create, 9 | update: FilterMethods.update, 10 | remove: FilterMethods.remove, 11 | }; 12 | -------------------------------------------------------------------------------- /nodes/Mastodon/followRequests/FollowRequestMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | import { IAccount } from '../account/AccountInterfaces'; 4 | import { IRelationship } from '../relationship/RelationshipInterfaces'; 5 | 6 | /** 7 | * Gets a list of pending follow requests 8 | * GET /api/v1/follow_requests 9 | * OAuth Scope: read:follows 10 | */ 11 | export async function list( 12 | this: IExecuteFunctions, 13 | baseUrl: string, 14 | items: INodeExecutionData[], 15 | i: number, 16 | ): Promise { 17 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/follow_requests`); 18 | } 19 | 20 | /** 21 | * Authorizes a follow request 22 | * POST /api/v1/follow_requests/:id/authorize 23 | * OAuth Scope: write:follows 24 | */ 25 | export async function acceptRequest( 26 | this: IExecuteFunctions, 27 | baseUrl: string, 28 | items: INodeExecutionData[], 29 | i: number, 30 | ): Promise { 31 | const accountId = this.getNodeParameter('accountId', i) as string; 32 | return await handleApiRequest.call( 33 | this, 34 | 'POST', 35 | `${baseUrl}/api/v1/follow_requests/${accountId}/authorize`, 36 | ); 37 | } 38 | 39 | /** 40 | * Rejects a follow request 41 | * POST /api/v1/follow_requests/:id/reject 42 | * OAuth Scope: write:follows 43 | */ 44 | export async function rejectRequest( 45 | this: IExecuteFunctions, 46 | baseUrl: string, 47 | items: INodeExecutionData[], 48 | i: number, 49 | ): Promise { 50 | const accountId = this.getNodeParameter('accountId', i) as string; 51 | return await handleApiRequest.call( 52 | this, 53 | 'POST', 54 | `${baseUrl}/api/v1/follow_requests/${accountId}/reject`, 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /nodes/Mastodon/followRequests/FollowRequestProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const followRequestOperations = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['followRequests'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'Get Follow Requests', 17 | value: 'list', 18 | description: 'Get a list of pending follow requests', 19 | action: 'Get follow requests', 20 | }, 21 | { 22 | name: 'Authorize Follow Request', 23 | value: 'authorize', 24 | description: 'Authorize a follow request', 25 | action: 'Authorize a follow request', 26 | }, 27 | { 28 | name: 'Reject Follow Request', 29 | value: 'reject', 30 | description: 'Reject a follow request', 31 | action: 'Reject a follow request', 32 | }, 33 | ], 34 | default: 'list', 35 | }, 36 | ] as INodeProperties[]; 37 | 38 | export const followRequestFields = [ 39 | { 40 | displayName: 'Account ID', 41 | name: 'accountId', 42 | type: 'string', 43 | required: true, 44 | default: '', 45 | displayOptions: { 46 | show: { 47 | resource: ['followRequests'], 48 | operation: ['authorize', 'reject'], 49 | }, 50 | }, 51 | description: 'The ID of the account requesting to follow', 52 | }, 53 | ] as INodeProperties[]; 54 | -------------------------------------------------------------------------------- /nodes/Mastodon/followRequests/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as FollowRequestMethods from './FollowRequestMethods'; 3 | import { followRequestOperations, followRequestFields } from './FollowRequestProperties'; 4 | 5 | export const followRequestsProperties: INodeProperties[] = [ 6 | ...followRequestOperations, 7 | ...followRequestFields, 8 | ]; 9 | 10 | export const followRequestsMethods = { 11 | list: FollowRequestMethods.list, 12 | acceptRequest: FollowRequestMethods.acceptRequest, 13 | rejectRequest: FollowRequestMethods.rejectRequest, 14 | }; 15 | -------------------------------------------------------------------------------- /nodes/Mastodon/followedTags/FollowedTagsMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | 4 | /** 5 | * Gets followed hashtags with pagination support 6 | * GET /api/v1/followed_tags 7 | */ 8 | export async function list( 9 | this: IExecuteFunctions, 10 | baseUrl: string, 11 | items: INodeExecutionData[], 12 | i: number, 13 | ): Promise { 14 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 15 | const qs: IDataObject = {}; 16 | 17 | if (additionalFields.max_id) { 18 | qs.max_id = additionalFields.max_id; 19 | } 20 | if (additionalFields.since_id) { 21 | qs.since_id = additionalFields.since_id; 22 | } 23 | if (additionalFields.limit) { 24 | qs.limit = additionalFields.limit; 25 | } 26 | 27 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/followed_tags`, {}, qs); 28 | } 29 | -------------------------------------------------------------------------------- /nodes/Mastodon/followedTags/FollowedTagsProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const followedTagsOperations = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['followedTags'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'Get Followed Tags', 17 | value: 'list', 18 | description: 'Get a list of followed hashtags', 19 | action: 'Get followed tags', 20 | }, 21 | ], 22 | default: 'list', 23 | }, 24 | ] as INodeProperties[]; 25 | 26 | export const followedTagsFields = [ 27 | { 28 | displayName: 'Additional Fields', 29 | name: 'additionalFields', 30 | type: 'collection', 31 | placeholder: 'Add Field', 32 | default: {}, 33 | displayOptions: { 34 | show: { 35 | resource: ['followedTags'], 36 | operation: ['list'], 37 | }, 38 | }, 39 | options: [ 40 | { 41 | displayName: 'Max ID', 42 | name: 'max_id', 43 | type: 'string', 44 | default: '', 45 | description: 'Return results older than this ID', 46 | }, 47 | { 48 | displayName: 'Since ID', 49 | name: 'since_id', 50 | type: 'string', 51 | default: '', 52 | description: 'Return results newer than this ID', 53 | }, 54 | { 55 | displayName: 'Limit', 56 | name: 'limit', 57 | type: 'number', 58 | default: 20, 59 | description: 'Maximum number of results to return', 60 | }, 61 | ], 62 | }, 63 | ] as INodeProperties[]; 64 | -------------------------------------------------------------------------------- /nodes/Mastodon/followedTags/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as FollowedTagsMethods from './FollowedTagsMethods'; 3 | import { followedTagsOperations, followedTagsFields } from './FollowedTagsProperties'; 4 | 5 | export const followedTagsProperties: INodeProperties[] = [ 6 | ...followedTagsOperations, 7 | ...followedTagsFields, 8 | ]; 9 | 10 | export const followedTagsMethods = { 11 | list: FollowedTagsMethods.list, 12 | }; 13 | -------------------------------------------------------------------------------- /nodes/Mastodon/instance/InstanceInterfaces.ts: -------------------------------------------------------------------------------- 1 | // Instance-related interfaces for Mastodon node 2 | export interface IInstance { 3 | domain: string; 4 | title: string; 5 | version: string; 6 | description: string; 7 | usage: { 8 | users: { 9 | active_month: number; 10 | }; 11 | }; 12 | thumbnail: { 13 | url: string; 14 | blurhash: string; 15 | versions: { 16 | [key: string]: string; 17 | }; 18 | }; 19 | languages: string[]; 20 | configuration: { 21 | urls: { 22 | streaming: string; 23 | }; 24 | accounts: { 25 | max_featured_tags: number; 26 | }; 27 | statuses: { 28 | max_characters: number; 29 | max_media_attachments: number; 30 | characters_reserved_per_url: number; 31 | }; 32 | media_attachments: { 33 | supported_mime_types: string[]; 34 | image_size_limit: number; 35 | image_matrix_limit: number; 36 | video_size_limit: number; 37 | video_frame_rate_limit: number; 38 | video_matrix_limit: number; 39 | }; 40 | polls: { 41 | max_options: number; 42 | max_characters_per_option: number; 43 | min_expiration: number; 44 | max_expiration: number; 45 | }; 46 | }; 47 | registrations: { 48 | enabled: boolean; 49 | approval_required: boolean; 50 | message: string | null; 51 | }; 52 | contact: { 53 | email: string; 54 | account: any; // Can be expanded to IAccount if needed 55 | }; 56 | rules: { 57 | id: string; 58 | text: string; 59 | }[]; 60 | } 61 | -------------------------------------------------------------------------------- /nodes/Mastodon/instance/InstanceMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | import { IInstance } from './InstanceInterfaces'; 4 | 5 | /** 6 | * Get server information 7 | * GET /api/v2/instance 8 | */ 9 | export async function getServerInfo( 10 | this: IExecuteFunctions, 11 | baseUrl: string, 12 | items: INodeExecutionData[], 13 | i: number, 14 | ): Promise { 15 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v2/instance`); 16 | } 17 | -------------------------------------------------------------------------------- /nodes/Mastodon/instance/InstanceProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const instanceOperations: INodeProperties[] = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['instance'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'Get Server Information', 17 | value: 'getServerInfo', 18 | description: 'Get information about the server', 19 | action: 'Get server information', 20 | }, 21 | ], 22 | default: 'getServerInfo', 23 | }, 24 | ]; 25 | -------------------------------------------------------------------------------- /nodes/Mastodon/instance/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './InstanceMethods'; 3 | import * as Properties from './InstanceProperties'; 4 | 5 | export const instanceProperties: INodeProperties[] = [...Properties.instanceOperations]; 6 | 7 | export const instanceMethods = { 8 | getServerInfo: Methods.getServerInfo, 9 | }; 10 | -------------------------------------------------------------------------------- /nodes/Mastodon/lists/ListInterfaces.ts: -------------------------------------------------------------------------------- 1 | // List-related interfaces for Mastodon node 2 | export interface IList { 3 | id: string; 4 | title: string; 5 | replies_policy?: string; 6 | exclusive?: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /nodes/Mastodon/lists/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './ListMethods'; 3 | import * as Properties from './ListProperties'; 4 | 5 | export const listProperties: INodeProperties[] = [ 6 | ...Properties.listOperations, 7 | ...Properties.listFields, 8 | ]; 9 | 10 | export const listMethods = { 11 | getLists: Methods.getLists, 12 | getList: Methods.getList, 13 | createList: Methods.createList, 14 | updateList: Methods.updateList, 15 | deleteList: Methods.deleteList, 16 | getAccountsInList: Methods.getAccountsInList, 17 | addAccountsToList: Methods.addAccountsToList, 18 | removeAccountsFromList: Methods.removeAccountsFromList, 19 | }; 20 | -------------------------------------------------------------------------------- /nodes/Mastodon/markers/MarkerInterfaces.ts: -------------------------------------------------------------------------------- 1 | import { IDataObject } from 'n8n-workflow'; 2 | 3 | // Modularized Marker interfaces for Mastodon node 4 | export interface IMarker { 5 | last_read_id: string; 6 | version: number; 7 | updated_at: string; 8 | } 9 | 10 | export interface IMarkerResponse { 11 | home?: IMarker; 12 | notifications?: IMarker; 13 | } 14 | 15 | export interface IMarkerPayload { 16 | 'home[last_read_id]'?: string; 17 | 'notifications[last_read_id]'?: string; 18 | } 19 | 20 | export interface IMarkerParams { 21 | timeline: string[]; 22 | } 23 | -------------------------------------------------------------------------------- /nodes/Mastodon/markers/MarkerMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | import { IMarkerResponse, IMarkerParams, IMarkerPayload } from './MarkerInterfaces'; 4 | 5 | /** 6 | * Retrieve Markers 7 | * GET /api/v1/markers 8 | * OAuth Scope: read:statuses 9 | */ 10 | export async function getMarkers( 11 | this: IExecuteFunctions, 12 | baseUrl: string, 13 | items: INodeExecutionData[], 14 | i: number, 15 | ): Promise { 16 | const timelines = this.getNodeParameter('timeline', i) as string[]; 17 | const params: IMarkerParams = { 18 | timeline: timelines, 19 | }; 20 | 21 | return await handleApiRequest.call( 22 | this, 23 | 'GET', 24 | `${baseUrl}/api/v1/markers`, 25 | {}, 26 | params as unknown as IDataObject, 27 | ); 28 | } 29 | 30 | /** 31 | * Save Markers 32 | * POST /api/v1/markers 33 | * OAuth Scope: write:statuses 34 | */ 35 | export async function saveMarkers( 36 | this: IExecuteFunctions, 37 | baseUrl: string, 38 | items: INodeExecutionData[], 39 | i: number, 40 | ): Promise { 41 | const body: IMarkerPayload = {}; 42 | const homeLastReadId = this.getNodeParameter('homeLastReadId', i, undefined); 43 | const notificationsLastReadId = this.getNodeParameter('notificationsLastReadId', i, undefined); 44 | 45 | if (homeLastReadId) { 46 | body['home[last_read_id]'] = homeLastReadId as string; 47 | } 48 | if (notificationsLastReadId) { 49 | body['notifications[last_read_id]'] = notificationsLastReadId as string; 50 | } 51 | 52 | return await handleApiRequest.call( 53 | this, 54 | 'POST', 55 | `${baseUrl}/api/v1/markers`, 56 | body as IDataObject, 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /nodes/Mastodon/markers/MarkersInterfaces.ts: -------------------------------------------------------------------------------- 1 | import { IDataObject } from 'n8n-workflow'; 2 | 3 | export interface IMarker { 4 | last_read_id: string; 5 | version: number; 6 | updated_at: string; 7 | } 8 | 9 | export interface IMarkerRequest { 10 | home?: IDataObject; 11 | notifications?: IDataObject; 12 | } 13 | 14 | export interface IMarkersResponse { 15 | home?: IMarker; 16 | notifications?: IMarker; 17 | } 18 | 19 | export interface IMarkerPayload extends IDataObject { 20 | 'home[last_read_id]'?: string; 21 | 'notifications[last_read_id]'?: string; 22 | } 23 | -------------------------------------------------------------------------------- /nodes/Mastodon/markers/MarkersProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const markersOperations: INodeProperties[] = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['markers'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'Get Markers', 17 | value: 'get', 18 | description: 'Fetch last read positions in timelines', 19 | action: 'Get markers', 20 | }, 21 | { 22 | name: 'Save Markers', 23 | value: 'save', 24 | description: 'Save last read positions in timelines', 25 | action: 'Save markers', 26 | }, 27 | ], 28 | default: 'get', 29 | }, 30 | ]; 31 | 32 | export const markersFields: INodeProperties[] = [ 33 | { 34 | displayName: 'Timeline', 35 | name: 'timeline', 36 | type: 'multiOptions', 37 | displayOptions: { 38 | show: { 39 | resource: ['markers'], 40 | operation: ['get', 'save'], 41 | }, 42 | }, 43 | options: [ 44 | { 45 | name: 'Home', 46 | value: 'home', 47 | }, 48 | { 49 | name: 'Notifications', 50 | value: 'notifications', 51 | }, 52 | ], 53 | default: [], 54 | required: true, 55 | description: 'Timelines to retrieve/save markers for', 56 | }, 57 | { 58 | displayName: 'Home Last Read ID', 59 | name: 'homeLastReadId', 60 | type: 'string', 61 | default: '', 62 | displayOptions: { 63 | show: { 64 | resource: ['markers'], 65 | operation: ['save'], 66 | timeline: ['home'], 67 | }, 68 | }, 69 | description: 'ID of the last read status in the home timeline', 70 | }, 71 | { 72 | displayName: 'Notifications Last Read ID', 73 | name: 'notificationsLastReadId', 74 | type: 'string', 75 | default: '', 76 | displayOptions: { 77 | show: { 78 | resource: ['markers'], 79 | operation: ['save'], 80 | timeline: ['notifications'], 81 | }, 82 | }, 83 | description: 'ID of the last read notification', 84 | }, 85 | ] as INodeProperties[]; 86 | -------------------------------------------------------------------------------- /nodes/Mastodon/markers/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './MarkerMethods'; 3 | import * as Properties from './MarkersProperties'; 4 | 5 | export const markerProperties: INodeProperties[] = [ 6 | ...Properties.markersOperations, 7 | ...Properties.markersFields, 8 | ]; 9 | 10 | export const markerMethods = { 11 | getMarkers: Methods.getMarkers, 12 | saveMarkers: Methods.saveMarkers, 13 | }; 14 | -------------------------------------------------------------------------------- /nodes/Mastodon/measures/MeasureMethods.ts: -------------------------------------------------------------------------------- 1 | // Modularized Measure methods for Mastodon node 2 | import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; 3 | import { handleApiRequest } from '../Mastodon_Methods'; 4 | 5 | export async function listMeasures(this: IExecuteFunctions, baseUrl: string) { 6 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/admin/measures`); 7 | } 8 | 9 | export async function getMeasureMetrics( 10 | this: IExecuteFunctions, 11 | baseUrl: string, 12 | items: INodeExecutionData[], 13 | i: number, 14 | ) { 15 | const measureId = this.getNodeParameter('measureId', i) as string; 16 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/admin/measures/${measureId}`); 17 | } 18 | -------------------------------------------------------------------------------- /nodes/Mastodon/measures/MeasureProperties.ts: -------------------------------------------------------------------------------- 1 | // Modularized Measure properties for Mastodon node 2 | import { INodeProperties } from 'n8n-workflow'; 3 | 4 | export const measureOperations = [ 5 | { 6 | displayName: 'Operation', 7 | name: 'operation', 8 | type: 'options', 9 | noDataExpression: true, 10 | displayOptions: { 11 | show: { 12 | resource: ['measures'], 13 | }, 14 | }, 15 | options: [ 16 | { 17 | name: 'List Measures', 18 | value: 'listMeasures', 19 | description: 'Get a list of available measures for metrics', 20 | action: 'List measures', 21 | }, 22 | { 23 | name: 'Get Measure Metrics', 24 | value: 'getMeasureMetrics', 25 | description: 'Get metrics for a specific measure', 26 | action: 'Get measure metrics', 27 | }, 28 | ], 29 | default: 'listMeasures', 30 | }, 31 | ] as INodeProperties[]; 32 | 33 | export const measureFields = [ 34 | { 35 | displayName: 'Measure ID', 36 | name: 'measureId', 37 | type: 'string', 38 | required: true, 39 | default: '', 40 | displayOptions: { 41 | show: { 42 | resource: ['measures'], 43 | operation: ['getMeasureMetrics'], 44 | }, 45 | }, 46 | description: 'The ID of the measure to retrieve metrics for', 47 | }, 48 | ] as INodeProperties[]; 49 | -------------------------------------------------------------------------------- /nodes/Mastodon/measures/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './MeasureMethods'; 3 | import { measureOperations, measureFields } from './MeasureProperties'; 4 | 5 | export const measureProperties: INodeProperties[] = [...measureOperations, ...measureFields]; 6 | 7 | export const measureMethods = { 8 | listMeasures: Methods.listMeasures, 9 | getMeasureMetrics: Methods.getMeasureMetrics, 10 | }; 11 | -------------------------------------------------------------------------------- /nodes/Mastodon/media/MediaMethods.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IExecuteFunctions, 3 | INodeExecutionData, 4 | IDataObject, 5 | NodeOperationError, 6 | } from 'n8n-workflow'; 7 | import { handleApiRequest } from '../Mastodon_Methods'; 8 | 9 | interface IMediaAttachment { 10 | id: string; 11 | type: string; 12 | url: string; 13 | preview_url: string; 14 | remote_url: string | null; 15 | description: string | null; 16 | blurhash: string; 17 | } 18 | 19 | /** 20 | * Uploads media 21 | * POST /api/v1/media 22 | */ 23 | export async function upload( 24 | this: IExecuteFunctions, 25 | baseUrl: string, 26 | items: INodeExecutionData[], 27 | i: number, 28 | ): Promise { 29 | const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string; 30 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 31 | const binaryData = items[i].binary?.[binaryPropertyName]; 32 | 33 | if (!binaryData) { 34 | throw new NodeOperationError(this.getNode(), 'No binary data exists on input item.'); 35 | } 36 | 37 | const buffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName); 38 | const formData: { [key: string]: any } = { 39 | file: { 40 | value: buffer, 41 | options: { 42 | filename: binaryData.fileName, 43 | contentType: binaryData.mimeType, 44 | }, 45 | }, 46 | }; 47 | 48 | if (additionalFields.description) { 49 | formData.description = additionalFields.description as string; 50 | } 51 | if (additionalFields.focus) { 52 | formData.focus = additionalFields.focus as string; 53 | } 54 | 55 | return await handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/media`, {}, {}, { formData }); 56 | } 57 | 58 | /** 59 | * Updates media metadata 60 | * PUT /api/v1/media/:id 61 | */ 62 | export async function update( 63 | this: IExecuteFunctions, 64 | baseUrl: string, 65 | items: INodeExecutionData[], 66 | i: number, 67 | ): Promise { 68 | const mediaId = this.getNodeParameter('mediaId', i) as string; 69 | const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; 70 | 71 | const body: IDataObject = {}; 72 | if (updateFields.description) { 73 | body.description = updateFields.description; 74 | } 75 | if (updateFields.focus) { 76 | body.focus = updateFields.focus; 77 | } 78 | 79 | return await handleApiRequest.call(this, 'PUT', `${baseUrl}/api/v1/media/${mediaId}`, body); 80 | } 81 | -------------------------------------------------------------------------------- /nodes/Mastodon/media/MediaProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const mediaOperations = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['media'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'Upload Media', 17 | value: 'upload', 18 | description: 'Upload a new media file', 19 | action: 'Upload media', 20 | }, 21 | { 22 | name: 'Update Media', 23 | value: 'update', 24 | description: 'Update media metadata', 25 | action: 'Update media', 26 | }, 27 | ], 28 | default: 'upload', 29 | }, 30 | ] as INodeProperties[]; 31 | 32 | export const mediaFields = [ 33 | // Fields for uploading media 34 | { 35 | displayName: 'Binary Property', 36 | name: 'binaryPropertyName', 37 | type: 'string', 38 | required: true, 39 | default: 'data', 40 | displayOptions: { 41 | show: { 42 | resource: ['media'], 43 | operation: ['upload'], 44 | }, 45 | }, 46 | description: 'Name of the binary property containing the file to be uploaded', 47 | }, 48 | { 49 | displayName: 'Additional Fields', 50 | name: 'additionalFields', 51 | type: 'collection', 52 | placeholder: 'Add Field', 53 | default: {}, 54 | displayOptions: { 55 | show: { 56 | resource: ['media'], 57 | operation: ['upload'], 58 | }, 59 | }, 60 | options: [ 61 | { 62 | displayName: 'Description', 63 | name: 'description', 64 | type: 'string', 65 | default: '', 66 | description: 'A text description of the media for accessibility', 67 | }, 68 | { 69 | displayName: 'Focus', 70 | name: 'focus', 71 | type: 'string', 72 | default: '', 73 | description: 'Two floating points between -1.0 and 1.0 (x,y)', 74 | placeholder: '0.0,0.0', 75 | }, 76 | ], 77 | }, 78 | 79 | // Fields for updating media 80 | { 81 | displayName: 'Media ID', 82 | name: 'mediaId', 83 | type: 'string', 84 | required: true, 85 | default: '', 86 | displayOptions: { 87 | show: { 88 | resource: ['media'], 89 | operation: ['update'], 90 | }, 91 | }, 92 | description: 'ID of the media to update', 93 | }, 94 | { 95 | displayName: 'Update Fields', 96 | name: 'updateFields', 97 | type: 'collection', 98 | placeholder: 'Add Field', 99 | default: {}, 100 | displayOptions: { 101 | show: { 102 | resource: ['media'], 103 | operation: ['update'], 104 | }, 105 | }, 106 | options: [ 107 | { 108 | displayName: 'Description', 109 | name: 'description', 110 | type: 'string', 111 | default: '', 112 | description: 'A text description of the media for accessibility', 113 | }, 114 | { 115 | displayName: 'Focus', 116 | name: 'focus', 117 | type: 'string', 118 | default: '', 119 | description: 'Two floating points between -1.0 and 1.0 (x,y)', 120 | placeholder: '0.0,0.0', 121 | }, 122 | ], 123 | }, 124 | ] as INodeProperties[]; 125 | -------------------------------------------------------------------------------- /nodes/Mastodon/media/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as MediaMethods from './MediaMethods'; 3 | import { mediaOperations, mediaFields } from './MediaProperties'; 4 | 5 | export const mediaProperties: INodeProperties[] = [...mediaOperations, ...mediaFields]; 6 | 7 | export const mediaMethods = { 8 | upload: MediaMethods.upload, 9 | update: MediaMethods.update, 10 | }; 11 | -------------------------------------------------------------------------------- /nodes/Mastodon/mutes/MutesMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | 4 | export async function mute( 5 | this: IExecuteFunctions, 6 | baseUrl: string, 7 | items: INodeExecutionData[], 8 | i: number, 9 | ) { 10 | const accountId = this.getNodeParameter('accountId', i) as string; 11 | const options = this.getNodeParameter('options', i, {}) as { 12 | notifications?: boolean; 13 | duration?: number; 14 | }; 15 | 16 | const body: { [key: string]: any } = {}; 17 | if (options.notifications !== undefined) { 18 | body.notifications = options.notifications; 19 | } 20 | if (options.duration !== undefined) { 21 | body.duration = options.duration; 22 | } 23 | 24 | return await handleApiRequest.call( 25 | this, 26 | 'POST', 27 | `${baseUrl}/api/v1/accounts/${accountId}/mute`, 28 | body, 29 | ); 30 | } 31 | 32 | export async function unmute( 33 | this: IExecuteFunctions, 34 | baseUrl: string, 35 | items: INodeExecutionData[], 36 | i: number, 37 | ) { 38 | const accountId = this.getNodeParameter('accountId', i) as string; 39 | return await handleApiRequest.call( 40 | this, 41 | 'POST', 42 | `${baseUrl}/api/v1/accounts/${accountId}/unmute`, 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /nodes/Mastodon/mutes/MutesProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const mutesOperations = { 4 | displayName: 'Operation', 5 | name: 'operation', 6 | type: 'options', 7 | noDataExpression: true, 8 | displayOptions: { 9 | show: { 10 | resource: ['mutes'], 11 | }, 12 | }, 13 | options: [ 14 | { 15 | name: 'Mute Account', 16 | value: 'mute', 17 | description: 'Mute an account', 18 | action: 'Mute an account', 19 | }, 20 | { 21 | name: 'Unmute Account', 22 | value: 'unmute', 23 | description: 'Unmute an account', 24 | action: 'Unmute an account', 25 | }, 26 | ], 27 | default: 'mute', 28 | } as INodeProperties; 29 | 30 | export const mutesFields = [ 31 | { 32 | displayName: 'Account ID', 33 | name: 'accountId', 34 | type: 'string', 35 | required: true, 36 | default: '', 37 | displayOptions: { 38 | show: { 39 | resource: ['mutes'], 40 | operation: ['mute', 'unmute'], 41 | }, 42 | }, 43 | description: 'ID of the account to mute/unmute', 44 | }, 45 | { 46 | displayName: 'Options', 47 | name: 'options', 48 | type: 'collection', 49 | placeholder: 'Add Option', 50 | default: {}, 51 | displayOptions: { 52 | show: { 53 | resource: ['mutes'], 54 | operation: ['mute'], 55 | }, 56 | }, 57 | options: [ 58 | { 59 | displayName: 'Mute Notifications', 60 | name: 'notifications', 61 | type: 'boolean', 62 | default: true, 63 | description: 'Whether to mute notifications from the account as well', 64 | }, 65 | { 66 | displayName: 'Duration', 67 | name: 'duration', 68 | type: 'number', 69 | default: 0, 70 | description: 'Number of seconds to mute for. 0 means indefinite.', 71 | }, 72 | ], 73 | }, 74 | ] as INodeProperties[]; 75 | -------------------------------------------------------------------------------- /nodes/Mastodon/mutes/index.ts: -------------------------------------------------------------------------------- 1 | import { mute, unmute } from './MutesMethods'; 2 | import { mutesOperations, mutesFields } from './MutesProperties'; 3 | 4 | export const mutesProperties = [mutesOperations, ...mutesFields]; 5 | 6 | export const mutesMethods = { 7 | mute, 8 | unmute, 9 | }; 10 | -------------------------------------------------------------------------------- /nodes/Mastodon/n8n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redoracle/n8n-nodes-the-mastodon/0362e0ff0b0b5b26a246f01810cb83248f2b5ab3/nodes/Mastodon/n8n.png -------------------------------------------------------------------------------- /nodes/Mastodon/n8n.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Layer 1 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /nodes/Mastodon/notifications/NotificationInterfaces.ts: -------------------------------------------------------------------------------- 1 | import { IDataObject } from 'n8n-workflow'; 2 | 3 | export interface INotification { 4 | id: string; 5 | type: string; 6 | created_at: string; 7 | account: IDataObject; 8 | status?: IDataObject; 9 | } 10 | 11 | export interface INotificationParams extends IDataObject { 12 | max_id?: string; 13 | since_id?: string; 14 | min_id?: string; 15 | limit?: number; 16 | types?: string[]; 17 | exclude_types?: string[]; 18 | } 19 | 20 | export interface IUnreadCount { 21 | count: number; 22 | } 23 | -------------------------------------------------------------------------------- /nodes/Mastodon/notifications/NotificationMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | import { INotification, INotificationParams, IUnreadCount } from './NotificationInterfaces'; 4 | 5 | /** 6 | * Get Notifications 7 | * GET /api/v1/notifications 8 | * OAuth Scope: read:notifications 9 | */ 10 | export async function getNotifications( 11 | this: IExecuteFunctions, 12 | baseUrl: string, 13 | items: INodeExecutionData[], 14 | i: number, 15 | ): Promise { 16 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 17 | const returnAll = this.getNodeParameter('returnAll', i) as boolean; 18 | const qs: INotificationParams = {}; 19 | 20 | if (additionalFields.max_id) { 21 | qs.max_id = additionalFields.max_id as string; 22 | } 23 | if (additionalFields.since_id) { 24 | qs.since_id = additionalFields.since_id as string; 25 | } 26 | if (additionalFields.min_id) { 27 | qs.min_id = additionalFields.min_id as string; 28 | } 29 | if (additionalFields.types) { 30 | qs.types = additionalFields.types as string[]; 31 | } 32 | if (additionalFields.exclude_types) { 33 | qs.exclude_types = additionalFields.exclude_types as string[]; 34 | } 35 | if (!returnAll) { 36 | qs.limit = Math.min(this.getNodeParameter('limit', i) as number, 40); 37 | } 38 | 39 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/notifications`, {}, qs); 40 | } 41 | 42 | /** 43 | * Dismiss a Notification 44 | * POST /api/v1/notifications/dismiss 45 | * OAuth Scope: write:notifications 46 | */ 47 | export async function dismissNotification( 48 | this: IExecuteFunctions, 49 | baseUrl: string, 50 | items: INodeExecutionData[], 51 | i: number, 52 | ): Promise<{}> { 53 | const notificationId = this.getNodeParameter('id', i) as string; 54 | 55 | return await handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/notifications/dismiss`, { 56 | id: notificationId, 57 | }); 58 | } 59 | 60 | /** 61 | * Get Unread Notifications Count 62 | * GET /api/v1/notifications/unread_count 63 | * OAuth Scope: read:notifications 64 | */ 65 | export async function getUnreadCount( 66 | this: IExecuteFunctions, 67 | baseUrl: string, 68 | ): Promise { 69 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/notifications/unread_count`); 70 | } 71 | -------------------------------------------------------------------------------- /nodes/Mastodon/notifications/NotificationProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const notificationOperations: INodeProperties[] = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['notifications'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'Get Notifications', 17 | value: 'get', 18 | description: 'Retrieve notifications for authenticated user', 19 | action: 'Get notifications', 20 | }, 21 | { 22 | name: 'Dismiss Notification', 23 | value: 'dismiss', 24 | description: 'Dismiss a specific notification', 25 | action: 'Dismiss notification', 26 | }, 27 | { 28 | name: 'Get Unread Count', 29 | value: 'unreadCount', 30 | description: 'Get number of unread notifications', 31 | action: 'Get unread count', 32 | }, 33 | ], 34 | default: 'get', 35 | }, 36 | ]; 37 | 38 | export const notificationFields: INodeProperties[] = [ 39 | { 40 | displayName: 'Return All', 41 | name: 'returnAll', 42 | type: 'boolean', 43 | displayOptions: { 44 | show: { 45 | resource: ['notifications'], 46 | operation: ['get'], 47 | }, 48 | }, 49 | default: false, 50 | description: 'Whether to return all results or only up to a given limit', 51 | }, 52 | { 53 | displayName: 'Limit', 54 | name: 'limit', 55 | type: 'number', 56 | displayOptions: { 57 | show: { 58 | resource: ['notifications'], 59 | operation: ['get'], 60 | returnAll: [false], 61 | }, 62 | }, 63 | typeOptions: { 64 | minValue: 1, 65 | maxValue: 40, 66 | }, 67 | default: 20, 68 | description: 'Max number of results to return', 69 | }, 70 | { 71 | displayName: 'Notification ID', 72 | name: 'id', 73 | type: 'string', 74 | displayOptions: { 75 | show: { 76 | resource: ['notifications'], 77 | operation: ['dismiss'], 78 | }, 79 | }, 80 | default: '', 81 | required: true, 82 | description: 'ID of the notification to dismiss', 83 | }, 84 | { 85 | displayName: 'Additional Fields', 86 | name: 'additionalFields', 87 | type: 'collection', 88 | displayOptions: { 89 | show: { 90 | resource: ['notifications'], 91 | operation: ['get'], 92 | }, 93 | }, 94 | default: {}, 95 | placeholder: 'Add Field', 96 | options: [ 97 | { 98 | displayName: 'Max ID', 99 | name: 'max_id', 100 | type: 'string', 101 | default: '', 102 | description: 'Return results older than this ID', 103 | }, 104 | { 105 | displayName: 'Since ID', 106 | name: 'since_id', 107 | type: 'string', 108 | default: '', 109 | description: 'Return results newer than this ID', 110 | }, 111 | { 112 | displayName: 'Min ID', 113 | name: 'min_id', 114 | type: 'string', 115 | default: '', 116 | description: 'Return results immediately newer than this ID', 117 | }, 118 | { 119 | displayName: 'Types', 120 | name: 'types', 121 | type: 'multiOptions', 122 | options: [ 123 | { 124 | name: 'Follow', 125 | value: 'follow', 126 | }, 127 | { 128 | name: 'Favourite', 129 | value: 'favourite', 130 | }, 131 | { 132 | name: 'Reblog', 133 | value: 'reblog', 134 | }, 135 | { 136 | name: 'Mention', 137 | value: 'mention', 138 | }, 139 | { 140 | name: 'Poll', 141 | value: 'poll', 142 | }, 143 | ], 144 | default: [], 145 | description: 'Types of notifications to include', 146 | }, 147 | { 148 | displayName: 'Exclude Types', 149 | name: 'exclude_types', 150 | type: 'multiOptions', 151 | options: [ 152 | { 153 | name: 'Follow', 154 | value: 'follow', 155 | }, 156 | { 157 | name: 'Favourite', 158 | value: 'favourite', 159 | }, 160 | { 161 | name: 'Reblog', 162 | value: 'reblog', 163 | }, 164 | { 165 | name: 'Mention', 166 | value: 'mention', 167 | }, 168 | { 169 | name: 'Poll', 170 | value: 'poll', 171 | }, 172 | ], 173 | default: [], 174 | description: 'Types of notifications to exclude', 175 | }, 176 | ], 177 | }, 178 | ] as INodeProperties[]; 179 | -------------------------------------------------------------------------------- /nodes/Mastodon/notifications/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './NotificationMethods'; 3 | import * as Properties from './NotificationProperties'; 4 | 5 | export const notificationProperties: INodeProperties[] = [ 6 | ...Properties.notificationOperations, 7 | ...Properties.notificationFields, 8 | ]; 9 | 10 | export const notificationMethods = { 11 | getNotifications: Methods.getNotifications, 12 | dismissNotification: Methods.dismissNotification, 13 | getUnreadCount: Methods.getUnreadCount, 14 | }; 15 | -------------------------------------------------------------------------------- /nodes/Mastodon/oembed/OEmbedMethods.ts: -------------------------------------------------------------------------------- 1 | // Modularized oEmbed methods for Mastodon node 2 | import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; 3 | import { handleApiRequest } from '../Mastodon_Methods'; 4 | 5 | export async function fetchOembed( 6 | this: IExecuteFunctions, 7 | baseUrl: string, 8 | items: INodeExecutionData[], 9 | i: number, 10 | ) { 11 | const url = this.getNodeParameter('url', i) as string; 12 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/oembed`, {}, { url }); 13 | } 14 | -------------------------------------------------------------------------------- /nodes/Mastodon/oembed/OEmbedProperties.ts: -------------------------------------------------------------------------------- 1 | // Modularized oEmbed properties for Mastodon node 2 | import { INodeProperties } from 'n8n-workflow'; 3 | 4 | export const oembedOperations = [ 5 | { 6 | displayName: 'Operation', 7 | name: 'operation', 8 | type: 'options', 9 | noDataExpression: true, 10 | displayOptions: { 11 | show: { 12 | resource: ['oembed'], 13 | }, 14 | }, 15 | options: [ 16 | { 17 | name: 'Fetch oEmbed Data', 18 | value: 'fetchOembed', 19 | description: 'Get oEmbed data for a status URL', 20 | action: 'Fetch oEmbed data', 21 | }, 22 | ], 23 | default: 'fetchOembed', 24 | }, 25 | ] as INodeProperties[]; 26 | 27 | export const oembedFields = [ 28 | { 29 | displayName: 'Status URL', 30 | name: 'url', 31 | type: 'string', 32 | required: true, 33 | default: '', 34 | displayOptions: { 35 | show: { 36 | resource: ['oembed'], 37 | operation: ['fetchOembed'], 38 | }, 39 | }, 40 | description: 'URL of the status to fetch oEmbed data for', 41 | }, 42 | ] as INodeProperties[]; 43 | -------------------------------------------------------------------------------- /nodes/Mastodon/oembed/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './OEmbedMethods'; 3 | import { oembedOperations, oembedFields } from './OEmbedProperties'; 4 | 5 | export const oembedProperties: INodeProperties[] = [...oembedOperations, ...oembedFields]; 6 | 7 | export const oembedMethods = { 8 | fetchOembed: Methods.fetchOembed, 9 | }; 10 | -------------------------------------------------------------------------------- /nodes/Mastodon/polls/PollMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | 4 | interface IPoll { 5 | id: string; 6 | expires_at: string | null; 7 | expired: boolean; 8 | multiple: boolean; 9 | votes_count: number; 10 | voters_count: number | null; 11 | options: { 12 | title: string; 13 | votes_count: number | null; 14 | }[]; 15 | emojis: any[]; 16 | voted?: boolean; 17 | own_votes?: number[]; 18 | } 19 | 20 | /** 21 | * Views a poll 22 | * GET /api/v1/polls/:id 23 | */ 24 | export async function view( 25 | this: IExecuteFunctions, 26 | baseUrl: string, 27 | items: INodeExecutionData[], 28 | i: number, 29 | ): Promise { 30 | const pollId = this.getNodeParameter('pollId', i) as string; 31 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/polls/${pollId}`); 32 | } 33 | 34 | /** 35 | * Votes on a poll 36 | * POST /api/v1/polls/:id/votes 37 | */ 38 | export async function vote( 39 | this: IExecuteFunctions, 40 | baseUrl: string, 41 | items: INodeExecutionData[], 42 | i: number, 43 | ): Promise { 44 | const pollId = this.getNodeParameter('pollId', i) as string; 45 | const choices = this.getNodeParameter('choices', i) as number[]; 46 | 47 | return await handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/polls/${pollId}/votes`, { 48 | choices, 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /nodes/Mastodon/polls/PollProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const pollOperations = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['polls'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'View Poll', 17 | value: 'view', 18 | description: 'Get information about a poll', 19 | action: 'View a poll', 20 | }, 21 | { 22 | name: 'Vote on Poll', 23 | value: 'vote', 24 | description: 'Vote on a poll', 25 | action: 'Vote on a poll', 26 | }, 27 | ], 28 | default: 'view', 29 | }, 30 | ] as INodeProperties[]; 31 | 32 | export const pollFields = [ 33 | // Fields for viewing/voting on a poll 34 | { 35 | displayName: 'Poll ID', 36 | name: 'pollId', 37 | type: 'string', 38 | required: true, 39 | default: '', 40 | displayOptions: { 41 | show: { 42 | resource: ['polls'], 43 | operation: ['view', 'vote'], 44 | }, 45 | }, 46 | description: 'ID of the poll to interact with', 47 | }, 48 | // Fields for voting 49 | { 50 | displayName: 'Choices', 51 | name: 'choices', 52 | type: 'number[]', 53 | required: true, 54 | default: [], 55 | displayOptions: { 56 | show: { 57 | resource: ['polls'], 58 | operation: ['vote'], 59 | }, 60 | }, 61 | description: 'Array of chosen options (zero-based index)', 62 | }, 63 | ] as INodeProperties[]; 64 | -------------------------------------------------------------------------------- /nodes/Mastodon/polls/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as PollMethods from './PollMethods'; 3 | import { pollOperations, pollFields } from './PollProperties'; 4 | 5 | export const pollProperties: INodeProperties[] = [...pollOperations, ...pollFields]; 6 | 7 | export const pollMethods = { 8 | view: PollMethods.view, 9 | vote: PollMethods.vote, 10 | }; 11 | -------------------------------------------------------------------------------- /nodes/Mastodon/preferences/PreferenceMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | 4 | /** 5 | * Gets user preferences 6 | * GET /api/v1/preferences 7 | */ 8 | export async function get( 9 | this: IExecuteFunctions, 10 | baseUrl: string, 11 | items: INodeExecutionData[], 12 | i: number, 13 | ): Promise { 14 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/preferences`); 15 | } 16 | -------------------------------------------------------------------------------- /nodes/Mastodon/preferences/PreferenceProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const preferenceOperations = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['preferences'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'Get User Preferences', 17 | value: 'get', 18 | description: 'Get authenticated user preferences', 19 | action: 'Get user preferences', 20 | }, 21 | ], 22 | default: 'get', 23 | }, 24 | ] as INodeProperties[]; 25 | 26 | export const preferenceFields = [] as INodeProperties[]; 27 | -------------------------------------------------------------------------------- /nodes/Mastodon/preferences/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as PreferenceMethods from './PreferenceMethods'; 3 | import { preferenceOperations, preferenceFields } from './PreferenceProperties'; 4 | 5 | export const preferenceProperties: INodeProperties[] = [ 6 | ...preferenceOperations, 7 | ...preferenceFields, 8 | ]; 9 | 10 | export const preferenceMethods = { 11 | get: PreferenceMethods.get, 12 | }; 13 | -------------------------------------------------------------------------------- /nodes/Mastodon/profile/ProfileMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | 4 | interface ICredentialAccount { 5 | id: string; 6 | username: string; 7 | acct: string; 8 | avatar: string; 9 | header: string; 10 | [key: string]: any; 11 | } 12 | 13 | /** 14 | * Deletes profile avatar 15 | * DELETE /api/v1/profile/avatar 16 | */ 17 | export async function deleteAvatar( 18 | this: IExecuteFunctions, 19 | baseUrl: string, 20 | items: INodeExecutionData[], 21 | i: number, 22 | ): Promise { 23 | return await handleApiRequest.call(this, 'DELETE', `${baseUrl}/api/v1/profile/avatar`); 24 | } 25 | 26 | /** 27 | * Deletes profile header 28 | * DELETE /api/v1/profile/header 29 | */ 30 | export async function deleteHeader( 31 | this: IExecuteFunctions, 32 | baseUrl: string, 33 | items: INodeExecutionData[], 34 | i: number, 35 | ): Promise { 36 | return await handleApiRequest.call(this, 'DELETE', `${baseUrl}/api/v1/profile/header`); 37 | } 38 | -------------------------------------------------------------------------------- /nodes/Mastodon/profile/ProfileProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const profileOperations = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['profile'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'Delete Avatar', 17 | value: 'deleteAvatar', 18 | description: 'Delete profile avatar', 19 | action: 'Delete avatar', 20 | }, 21 | { 22 | name: 'Delete Header', 23 | value: 'deleteHeader', 24 | description: 'Delete profile header', 25 | action: 'Delete header', 26 | }, 27 | ], 28 | default: 'deleteAvatar', 29 | }, 30 | ] as INodeProperties[]; 31 | 32 | export const profileFields = [] as INodeProperties[]; 33 | -------------------------------------------------------------------------------- /nodes/Mastodon/profile/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as ProfileMethods from './ProfileMethods'; 3 | import { profileOperations, profileFields } from './ProfileProperties'; 4 | 5 | export const profileProperties: INodeProperties[] = [...profileOperations, ...profileFields]; 6 | 7 | export const profileMethods = { 8 | deleteAvatar: ProfileMethods.deleteAvatar, 9 | deleteHeader: ProfileMethods.deleteHeader, 10 | }; 11 | -------------------------------------------------------------------------------- /nodes/Mastodon/proofs/ProofMethods.ts: -------------------------------------------------------------------------------- 1 | // Modularized Proof methods for Mastodon node 2 | import { IExecuteFunctions } from 'n8n-workflow'; 3 | import { handleApiRequest } from '../Mastodon_Methods'; 4 | 5 | export async function listProofs(this: IExecuteFunctions, baseUrl: string) { 6 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/proofs`); 7 | } 8 | -------------------------------------------------------------------------------- /nodes/Mastodon/proofs/ProofProperties.ts: -------------------------------------------------------------------------------- 1 | // Modularized Proof properties for Mastodon node 2 | import { INodeProperties } from 'n8n-workflow'; 3 | 4 | export const proofOperations = [ 5 | { 6 | displayName: 'Operation', 7 | name: 'operation', 8 | type: 'options', 9 | noDataExpression: true, 10 | displayOptions: { 11 | show: { 12 | resource: ['proofs'], 13 | }, 14 | }, 15 | options: [ 16 | { 17 | name: 'List Identity Proofs', 18 | value: 'listProofs', 19 | description: 'Get a list of identity proofs for the authenticated user', 20 | action: 'List identity proofs', 21 | }, 22 | ], 23 | default: 'listProofs', 24 | }, 25 | ] as INodeProperties[]; 26 | 27 | // No additional fields needed for proofs operations 28 | export const proofFields = [] as INodeProperties[]; 29 | -------------------------------------------------------------------------------- /nodes/Mastodon/proofs/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './ProofMethods'; 3 | import { proofOperations, proofFields } from './ProofProperties'; 4 | 5 | export const proofProperties: INodeProperties[] = [...proofOperations, ...proofFields]; 6 | 7 | export const proofMethods = { 8 | listProofs: Methods.listProofs, 9 | }; 10 | -------------------------------------------------------------------------------- /nodes/Mastodon/push/PushInterfaces.ts: -------------------------------------------------------------------------------- 1 | // Push Notifications-related interfaces for Mastodon node 2 | 3 | export interface IPushSubscriptionKeys { 4 | p256dh: string; 5 | auth: string; 6 | } 7 | 8 | export interface IPushSubscriptionData { 9 | endpoint: string; 10 | keys: IPushSubscriptionKeys; 11 | } 12 | 13 | export interface IPushAlerts { 14 | mention?: boolean; 15 | status?: boolean; 16 | reblog?: boolean; 17 | follow?: boolean; 18 | follow_request?: boolean; 19 | favourite?: boolean; 20 | poll?: boolean; 21 | update?: boolean; 22 | 'admin.sign_up'?: boolean; 23 | 'admin.report'?: boolean; 24 | } 25 | 26 | // Push notification-related interfaces for Mastodon node 27 | export interface IWebPushSubscription { 28 | id: string; 29 | endpoint: string; 30 | server_key: string; 31 | alerts: IPushAlerts; 32 | policy: string; 33 | } 34 | -------------------------------------------------------------------------------- /nodes/Mastodon/push/PushMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | import { IWebPushSubscription, IPushSubscriptionData, IPushAlerts } from './PushInterfaces'; 4 | 5 | /** 6 | * Subscribe to push notifications 7 | * POST /api/v1/push/subscription 8 | * OAuth Scope: push 9 | */ 10 | export async function subscribe( 11 | this: IExecuteFunctions, 12 | baseUrl: string, 13 | items: INodeExecutionData[], 14 | i: number, 15 | ): Promise { 16 | const subscription = this.getNodeParameter('subscription', i) as IPushSubscriptionData; 17 | const alerts = this.getNodeParameter('alerts', i) as IPushAlerts; 18 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 19 | 20 | const body: IDataObject = { 21 | subscription, 22 | alerts, 23 | }; 24 | 25 | if (additionalFields.policy) { 26 | body.policy = additionalFields.policy; 27 | } 28 | if (additionalFields.data) { 29 | body.data = additionalFields.data; 30 | } 31 | 32 | return await handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/push/subscription`, body); 33 | } 34 | 35 | /** 36 | * Get current subscription 37 | * GET /api/v1/push/subscription 38 | * OAuth Scope: push 39 | */ 40 | export async function get( 41 | this: IExecuteFunctions, 42 | baseUrl: string, 43 | items: INodeExecutionData[], 44 | i: number, 45 | ): Promise { 46 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/push/subscription`); 47 | } 48 | 49 | /** 50 | * Update subscription 51 | * PUT /api/v1/push/subscription 52 | * OAuth Scope: push 53 | */ 54 | export async function update( 55 | this: IExecuteFunctions, 56 | baseUrl: string, 57 | items: INodeExecutionData[], 58 | i: number, 59 | ): Promise { 60 | const alerts = this.getNodeParameter('alerts', i) as IPushAlerts; 61 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 62 | 63 | const body: IDataObject = { 64 | alerts, 65 | }; 66 | 67 | if (additionalFields.policy) { 68 | body.policy = additionalFields.policy; 69 | } 70 | if (additionalFields.data) { 71 | body.data = additionalFields.data; 72 | } 73 | 74 | return await handleApiRequest.call(this, 'PUT', `${baseUrl}/api/v1/push/subscription`, body); 75 | } 76 | 77 | /** 78 | * Remove subscription 79 | * DELETE /api/v1/push/subscription 80 | * OAuth Scope: push 81 | */ 82 | export async function remove( 83 | this: IExecuteFunctions, 84 | baseUrl: string, 85 | items: INodeExecutionData[], 86 | i: number, 87 | ): Promise<{}> { 88 | return await handleApiRequest.call(this, 'DELETE', `${baseUrl}/api/v1/push/subscription`); 89 | } 90 | -------------------------------------------------------------------------------- /nodes/Mastodon/push/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './PushMethods'; 3 | import * as Properties from './PushProperties'; 4 | 5 | export const pushProperties: INodeProperties[] = [ 6 | Properties.pushOperations, 7 | ...Properties.pushFields, 8 | ]; 9 | 10 | export const pushMethods = { 11 | subscribe: Methods.subscribe, 12 | get: Methods.get, 13 | update: Methods.update, 14 | remove: Methods.remove, 15 | }; 16 | -------------------------------------------------------------------------------- /nodes/Mastodon/relationship/RelationshipInterfaces.ts: -------------------------------------------------------------------------------- 1 | export interface IRelationship { 2 | id: string; 3 | following: boolean; 4 | followed_by: boolean; 5 | blocking: boolean; 6 | blocked_by: boolean; 7 | muting: boolean; 8 | muting_notifications: boolean; 9 | requested: boolean; 10 | domain_blocking: boolean; 11 | showing_reblogs: boolean; 12 | endorsed: boolean; 13 | notifying: boolean; 14 | note: string; 15 | } 16 | -------------------------------------------------------------------------------- /nodes/Mastodon/reports/ReportMethods.ts: -------------------------------------------------------------------------------- 1 | // Modularized Report methods for Mastodon node 2 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 3 | import { handleApiRequest } from '../Mastodon_Methods'; 4 | 5 | export async function listReports( 6 | this: IExecuteFunctions, 7 | baseUrl: string, 8 | items: INodeExecutionData[], 9 | i: number, 10 | ) { 11 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 12 | const qs: IDataObject = {}; 13 | 14 | if (additionalFields.resolved !== undefined) { 15 | qs.resolved = additionalFields.resolved; 16 | } 17 | if (additionalFields.account_id) { 18 | qs.account_id = additionalFields.account_id; 19 | } 20 | if (additionalFields.target_account_id) { 21 | qs.target_account_id = additionalFields.target_account_id; 22 | } 23 | if (additionalFields.limit) { 24 | qs.limit = additionalFields.limit; 25 | } 26 | if (additionalFields.offset) { 27 | qs.offset = additionalFields.offset; 28 | } 29 | 30 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/admin/reports`, {}, qs); 31 | } 32 | 33 | export async function resolveReport( 34 | this: IExecuteFunctions, 35 | baseUrl: string, 36 | items: INodeExecutionData[], 37 | i: number, 38 | ) { 39 | const reportId = this.getNodeParameter('reportId', i) as string; 40 | return await handleApiRequest.call( 41 | this, 42 | 'POST', 43 | `${baseUrl}/api/v1/admin/reports/${reportId}/resolve`, 44 | ); 45 | } 46 | 47 | /** 48 | * Create a report 49 | * POST /api/v1/reports 50 | */ 51 | export async function create( 52 | this: IExecuteFunctions, 53 | baseUrl: string, 54 | items: INodeExecutionData[], 55 | i: number, 56 | ): Promise { 57 | const accountId = this.getNodeParameter('accountId', i) as string; 58 | const comment = this.getNodeParameter('comment', i) as string; 59 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 60 | 61 | const body: IDataObject = { 62 | account_id: accountId, 63 | comment, 64 | }; 65 | 66 | if (additionalFields.status_ids) { 67 | body.status_ids = additionalFields.status_ids; 68 | } 69 | 70 | if (additionalFields.forward !== undefined) { 71 | body.forward = additionalFields.forward as boolean; 72 | } 73 | 74 | return await handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/reports`, body); 75 | } 76 | -------------------------------------------------------------------------------- /nodes/Mastodon/reports/ReportProperties.ts: -------------------------------------------------------------------------------- 1 | // Modularized Report properties for Mastodon node 2 | import { INodeProperties } from 'n8n-workflow'; 3 | 4 | export const reportOperations = [ 5 | { 6 | displayName: 'Operation', 7 | name: 'operation', 8 | type: 'options', 9 | noDataExpression: true, 10 | displayOptions: { 11 | show: { 12 | resource: ['reports'], 13 | }, 14 | }, 15 | options: [ 16 | { 17 | name: 'List Reports', 18 | value: 'listReports', 19 | description: 'Get a list of user reports with optional filters', 20 | action: 'List reports', 21 | }, 22 | { 23 | name: 'Resolve Report', 24 | value: 'resolveReport', 25 | description: 'Mark a report as resolved', 26 | action: 'Resolve a report', 27 | }, 28 | { 29 | name: 'Create Report', 30 | value: 'create', 31 | description: 'File a report against an account', 32 | action: 'Create a report', 33 | }, 34 | ], 35 | default: 'listReports', 36 | }, 37 | ] as INodeProperties[]; 38 | 39 | export const reportFields = [ 40 | // Fields for listing reports 41 | { 42 | displayName: 'Additional Fields', 43 | name: 'additionalFields', 44 | type: 'collection', 45 | placeholder: 'Add Field', 46 | default: {}, 47 | displayOptions: { 48 | show: { 49 | resource: ['reports'], 50 | operation: ['listReports'], 51 | }, 52 | }, 53 | options: [ 54 | { 55 | displayName: 'Resolved', 56 | name: 'resolved', 57 | type: 'boolean', 58 | default: false, 59 | description: 'Filter by resolution status', 60 | }, 61 | { 62 | displayName: 'Account ID', 63 | name: 'account_id', 64 | type: 'string', 65 | default: '', 66 | description: 'Filter by account ID', 67 | }, 68 | { 69 | displayName: 'Target Account ID', 70 | name: 'target_account_id', 71 | type: 'string', 72 | default: '', 73 | description: 'Filter by target account ID', 74 | }, 75 | { 76 | displayName: 'Limit', 77 | name: 'limit', 78 | type: 'number', 79 | default: 20, 80 | description: 'Maximum number of results to return', 81 | }, 82 | { 83 | displayName: 'Offset', 84 | name: 'offset', 85 | type: 'number', 86 | default: 0, 87 | description: 'Number of reports to skip before returning results', 88 | }, 89 | ], 90 | }, 91 | // Fields for resolving a report 92 | { 93 | displayName: 'Report ID', 94 | name: 'reportId', 95 | type: 'string', 96 | required: true, 97 | default: '', 98 | displayOptions: { 99 | show: { 100 | resource: ['reports'], 101 | operation: ['resolveReport'], 102 | }, 103 | }, 104 | description: 'The ID of the report to resolve', 105 | }, 106 | // Fields for creating a report 107 | { 108 | displayName: 'Account ID', 109 | name: 'accountId', 110 | type: 'string', 111 | required: true, 112 | default: '', 113 | displayOptions: { 114 | show: { 115 | resource: ['reports'], 116 | operation: ['create'], 117 | }, 118 | }, 119 | description: 'ID of the account being reported', 120 | }, 121 | { 122 | displayName: 'Comment', 123 | name: 'comment', 124 | type: 'string', 125 | required: true, 126 | default: '', 127 | displayOptions: { 128 | show: { 129 | resource: ['reports'], 130 | operation: ['create'], 131 | }, 132 | }, 133 | description: 'Additional information about the report', 134 | }, 135 | { 136 | displayName: 'Additional Fields', 137 | name: 'additionalFields', 138 | type: 'collection', 139 | placeholder: 'Add Field', 140 | default: {}, 141 | displayOptions: { 142 | show: { 143 | resource: ['reports'], 144 | operation: ['create'], 145 | }, 146 | }, 147 | options: [ 148 | { 149 | displayName: 'Status IDs', 150 | name: 'status_ids', 151 | type: 'string', 152 | default: '', 153 | description: 'Array of status IDs to report (comma-separated)', 154 | }, 155 | { 156 | displayName: 'Forward', 157 | name: 'forward', 158 | type: 'boolean', 159 | default: false, 160 | description: 'Whether to forward the report to the remote instance', 161 | }, 162 | ], 163 | }, 164 | ] as INodeProperties[]; 165 | -------------------------------------------------------------------------------- /nodes/Mastodon/reports/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './ReportMethods'; 3 | import { reportOperations, reportFields } from './ReportProperties'; 4 | 5 | export const reportProperties: INodeProperties[] = [...reportOperations, ...reportFields]; 6 | 7 | export const reportMethods = { 8 | create: Methods.create, 9 | listReports: Methods.listReports, 10 | resolveReport: Methods.resolveReport, 11 | }; 12 | -------------------------------------------------------------------------------- /nodes/Mastodon/retention/RetentionMethods.ts: -------------------------------------------------------------------------------- 1 | // Modularized Retention methods for Mastodon node 2 | import { IExecuteFunctions } from 'n8n-workflow'; 3 | import { handleApiRequest } from '../Mastodon_Methods'; 4 | 5 | export async function viewStatistics(this: IExecuteFunctions, baseUrl: string) { 6 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/admin/retention`); 7 | } 8 | -------------------------------------------------------------------------------- /nodes/Mastodon/retention/RetentionProperties.ts: -------------------------------------------------------------------------------- 1 | // Modularized Retention properties for Mastodon node 2 | import { INodeProperties } from 'n8n-workflow'; 3 | 4 | export const retentionOperations = [ 5 | { 6 | displayName: 'Operation', 7 | name: 'operation', 8 | type: 'options', 9 | noDataExpression: true, 10 | displayOptions: { 11 | show: { 12 | resource: ['retention'], 13 | }, 14 | }, 15 | options: [ 16 | { 17 | name: 'View Statistics', 18 | value: 'viewStatistics', 19 | description: 'Get user retention statistics', 20 | action: 'View retention statistics', 21 | }, 22 | ], 23 | default: 'viewStatistics', 24 | }, 25 | ] as INodeProperties[]; 26 | 27 | // No additional fields needed for retention operations 28 | export const retentionFields = [] as INodeProperties[]; 29 | -------------------------------------------------------------------------------- /nodes/Mastodon/retention/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './RetentionMethods'; 3 | import { retentionOperations, retentionFields } from './RetentionProperties'; 4 | 5 | export const retentionProperties: INodeProperties[] = [...retentionOperations, ...retentionFields]; 6 | 7 | export const retentionMethods = { 8 | viewStatistics: Methods.viewStatistics, 9 | }; 10 | -------------------------------------------------------------------------------- /nodes/Mastodon/scheduledStatuses/ScheduledStatusMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | 4 | interface IScheduledStatus { 5 | id: string; 6 | scheduled_at: string; 7 | params: { 8 | text: string; 9 | media_ids: string[] | null; 10 | poll: object | null; 11 | in_reply_to_id: string | null; 12 | sensitive: boolean; 13 | spoiler_text: string | null; 14 | visibility: string; 15 | language: string | null; 16 | }; 17 | media_attachments: any[]; 18 | } 19 | 20 | /** 21 | * Lists scheduled statuses 22 | * GET /api/v1/scheduled_statuses 23 | */ 24 | export async function list( 25 | this: IExecuteFunctions, 26 | baseUrl: string, 27 | items: INodeExecutionData[], 28 | i: number, 29 | ): Promise { 30 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 31 | const qs: IDataObject = {}; 32 | 33 | if (additionalFields.limit) { 34 | qs.limit = Math.min(additionalFields.limit as number, 40); 35 | } 36 | if (additionalFields.max_id) { 37 | qs.max_id = additionalFields.max_id; 38 | } 39 | if (additionalFields.since_id) { 40 | qs.since_id = additionalFields.since_id; 41 | } 42 | if (additionalFields.min_id) { 43 | qs.min_id = additionalFields.min_id; 44 | } 45 | 46 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/scheduled_statuses`, {}, qs); 47 | } 48 | 49 | /** 50 | * Views a scheduled status 51 | * GET /api/v1/scheduled_statuses/:id 52 | */ 53 | export async function view( 54 | this: IExecuteFunctions, 55 | baseUrl: string, 56 | items: INodeExecutionData[], 57 | i: number, 58 | ): Promise { 59 | const statusId = this.getNodeParameter('statusId', i) as string; 60 | return await handleApiRequest.call( 61 | this, 62 | 'GET', 63 | `${baseUrl}/api/v1/scheduled_statuses/${statusId}`, 64 | ); 65 | } 66 | 67 | /** 68 | * Updates a scheduled status 69 | * PUT /api/v1/scheduled_statuses/:id 70 | */ 71 | export async function update( 72 | this: IExecuteFunctions, 73 | baseUrl: string, 74 | items: INodeExecutionData[], 75 | i: number, 76 | ): Promise { 77 | const statusId = this.getNodeParameter('statusId', i) as string; 78 | const scheduledAt = this.getNodeParameter('scheduledAt', i) as string; 79 | 80 | return await handleApiRequest.call( 81 | this, 82 | 'PUT', 83 | `${baseUrl}/api/v1/scheduled_statuses/${statusId}`, 84 | { scheduled_at: scheduledAt }, 85 | ); 86 | } 87 | 88 | /** 89 | * Cancels a scheduled status 90 | * DELETE /api/v1/scheduled_statuses/:id 91 | */ 92 | export async function cancel( 93 | this: IExecuteFunctions, 94 | baseUrl: string, 95 | items: INodeExecutionData[], 96 | i: number, 97 | ): Promise<{}> { 98 | const statusId = this.getNodeParameter('statusId', i) as string; 99 | return await handleApiRequest.call( 100 | this, 101 | 'DELETE', 102 | `${baseUrl}/api/v1/scheduled_statuses/${statusId}`, 103 | ); 104 | } 105 | -------------------------------------------------------------------------------- /nodes/Mastodon/scheduledStatuses/ScheduledStatusProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const scheduledStatusOperations = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['scheduledStatuses'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'List Scheduled Statuses', 17 | value: 'list', 18 | description: 'Get a list of scheduled statuses', 19 | action: 'List scheduled statuses', 20 | }, 21 | { 22 | name: 'View Scheduled Status', 23 | value: 'view', 24 | description: 'Get details of a scheduled status', 25 | action: 'View a scheduled status', 26 | }, 27 | { 28 | name: 'Update Scheduled Status', 29 | value: 'update', 30 | description: 'Update the scheduled time of a status', 31 | action: 'Update a scheduled status', 32 | }, 33 | { 34 | name: 'Cancel Scheduled Status', 35 | value: 'cancel', 36 | description: 'Cancel a scheduled status', 37 | action: 'Cancel a scheduled status', 38 | }, 39 | ], 40 | default: 'list', 41 | }, 42 | ] as INodeProperties[]; 43 | 44 | export const scheduledStatusFields = [ 45 | // Fields for listing scheduled statuses 46 | { 47 | displayName: 'Additional Fields', 48 | name: 'additionalFields', 49 | type: 'collection', 50 | placeholder: 'Add Field', 51 | default: {}, 52 | displayOptions: { 53 | show: { 54 | resource: ['scheduledStatuses'], 55 | operation: ['list'], 56 | }, 57 | }, 58 | options: [ 59 | { 60 | displayName: 'Limit', 61 | name: 'limit', 62 | type: 'number', 63 | typeOptions: { 64 | minValue: 1, 65 | maxValue: 40, 66 | }, 67 | default: 20, 68 | description: 'Maximum number of results to return (max: 40)', 69 | }, 70 | { 71 | displayName: 'Max ID', 72 | name: 'max_id', 73 | type: 'string', 74 | default: '', 75 | description: 'Return results older than this ID', 76 | }, 77 | { 78 | displayName: 'Since ID', 79 | name: 'since_id', 80 | type: 'string', 81 | default: '', 82 | description: 'Return results newer than this ID', 83 | }, 84 | { 85 | displayName: 'Min ID', 86 | name: 'min_id', 87 | type: 'string', 88 | default: '', 89 | description: 'Return results immediately newer than this ID', 90 | }, 91 | ], 92 | }, 93 | 94 | // Fields for viewing/updating/canceling a scheduled status 95 | { 96 | displayName: 'Status ID', 97 | name: 'statusId', 98 | type: 'string', 99 | required: true, 100 | default: '', 101 | displayOptions: { 102 | show: { 103 | resource: ['scheduledStatuses'], 104 | operation: ['view', 'update', 'cancel'], 105 | }, 106 | }, 107 | description: 'ID of the scheduled status', 108 | }, 109 | 110 | // Fields for updating scheduled time 111 | { 112 | displayName: 'Scheduled Time', 113 | name: 'scheduledAt', 114 | type: 'string', 115 | required: true, 116 | default: '', 117 | displayOptions: { 118 | show: { 119 | resource: ['scheduledStatuses'], 120 | operation: ['update'], 121 | }, 122 | }, 123 | description: 'The new scheduled time in ISO 8601 format', 124 | placeholder: '2025-05-01T10:00:00Z', 125 | }, 126 | ] as INodeProperties[]; 127 | -------------------------------------------------------------------------------- /nodes/Mastodon/scheduledStatuses/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as ScheduledStatusMethods from './ScheduledStatusMethods'; 3 | import { scheduledStatusOperations, scheduledStatusFields } from './ScheduledStatusProperties'; 4 | 5 | export const scheduledStatusProperties: INodeProperties[] = [ 6 | ...scheduledStatusOperations, 7 | ...scheduledStatusFields, 8 | ]; 9 | 10 | export const scheduledStatusMethods = { 11 | list: ScheduledStatusMethods.list, 12 | view: ScheduledStatusMethods.view, 13 | update: ScheduledStatusMethods.update, 14 | cancel: ScheduledStatusMethods.cancel, 15 | }; 16 | -------------------------------------------------------------------------------- /nodes/Mastodon/search/SearchInterfaces.ts: -------------------------------------------------------------------------------- 1 | // Search-related interfaces for Mastodon node 2 | import { IAccount } from '../account/AccountInterfaces'; 3 | 4 | export interface IHashtag { 5 | name: string; 6 | url: string; 7 | history: { day: string; uses: string; accounts: string }[]; 8 | } 9 | 10 | export interface IStatus { 11 | id: string; 12 | uri: string; 13 | url: string; 14 | account: IAccount; 15 | content: string; 16 | created_at: string; 17 | // ... other status fields as needed 18 | } 19 | 20 | export interface ISearchResults { 21 | accounts: IAccount[]; 22 | statuses: IStatus[]; 23 | hashtags: IHashtag[]; 24 | } 25 | -------------------------------------------------------------------------------- /nodes/Mastodon/search/SearchMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | import { ISearchResults } from './SearchInterfaces'; 4 | 5 | /** 6 | * Perform a search 7 | * GET /api/v2/search 8 | * OAuth Scope: read:search 9 | */ 10 | export async function search( 11 | this: IExecuteFunctions, 12 | baseUrl: string, 13 | items: INodeExecutionData[], 14 | i: number, 15 | ): Promise { 16 | const query = this.getNodeParameter('query', i) as string; 17 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 18 | 19 | const qs: IDataObject = { 20 | q: query, 21 | }; 22 | 23 | if (additionalFields.type) { 24 | qs.type = additionalFields.type as string; 25 | } 26 | if (additionalFields.resolve !== undefined) { 27 | qs.resolve = additionalFields.resolve as boolean; 28 | } 29 | if (additionalFields.following !== undefined) { 30 | qs.following = additionalFields.following as boolean; 31 | } 32 | if (additionalFields.account_id) { 33 | qs.account_id = additionalFields.account_id as string; 34 | } 35 | if (additionalFields.exclude_unreviewed !== undefined) { 36 | qs.exclude_unreviewed = additionalFields.exclude_unreviewed as boolean; 37 | } 38 | if (additionalFields.max_id) { 39 | qs.max_id = additionalFields.max_id as string; 40 | } 41 | if (additionalFields.min_id) { 42 | qs.min_id = additionalFields.min_id as string; 43 | } 44 | if (additionalFields.limit) { 45 | qs.limit = additionalFields.limit as number; 46 | } 47 | 48 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v2/search`, {}, qs); 49 | } 50 | -------------------------------------------------------------------------------- /nodes/Mastodon/search/SearchProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const searchOperations: INodeProperties = { 4 | displayName: 'Operation', 5 | name: 'operation', 6 | type: 'options', 7 | noDataExpression: true, 8 | displayOptions: { 9 | show: { 10 | resource: ['search'], 11 | }, 12 | }, 13 | options: [ 14 | { 15 | name: 'Search', 16 | value: 'search', 17 | description: 'Search for content', 18 | action: 'Search for content', 19 | }, 20 | ], 21 | default: 'search', 22 | }; 23 | 24 | export const searchFields: INodeProperties[] = [ 25 | { 26 | displayName: 'Query', 27 | name: 'query', 28 | type: 'string', 29 | default: '', 30 | required: true, 31 | displayOptions: { 32 | show: { 33 | resource: ['search'], 34 | operation: ['search'], 35 | }, 36 | }, 37 | description: 'The search query', 38 | }, 39 | { 40 | displayName: 'Additional Fields', 41 | name: 'additionalFields', 42 | type: 'collection', 43 | placeholder: 'Add Field', 44 | default: {}, 45 | displayOptions: { 46 | show: { 47 | resource: ['search'], 48 | operation: ['search'], 49 | }, 50 | }, 51 | options: [ 52 | { 53 | displayName: 'Type', 54 | name: 'type', 55 | type: 'options', 56 | options: [ 57 | { 58 | name: 'Accounts', 59 | value: 'accounts', 60 | }, 61 | { 62 | name: 'Hashtags', 63 | value: 'hashtags', 64 | }, 65 | { 66 | name: 'Statuses', 67 | value: 'statuses', 68 | }, 69 | ], 70 | default: '', 71 | description: 'Specify type of results to return', 72 | }, 73 | { 74 | displayName: 'Resolve', 75 | name: 'resolve', 76 | type: 'boolean', 77 | default: false, 78 | description: 'Attempt WebFinger lookup for remote accounts', 79 | }, 80 | { 81 | displayName: 'Following', 82 | name: 'following', 83 | type: 'boolean', 84 | default: false, 85 | description: 'Only return accounts the user is following', 86 | }, 87 | { 88 | displayName: 'Account ID', 89 | name: 'account_id', 90 | type: 'string', 91 | default: '', 92 | description: 'Filter statuses to this account ID', 93 | }, 94 | { 95 | displayName: 'Exclude Unreviewed', 96 | name: 'exclude_unreviewed', 97 | type: 'boolean', 98 | default: false, 99 | description: 'Exclude unreviewed tags', 100 | }, 101 | { 102 | displayName: 'Max ID', 103 | name: 'max_id', 104 | type: 'string', 105 | default: '', 106 | description: 'Return results older than this ID', 107 | }, 108 | { 109 | displayName: 'Min ID', 110 | name: 'min_id', 111 | type: 'string', 112 | default: '', 113 | description: 'Return results newer than this ID', 114 | }, 115 | { 116 | displayName: 'Limit', 117 | name: 'limit', 118 | type: 'number', 119 | default: 20, 120 | description: 'Maximum number of results to return', 121 | }, 122 | ], 123 | }, 124 | ]; 125 | -------------------------------------------------------------------------------- /nodes/Mastodon/search/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './SearchMethods'; 3 | import * as Properties from './SearchProperties'; 4 | 5 | export const searchProperties: INodeProperties[] = [ 6 | Properties.searchOperations, 7 | ...Properties.searchFields, 8 | ]; 9 | 10 | export const searchMethods = { 11 | search: Methods.search, 12 | }; 13 | -------------------------------------------------------------------------------- /nodes/Mastodon/status/StatusInterface.ts: -------------------------------------------------------------------------------- 1 | export interface IStatus { 2 | status: string; 3 | in_reply_to_id?: string; 4 | spoiler_text?: string; 5 | sensitive?: boolean; 6 | language?: string; 7 | 'media_ids[]'?: string; 8 | } 9 | -------------------------------------------------------------------------------- /nodes/Mastodon/status/StatusMethodsTypes.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | 3 | export interface IStatus { 4 | id: string; 5 | content: string; 6 | created_at: string; 7 | visibility: 'public' | 'unlisted' | 'private' | 'direct'; 8 | sensitive: boolean; 9 | spoiler_text?: string; 10 | media_attachments?: any[]; 11 | url: string; 12 | } 13 | 14 | export interface IMediaAttachment { 15 | id: string; 16 | type: string; 17 | url: string; 18 | preview_url: string; 19 | description?: string; 20 | } 21 | 22 | export interface IStatusSource { 23 | id: string; 24 | text: string; 25 | spoiler_text: string; 26 | } 27 | 28 | export interface IStatusMethods { 29 | create( 30 | this: IExecuteFunctions, 31 | baseUrl: string, 32 | items: INodeExecutionData[], 33 | i: number, 34 | ): Promise; 35 | 36 | view( 37 | this: IExecuteFunctions, 38 | baseUrl: string, 39 | items: INodeExecutionData[], 40 | i: number, 41 | ): Promise; 42 | 43 | delete( 44 | this: IExecuteFunctions, 45 | baseUrl: string, 46 | items: INodeExecutionData[], 47 | i: number, 48 | ): Promise; 49 | 50 | search( 51 | this: IExecuteFunctions, 52 | baseUrl: string, 53 | items: INodeExecutionData[], 54 | i: number, 55 | ): Promise; 56 | 57 | favourite( 58 | this: IExecuteFunctions, 59 | baseUrl: string, 60 | items: INodeExecutionData[], 61 | i: number, 62 | ): Promise; 63 | 64 | unfavourite( 65 | this: IExecuteFunctions, 66 | baseUrl: string, 67 | items: INodeExecutionData[], 68 | i: number, 69 | ): Promise; 70 | 71 | boost( 72 | this: IExecuteFunctions, 73 | baseUrl: string, 74 | items: INodeExecutionData[], 75 | i: number, 76 | ): Promise; 77 | 78 | unboost( 79 | this: IExecuteFunctions, 80 | baseUrl: string, 81 | items: INodeExecutionData[], 82 | i: number, 83 | ): Promise; 84 | 85 | bookmark( 86 | this: IExecuteFunctions, 87 | baseUrl: string, 88 | items: INodeExecutionData[], 89 | i: number, 90 | ): Promise; 91 | 92 | mediaUpload( 93 | this: IExecuteFunctions, 94 | baseUrl: string, 95 | items: INodeExecutionData[], 96 | i: number, 97 | ): Promise; 98 | 99 | scheduledStatuses( 100 | this: IExecuteFunctions, 101 | baseUrl: string, 102 | items: INodeExecutionData[], 103 | i: number, 104 | ): Promise; 105 | 106 | edit( 107 | this: IExecuteFunctions, 108 | baseUrl: string, 109 | items: INodeExecutionData[], 110 | i: number, 111 | ): Promise; 112 | 113 | viewEditHistory( 114 | this: IExecuteFunctions, 115 | baseUrl: string, 116 | items: INodeExecutionData[], 117 | i: number, 118 | ): Promise; 119 | 120 | viewSource( 121 | this: IExecuteFunctions, 122 | baseUrl: string, 123 | items: INodeExecutionData[], 124 | i: number, 125 | ): Promise; 126 | 127 | /** 128 | * Get status context (ancestors and descendants) 129 | * GET /api/v1/statuses/:id/context 130 | */ 131 | context( 132 | this: IExecuteFunctions, 133 | baseUrl: string, 134 | items: INodeExecutionData[], 135 | i: number, 136 | ): Promise; 137 | } 138 | -------------------------------------------------------------------------------- /nodes/Mastodon/status/index.ts: -------------------------------------------------------------------------------- 1 | // Aggregated status module for Mastodon node 2 | import { INodeProperties } from 'n8n-workflow'; 3 | import * as Props from './StatusProperties'; 4 | import StatusMethods from './StatusMethods'; 5 | 6 | export const statusProperties: INodeProperties[] = [ 7 | Props.statusOperations, 8 | ...Props.createFields, 9 | ...Props.deleteFields, 10 | ...Props.searchFields, 11 | ...Props.favouriteFields, 12 | ...Props.boostFields, 13 | ...Props.mediaUploadFields, 14 | ...Props.scheduledStatusesFields, 15 | ...Props.statusExtraFields, 16 | ...Props.contextFields, 17 | ]; 18 | 19 | export const statusMethods = { 20 | create: StatusMethods.create, 21 | delete: StatusMethods.delete, 22 | edit: StatusMethods.edit, 23 | search: StatusMethods.search, 24 | favourite: StatusMethods.favourite, 25 | unfavourite: StatusMethods.unfavourite, 26 | boost: StatusMethods.boost, 27 | mediaUpload: StatusMethods.mediaUpload, 28 | scheduledStatuses: StatusMethods.scheduledStatuses, 29 | view: StatusMethods.view, 30 | unboost: StatusMethods.unboost, 31 | bookmark: StatusMethods.bookmark, 32 | viewEditHistory: StatusMethods.viewEditHistory, 33 | viewSource: StatusMethods.viewSource, 34 | context: StatusMethods.context, 35 | }; 36 | -------------------------------------------------------------------------------- /nodes/Mastodon/streaming/StreamingInterfaces.ts: -------------------------------------------------------------------------------- 1 | import { IDataObject } from 'n8n-workflow'; 2 | 3 | export interface IStreamingParams { 4 | tag?: string; 5 | list?: string; 6 | local?: boolean; 7 | } 8 | 9 | export interface IStreamingResponse { 10 | event: string; 11 | payload: IDataObject; 12 | } 13 | -------------------------------------------------------------------------------- /nodes/Mastodon/streaming/StreamingMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | import { IStreamingParams, IStreamingResponse } from './StreamingInterfaces'; 4 | 5 | /** 6 | * Stream Public Timeline 7 | * GET /api/v1/streaming/public 8 | * Public access 9 | */ 10 | export async function streamPublic( 11 | this: IExecuteFunctions, 12 | baseUrl: string, 13 | items: INodeExecutionData[], 14 | i: number, 15 | ): Promise { 16 | const additionalFields = this.getNodeParameter('additionalFields', i) as IStreamingParams; 17 | const qs: IDataObject = {}; 18 | 19 | if (additionalFields.local !== undefined) { 20 | qs.local = additionalFields.local; 21 | } 22 | 23 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/streaming/public`, {}, qs, { 24 | encoding: null, 25 | }); 26 | } 27 | 28 | /** 29 | * Stream Hashtag Timeline 30 | * GET /api/v1/streaming/hashtag 31 | * Public access 32 | */ 33 | export async function streamHashtag( 34 | this: IExecuteFunctions, 35 | baseUrl: string, 36 | items: INodeExecutionData[], 37 | i: number, 38 | ): Promise { 39 | const tag = this.getNodeParameter('tag', i) as string; 40 | const additionalFields = this.getNodeParameter('additionalFields', i) as IStreamingParams; 41 | const qs: IDataObject = { tag }; 42 | 43 | if (additionalFields.local !== undefined) { 44 | qs.local = additionalFields.local; 45 | } 46 | 47 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/streaming/hashtag`, {}, qs, { 48 | encoding: null, 49 | }); 50 | } 51 | 52 | /** 53 | * Stream User Timeline 54 | * GET /api/v1/streaming/user 55 | * OAuth Scope: read:statuses 56 | */ 57 | export async function streamUser( 58 | this: IExecuteFunctions, 59 | baseUrl: string, 60 | items: INodeExecutionData[], 61 | i: number, 62 | ): Promise { 63 | return await handleApiRequest.call( 64 | this, 65 | 'GET', 66 | `${baseUrl}/api/v1/streaming/user`, 67 | {}, 68 | {}, 69 | { encoding: null }, 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /nodes/Mastodon/streaming/StreamingProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const streamingOperations: INodeProperties[] = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['streaming'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'Stream Public Timeline', 17 | value: 'public', 18 | description: 'Stream all public statuses', 19 | action: 'Stream public timeline', 20 | }, 21 | { 22 | name: 'Stream Hashtag Timeline', 23 | value: 'hashtag', 24 | description: 'Stream public statuses for a specific hashtag', 25 | action: 'Stream hashtag timeline', 26 | }, 27 | { 28 | name: 'Stream User Timeline', 29 | value: 'user', 30 | description: 'Stream user-related events', 31 | action: 'Stream user timeline', 32 | }, 33 | ], 34 | default: 'public', 35 | }, 36 | ]; 37 | 38 | export const streamingFields: INodeProperties[] = [ 39 | { 40 | displayName: 'Tag', 41 | name: 'tag', 42 | type: 'string', 43 | required: true, 44 | displayOptions: { 45 | show: { 46 | resource: ['streaming'], 47 | operation: ['hashtag'], 48 | }, 49 | }, 50 | default: '', 51 | description: 'The hashtag to stream (without the # symbol)', 52 | }, 53 | { 54 | displayName: 'Additional Fields', 55 | name: 'additionalFields', 56 | type: 'collection', 57 | placeholder: 'Add Field', 58 | displayOptions: { 59 | show: { 60 | resource: ['streaming'], 61 | operation: ['public', 'hashtag'], 62 | }, 63 | }, 64 | default: {}, 65 | options: [ 66 | { 67 | displayName: 'Local', 68 | name: 'local', 69 | type: 'boolean', 70 | default: false, 71 | description: 'Only show local statuses', 72 | }, 73 | ], 74 | }, 75 | ] as INodeProperties[]; 76 | -------------------------------------------------------------------------------- /nodes/Mastodon/streaming/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './StreamingMethods'; 3 | import * as Properties from './StreamingProperties'; 4 | 5 | export const streamingProperties: INodeProperties[] = [ 6 | ...Properties.streamingOperations, 7 | ...Properties.streamingFields, 8 | ]; 9 | 10 | export const streamingMethods = { 11 | public: Methods.streamPublic, 12 | hashtag: Methods.streamHashtag, 13 | user: Methods.streamUser, 14 | }; 15 | -------------------------------------------------------------------------------- /nodes/Mastodon/suggestions/SuggestionMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | 4 | /** 5 | * Gets follow suggestions 6 | * GET /api/v2/suggestions 7 | */ 8 | export async function get( 9 | this: IExecuteFunctions, 10 | baseUrl: string, 11 | items: INodeExecutionData[], 12 | i: number, 13 | ): Promise { 14 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 15 | const qs: IDataObject = {}; 16 | 17 | if (additionalFields.limit) { 18 | qs.limit = Math.min(additionalFields.limit as number, 80); 19 | } 20 | 21 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v2/suggestions`, {}, qs); 22 | } 23 | 24 | /** 25 | * Removes a suggestion 26 | * DELETE /api/v1/suggestions/:account_id 27 | */ 28 | export async function remove( 29 | this: IExecuteFunctions, 30 | baseUrl: string, 31 | items: INodeExecutionData[], 32 | i: number, 33 | ): Promise<{}> { 34 | const accountId = this.getNodeParameter('accountId', i) as string; 35 | return await handleApiRequest.call(this, 'DELETE', `${baseUrl}/api/v1/suggestions/${accountId}`); 36 | } 37 | -------------------------------------------------------------------------------- /nodes/Mastodon/suggestions/SuggestionProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const suggestionOperations = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['suggestions'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'Get Suggestions', 17 | value: 'get', 18 | description: 'Get follow suggestions', 19 | action: 'Get suggestions', 20 | }, 21 | { 22 | name: 'Remove Suggestion', 23 | value: 'remove', 24 | description: 'Remove an account from suggestions', 25 | action: 'Remove a suggestion', 26 | }, 27 | ], 28 | default: 'get', 29 | }, 30 | ] as INodeProperties[]; 31 | 32 | export const suggestionFields = [ 33 | { 34 | displayName: 'Account ID', 35 | name: 'accountId', 36 | type: 'string', 37 | required: true, 38 | default: '', 39 | displayOptions: { 40 | show: { 41 | resource: ['suggestions'], 42 | operation: ['remove'], 43 | }, 44 | }, 45 | description: 'ID of the account to remove from suggestions', 46 | }, 47 | { 48 | displayName: 'Additional Fields', 49 | name: 'additionalFields', 50 | type: 'collection', 51 | placeholder: 'Add Field', 52 | default: {}, 53 | displayOptions: { 54 | show: { 55 | resource: ['suggestions'], 56 | operation: ['get'], 57 | }, 58 | }, 59 | options: [ 60 | { 61 | displayName: 'Limit', 62 | name: 'limit', 63 | type: 'number', 64 | default: 40, 65 | typeOptions: { 66 | minValue: 1, 67 | maxValue: 80, 68 | }, 69 | description: 'Maximum number of results to return (max: 80)', 70 | }, 71 | ], 72 | }, 73 | ] as INodeProperties[]; 74 | -------------------------------------------------------------------------------- /nodes/Mastodon/suggestions/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as SuggestionMethods from './SuggestionMethods'; 3 | import { suggestionOperations, suggestionFields } from './SuggestionProperties'; 4 | 5 | export const suggestionProperties: INodeProperties[] = [ 6 | ...suggestionOperations, 7 | ...suggestionFields, 8 | ]; 9 | 10 | export const suggestionMethods = { 11 | get: SuggestionMethods.get, 12 | remove: SuggestionMethods.remove, 13 | }; 14 | -------------------------------------------------------------------------------- /nodes/Mastodon/tags/TagMethods.ts: -------------------------------------------------------------------------------- 1 | import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; 2 | import { handleApiRequest } from '../Mastodon_Methods'; 3 | 4 | interface ITag { 5 | name: string; 6 | url: string; 7 | history: { 8 | day: string; 9 | uses: string; 10 | accounts: string; 11 | }[]; 12 | } 13 | 14 | /** 15 | * Gets information about a tag 16 | * GET /api/v1/tags/:id 17 | */ 18 | export async function get( 19 | this: IExecuteFunctions, 20 | baseUrl: string, 21 | items: INodeExecutionData[], 22 | i: number, 23 | ): Promise { 24 | const tagId = this.getNodeParameter('tagId', i) as string; 25 | return await handleApiRequest.call(this, 'GET', `${baseUrl}/api/v1/tags/${tagId}`); 26 | } 27 | 28 | /** 29 | * Follows a hashtag 30 | * POST /api/v1/tags/:id/follow 31 | */ 32 | export async function follow( 33 | this: IExecuteFunctions, 34 | baseUrl: string, 35 | items: INodeExecutionData[], 36 | i: number, 37 | ): Promise { 38 | const tagId = this.getNodeParameter('tagId', i) as string; 39 | return await handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/tags/${tagId}/follow`); 40 | } 41 | 42 | /** 43 | * Unfollows a hashtag 44 | * POST /api/v1/tags/:id/unfollow 45 | */ 46 | export async function unfollow( 47 | this: IExecuteFunctions, 48 | baseUrl: string, 49 | items: INodeExecutionData[], 50 | i: number, 51 | ): Promise { 52 | const tagId = this.getNodeParameter('tagId', i) as string; 53 | return await handleApiRequest.call(this, 'POST', `${baseUrl}/api/v1/tags/${tagId}/unfollow`); 54 | } 55 | -------------------------------------------------------------------------------- /nodes/Mastodon/tags/TagProperties.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | 3 | export const tagOperations = [ 4 | { 5 | displayName: 'Operation', 6 | name: 'operation', 7 | type: 'options', 8 | noDataExpression: true, 9 | displayOptions: { 10 | show: { 11 | resource: ['tags'], 12 | }, 13 | }, 14 | options: [ 15 | { 16 | name: 'Get Tag Information', 17 | value: 'get', 18 | description: 'Get information about a hashtag', 19 | action: 'Get tag information', 20 | }, 21 | { 22 | name: 'Follow Tag', 23 | value: 'follow', 24 | description: 'Follow a hashtag', 25 | action: 'Follow a tag', 26 | }, 27 | { 28 | name: 'Unfollow Tag', 29 | value: 'unfollow', 30 | description: 'Unfollow a hashtag', 31 | action: 'Unfollow a tag', 32 | }, 33 | ], 34 | default: 'get', 35 | }, 36 | ] as INodeProperties[]; 37 | 38 | export const tagFields = [ 39 | { 40 | displayName: 'Tag ID', 41 | name: 'tagId', 42 | type: 'string', 43 | required: true, 44 | default: '', 45 | displayOptions: { 46 | show: { 47 | resource: ['tags'], 48 | operation: ['get', 'follow', 'unfollow'], 49 | }, 50 | }, 51 | description: 'The hashtag to interact with (without the #)', 52 | }, 53 | ] as INodeProperties[]; 54 | -------------------------------------------------------------------------------- /nodes/Mastodon/tags/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as TagMethods from './TagMethods'; 3 | import { tagOperations, tagFields } from './TagProperties'; 4 | 5 | export const tagProperties: INodeProperties[] = [...tagOperations, ...tagFields]; 6 | 7 | export const tagMethods = { 8 | get: TagMethods.get, 9 | follow: TagMethods.follow, 10 | unfollow: TagMethods.unfollow, 11 | }; 12 | -------------------------------------------------------------------------------- /nodes/Mastodon/timeline/TimelineInterfaces.ts: -------------------------------------------------------------------------------- 1 | // Modularized Timeline interfaces for Mastodon node 2 | import { IStatus } from '../status/StatusInterface'; 3 | 4 | export interface ITimelineParams { 5 | local?: boolean; 6 | remote?: boolean; 7 | only_media?: boolean; 8 | max_id?: string; 9 | since_id?: string; 10 | min_id?: string; 11 | limit?: number; 12 | any?: string[]; 13 | all?: string[]; 14 | none?: string[]; 15 | } 16 | 17 | export type ITimeline = IStatus[]; 18 | -------------------------------------------------------------------------------- /nodes/Mastodon/timeline/index.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import * as Methods from './TimelineMethods'; 3 | import * as Properties from './TimelineProperties'; 4 | 5 | export const timelineProperties: INodeProperties[] = [ 6 | ...Properties.timelineOperations, 7 | ...Properties.timelineFields, 8 | ]; 9 | 10 | export const timelineMethods = { 11 | public: Methods.publicTimeline, 12 | hashtag: Methods.hashtagTimeline, 13 | home: Methods.homeTimeline, 14 | list: Methods.listTimeline, 15 | link: Methods.linkTimeline, 16 | }; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "n8n-nodes-the-mastodon", 3 | "version": "0.0.1", 4 | "description": "Mastodon is a decentralized, open-source software that allows users to set up servers to communicate with each other.", 5 | "keywords": [ 6 | "n8n", 7 | "n8n-community-node", 8 | "n8n-community-node-package", 9 | "mastodon" 10 | ], 11 | "license": "MIT", 12 | "homepage": "https://github.com/redoracle/n8n-nodes-the-mastodon#readme", 13 | "author": { 14 | "name": "Community Effort!", 15 | "email": "n8n-support@redoracle.com" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/redoracle/n8n-nodes-the-mastodon.git" 20 | }, 21 | "main": "index.js", 22 | "type": "commonjs", 23 | "scripts": { 24 | "build": "tsc && gulp build:icons", 25 | "dev": "tsc --watch", 26 | "format": "prettier --write .", 27 | "lint": "eslint --config eslint.config.js .", 28 | "lintfix": "eslint --config eslint.config.js nodes credentials package.json --fix", 29 | "prepublishOnly": "npm run build && npm run lint", 30 | "test": "jest", 31 | "clean": "rm -rf dist node_modules package-lock.json", 32 | "fresh-install": "npm run clean && npm install", 33 | "update-deps": "npm update && npm audit fix" 34 | }, 35 | "files": [ 36 | "dist" 37 | ], 38 | "dependencies": { 39 | "pkce-challenge": "^2.2.0" 40 | }, 41 | "peerDependencies": { 42 | "n8n-core": ">=1.14.0", 43 | "n8n-workflow": ">=1.82.0" 44 | }, 45 | "n8n": { 46 | "n8nNodesApiVersion": 1, 47 | "credentials": [ 48 | "dist/credentials/MastodonOAuth2Api.credentials.js" 49 | ], 50 | "nodes": [ 51 | "dist/nodes/Mastodon/Mastodon.node.js" 52 | ] 53 | }, 54 | "devDependencies": { 55 | "@types/express": "^5.0.1", 56 | "@types/jest": "^29.5.14", 57 | "@types/node": "^22.15.2", 58 | "@types/request-promise-native": "~1.0.21", 59 | "@typescript-eslint/eslint-plugin": "^8.31.0", 60 | "@typescript-eslint/parser": "^8.31.0", 61 | "eslint": "^9.25.1", 62 | "eslint-plugin-n8n-nodes-base": "^1.16.3", 63 | "globals": "^16.0.0", 64 | "gulp": "^5.0.0", 65 | "jest": "^29.7.0", 66 | "n8n": "^1.90.2", 67 | "n8n-core": "^1.14.1", 68 | "n8n-workflow": "^1.82.0", 69 | "prettier": "^3.5.3", 70 | "ts-jest": "^29.3.2", 71 | "typescript": "^5.8.3" 72 | }, 73 | "overrides": { 74 | "gm": false, 75 | "@azure/core-http": false, 76 | "@aws-sdk/node-http-handler": "@smithy/node-http-handler", 77 | "dommatrix": "@thednp/dommatrix", 78 | "semver": "^7.0.0", 79 | "glob": "^10.0.0", 80 | "rimraf": "^5.0.0", 81 | "eslint": "^9.25.1" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/utils/pkceWrapper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file provides a CommonJS compatible wrapper around the pkce-challenge ESM module 3 | * to prevent the "require() of ES Module not supported" error 4 | */ 5 | 6 | // Using dynamic import to handle the ESM module 7 | async function generatePKCE(): Promise<{ code_verifier: string; code_challenge: string }> { 8 | try { 9 | const { default: pkce } = await import('pkce-challenge'); 10 | return pkce(); 11 | } catch (error) { 12 | console.error('Error importing pkce-challenge:', error); 13 | throw error; 14 | } 15 | } 16 | 17 | export { generatePKCE }; 18 | -------------------------------------------------------------------------------- /test-instructions.md: -------------------------------------------------------------------------------- 1 | # Testing Your Mastodon n8n Plugin Locally 2 | 3 | You now have a packaged version of your plugin: `n8n-nodes-the-mastodon-0.0.1.tgz` 4 | 5 | ## Method 1: Local Development with Custom Nodes Directory (Recommended) 6 | 7 | ```bash 8 | # Build and package the plugin 9 | cd ./n8n-nodes-the-mastodon && npm run build 10 | npm pack 11 | 12 | # Copy package to n8n custom nodes directory 13 | cp n8n-nodes-the-mastodon-0.0.1.tgz $HOME/.n8n/custom 14 | cd $HOME/.n8n/custom 15 | 16 | # Clean up any existing package and extract new one 17 | rm -rf package 18 | tar xzvf n8n-nodes-the-mastodon-0.0.1.tgz 19 | 20 | # Start n8n 21 | n8n start 22 | ``` 23 | 24 | ## Method 2: Install in Global n8n 25 | 26 | ```bash 27 | # Install the package globally in n8n 28 | npm install -g ./n8n-nodes-the-mastodon/n8n-nodes-the-mastodon-0.0.1.tgz 29 | 30 | # Start n8n 31 | npx n8n 32 | ``` 33 | 34 | ## Method 3: Use with n8n Docker 35 | 36 | ```bash 37 | # Create a directory for custom nodes 38 | mkdir -p ~/.n8n/custom 39 | 40 | # Extract your package to the custom nodes directory 41 | cd ~/.n8n/custom 42 | tar -xzf ./n8n-nodes-the-mastodon/n8n-nodes-the-mastodon-0.0.1.tgz 43 | 44 | # Run n8n with Docker 45 | docker run -it --rm \ 46 | --name n8n \ 47 | -p 5678:5678 \ 48 | -v ~/.n8n:/home/node/.n8n \ 49 | n8nio/n8n 50 | ``` 51 | 52 | ## Method 4: Development Environment with n8n Source 53 | 54 | ```bash 55 | # Clone n8n repository 56 | git clone https://github.com/n8n-io/n8n.git 57 | cd n8n 58 | 59 | # Install dependencies 60 | npm install 61 | 62 | # Link your package 63 | npm link ./n8n-nodes-the-mastodon 64 | 65 | # Start in development mode 66 | npm run dev 67 | ``` 68 | 69 | ## Method 5: Local Development Server 70 | 71 | ```bash 72 | # In your plugin directory, start a file watcher 73 | npm run build:watch 74 | 75 | # In another terminal, install the package and restart n8n when changes occur 76 | # This requires manual restarts but gives you faster iteration 77 | ``` 78 | 79 | ## Testing Your Plugin 80 | 81 | 1. **Start n8n** using one of the methods above 82 | 2. **Open** in your browser 83 | 3. **Create a new workflow** 84 | 4. **Search for "Mastodon"** in the nodes panel 85 | 5. **Add the Mastodon node** to your workflow 86 | 87 | ## Setting Up Credentials 88 | 89 | 1. **Create Mastodon OAuth2 Credentials**: 90 | - Go to your Mastodon instance (e.g., ) 91 | - Go to Settings > Development > New Application 92 | - Set the redirect URI to match n8n's OAuth callback 93 | - Copy the Client ID and Client Secret 94 | 95 | 2. **Configure in n8n**: 96 | - Add new credentials of type "Mastodon OAuth2 API" 97 | - Enter your Mastodon instance URL 98 | - Enter Client ID and Client Secret 99 | - Complete the OAuth flow 100 | 101 | ## Testing Operations 102 | 103 | Try these operations to verify everything works: 104 | 105 | 1. **Status Operations**: 106 | - Create a status 107 | - View a status 108 | - Get status context (test the new feature!) 109 | 110 | 2. **Account Operations**: 111 | - View user profile 112 | - Verify credentials 113 | - Search accounts 114 | 115 | 3. **Timeline Operations**: 116 | - Get public timeline 117 | - Get home timeline 118 | 119 | ## Debugging 120 | 121 | If you encounter issues: 122 | 123 | 1. **Check n8n logs** for error messages 124 | 2. **Verify the plugin was loaded** by looking for "Mastodon" in the nodes list 125 | 3. **Check credentials** are properly configured 126 | 4. **Test API endpoints** manually to ensure they work 127 | 128 | ## Hot Reloading for Development 129 | 130 | For faster development, you can: 131 | 132 | 1. Use `npm run build:watch` to automatically rebuild on changes 133 | 2. Restart n8n after each build to load the new version 134 | 3. Use n8n's development mode if working with the source 135 | 136 | Your plugin includes comprehensive status context functionality with multiple return formats and proper API compliance! 137 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2017", "es2019"], 4 | "types": ["node", "jest"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noImplicitAny": true, 8 | "removeComments": true, 9 | "strictNullChecks": true, 10 | "strict": true, 11 | "preserveConstEnums": true, 12 | "resolveJsonModule": true, 13 | "declaration": true, 14 | "outDir": "./dist/", 15 | "target": "es2019", 16 | "sourceMap": true, 17 | "esModuleInterop": true, 18 | "allowSyntheticDefaultImports": true, 19 | "useUnknownInCatchVariables": false, 20 | "skipLibCheck": true 21 | }, 22 | "include": [ 23 | "credentials/**/*", 24 | "nodes/**/*", 25 | "nodes/**/*.json", 26 | "package.json", 27 | "__tests__/**/*", 28 | "src/**/*" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "linterOptions": { 3 | "exclude": ["node_modules/**/*"] 4 | }, 5 | "defaultSeverity": "error", 6 | "jsRules": {}, 7 | "rules": { 8 | "array-type": [true, "array-simple"], 9 | "arrow-return-shorthand": true, 10 | "ban": [ 11 | true, 12 | { 13 | "name": "Array", 14 | "message": "tsstyle#array-constructor" 15 | } 16 | ], 17 | "ban-types": [ 18 | true, 19 | ["Object", "Use {} instead."], 20 | ["String", "Use 'string' instead."], 21 | ["Number", "Use 'number' instead."], 22 | ["Boolean", "Use 'boolean' instead."] 23 | ], 24 | "class-name": true, 25 | "curly": [true, "ignore-same-line"], 26 | "forin": true, 27 | "jsdoc-format": true, 28 | "label-position": true, 29 | "indent": [true, "tabs", 2], 30 | "member-access": [true, "no-public"], 31 | "new-parens": true, 32 | "no-angle-bracket-type-assertion": true, 33 | "no-any": true, 34 | "no-arg": true, 35 | "no-conditional-assignment": true, 36 | "no-construct": true, 37 | "no-debugger": true, 38 | "no-default-export": true, 39 | "no-duplicate-variable": true, 40 | "no-inferrable-types": true, 41 | "ordered-imports": [ 42 | true, 43 | { 44 | "import-sources-order": "any", 45 | "named-imports-order": "case-insensitive" 46 | } 47 | ], 48 | "no-namespace": [true, "allow-declarations"], 49 | "no-reference": true, 50 | "no-string-throw": true, 51 | "no-unused-expression": true, 52 | "no-var-keyword": true, 53 | "object-literal-shorthand": true, 54 | "only-arrow-functions": [true, "allow-declarations", "allow-named-functions"], 55 | "prefer-const": true, 56 | "radix": true, 57 | "semicolon": [true, "always", "ignore-bound-class-methods"], 58 | "switch-default": true, 59 | "trailing-comma": [ 60 | true, 61 | { 62 | "multiline": { 63 | "objects": "always", 64 | "arrays": "always", 65 | "functions": "always", 66 | "typeLiterals": "ignore" 67 | }, 68 | "esSpecCompliant": true 69 | } 70 | ], 71 | "triple-equals": [true, "allow-null-check"], 72 | "use-isnan": true, 73 | "quotes": ["error", "single"], 74 | "variable-name": [ 75 | true, 76 | "check-format", 77 | "ban-keywords", 78 | "allow-leading-underscore", 79 | "allow-trailing-underscore" 80 | ] 81 | }, 82 | "rulesDirectory": [], 83 | "deprecated": true 84 | } 85 | --------------------------------------------------------------------------------