├── .editorconfig ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── husky │ ├── .gitignore │ ├── commit-msg │ └── pre-commit ├── labeler.yml ├── labels.yml ├── logo.png ├── logo_short.png ├── problemMatchers │ ├── eslint.json │ └── tsc.json ├── renovate.json └── workflows │ ├── auto-deprecate.yml │ ├── codeql-analysis.yml │ ├── continuous-delivery.yml │ ├── continuous-integration.yml │ ├── deprecate-on-merge.yml │ ├── labelsync.yml │ ├── pr-triage.yml │ └── publish.yml ├── .gitignore ├── .npm-deprecaterc.yml ├── .prettierignore ├── .prettierrc.json ├── .vscode ├── extensions.json └── settings.json ├── .yarn └── releases │ └── yarn-4.9.1.cjs ├── .yarnrc.yml ├── LICENSE.md ├── README.md ├── apps └── website │ ├── app │ ├── [[...slugs]] │ │ ├── Edit.tsx │ │ └── page.tsx │ ├── api │ │ ├── og │ │ │ ├── Inter-SemiBold.otf │ │ │ └── route.tsx │ │ └── search │ │ │ └── route.ts │ ├── globals.css │ ├── index.tsx │ ├── layout.tsx │ ├── provider.tsx │ └── source.ts │ ├── content │ └── docs │ │ ├── api │ │ ├── @tagscript │ │ │ ├── meta.json │ │ │ └── plugin-discord │ │ │ │ └── meta.json │ │ ├── index.mdx │ │ ├── meta.json │ │ ├── plugins │ │ │ └── meta.json │ │ └── tagscript │ │ │ └── meta.json │ │ ├── index.mdx │ │ ├── meta.json │ │ ├── plugins │ │ ├── index.mdx │ │ └── plugin-discord │ │ │ ├── index.mdx │ │ │ ├── meta.json │ │ │ └── parsers │ │ │ ├── embed.mdx │ │ │ └── index.mdx │ │ └── tagscript │ │ ├── index.mdx │ │ ├── meta.json │ │ ├── parsers │ │ ├── break.mdx │ │ ├── if-statement.mdx │ │ ├── index.mdx │ │ ├── intersection-statement.mdx │ │ ├── meta.json │ │ └── union-statement.mdx │ │ └── transformers │ │ ├── index.mdx │ │ ├── meta.json │ │ └── string.mdx │ ├── eslint.config.js │ ├── images │ ├── logo.png │ └── logo_short.png │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── postcss.config.cjs │ ├── source.config.ts │ └── tsconfig.json ├── eslint.config.mjs ├── package.json ├── packages ├── tagscript-plugin-discord │ ├── .cliff-jumperrc.yml │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── cliff.toml │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── Parsers │ │ │ │ ├── AllowDeny.ts │ │ │ │ ├── Cooldown.ts │ │ │ │ ├── Delete.ts │ │ │ │ ├── Embed.ts │ │ │ │ ├── Files.ts │ │ │ │ ├── Format.ts │ │ │ │ ├── Silent.ts │ │ │ │ └── index.ts │ │ │ ├── Transformer │ │ │ │ ├── Base.ts │ │ │ │ ├── Guild.ts │ │ │ │ ├── GuildMember.ts │ │ │ │ ├── GuildTextBasedChannel.ts │ │ │ │ ├── Interaction.ts │ │ │ │ ├── Role.ts │ │ │ │ ├── User.ts │ │ │ │ └── index.ts │ │ │ ├── Utils │ │ │ │ ├── CommandInteraction.ts │ │ │ │ ├── index.ts │ │ │ │ └── resolveColor.ts │ │ │ └── interfaces │ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── tests │ │ ├── Parsers │ │ │ ├── AllowDeny.test.ts │ │ │ ├── Cooldown.test.ts │ │ │ ├── Delete.test.ts │ │ │ ├── Embed.test.ts │ │ │ ├── Files.test.ts │ │ │ ├── Format.test.ts │ │ │ └── Silent.test.ts │ │ ├── Structures │ │ │ └── Structures.ts │ │ ├── Transformer │ │ │ ├── Guild.test.ts │ │ │ ├── GuildMember.test.ts │ │ │ ├── GuildTextBasedChannel.test.ts │ │ │ ├── Interaction.test.ts │ │ │ ├── Role.test.ts │ │ │ ├── User.test.ts │ │ │ └── __snapshots__ │ │ │ │ ├── Guild.test.ts.snap │ │ │ │ ├── GuildMember.test.ts.snap │ │ │ │ ├── GuildTextBasedChannel.test.ts.snap │ │ │ │ ├── Interaction.test.ts.snap │ │ │ │ ├── Role.test.ts.snap │ │ │ │ └── User.test.ts.snap │ │ ├── Utils │ │ │ ├── CommandInteraction.test.ts │ │ │ └── resolveColor.test.ts │ │ └── tsconfig.json │ ├── tsconfig.typecheck.json │ ├── tsup.config.ts │ ├── typedoc.json │ └── vitest.config.ts └── tagscript │ ├── .cliff-jumperrc.yml │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── cliff.toml │ ├── package.json │ ├── src │ ├── index.ts │ ├── lib │ │ ├── Interpreter │ │ │ ├── Context.ts │ │ │ ├── Interpreter.ts │ │ │ ├── Lexer.ts │ │ │ ├── Node.ts │ │ │ ├── Response.ts │ │ │ └── index.ts │ │ ├── Parsers │ │ │ ├── Base.ts │ │ │ ├── Break.ts │ │ │ ├── Control.ts │ │ │ ├── Define.ts │ │ │ ├── FiftyFifty.ts │ │ │ ├── Format.ts │ │ │ ├── Includes.ts │ │ │ ├── JSONVar.ts │ │ │ ├── LooseVars.ts │ │ │ ├── Random.ts │ │ │ ├── Range.ts │ │ │ ├── Replace.ts │ │ │ ├── Slice.ts │ │ │ ├── Stop.ts │ │ │ ├── StrictVars.ts │ │ │ ├── UrlEncoded.ts │ │ │ └── index.ts │ │ ├── Transformer │ │ │ ├── Function.ts │ │ │ ├── Integer.ts │ │ │ ├── Object.ts │ │ │ ├── String.ts │ │ │ └── index.ts │ │ ├── Utils │ │ │ └── Util.ts │ │ └── interfaces │ │ │ └── index.ts │ └── tsconfig.json │ ├── tests │ ├── Interpreter │ │ ├── Context.test.ts │ │ ├── Interpreter.test.ts │ │ ├── Lexer.test.ts │ │ ├── Node.test.ts │ │ ├── Response.test.ts │ │ └── parenType.test.ts │ ├── Parsers │ │ ├── Break.test.ts │ │ ├── Control.test.ts │ │ ├── Define.test.ts │ │ ├── FiftyFifty.test.ts │ │ ├── Format.test.ts │ │ ├── Includes.test.ts │ │ ├── JSONVar.test.ts │ │ ├── Random.test.ts │ │ ├── Range.test.ts │ │ ├── Replace.test.ts │ │ ├── Slice.test.ts │ │ ├── Stop.test.ts │ │ └── UrlEncoded.test.ts │ ├── Transformer │ │ ├── Function.test.ts │ │ ├── Integer.test.ts │ │ ├── Object.test.ts │ │ └── String.test.ts │ ├── Utils │ │ └── Util.test.ts │ └── tsconfig.json │ ├── tsconfig.typecheck.json │ ├── tsup.config.ts │ ├── typedoc.json │ └── vitest.config.ts ├── scripts ├── clean-full.mjs ├── clean.mjs ├── externalConfig.mjs ├── generateDocs.mjs ├── tsconfig.json ├── tsup.config.ts ├── uploadCoverage │ └── action.yml ├── vitest.config.ts └── yarnCache │ └── action.yml ├── tsconfig.json ├── turbo.json ├── typedoc.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.{js,ts}] 10 | indent_size = 4 11 | indent_style = tab 12 | block_comment_start = /* 13 | block_comment = * 14 | block_comment_end = */ 15 | 16 | [*.{yml,yaml}] 17 | indent_size = 2 18 | indent_style = space 19 | 20 | [*.{md,rmd,mkd,mkdn,mdwn,mdown,markdown,litcoffee}] 21 | tab_width = 4 22 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @imranbarbhuiya -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [imranbarbhuiya] 2 | -------------------------------------------------------------------------------- /.github/husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.github/husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit $1 -------------------------------------------------------------------------------- /.github/husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | apps:website: 2 | - changed-files: 3 | - any-glob-to-any-file: apps/website/** 4 | 5 | package:tagscript: 6 | - changed-files: 7 | - any-glob-to-any-file: packages/tagscript/** 8 | 9 | plugin:discord: 10 | - changed-files: 11 | - any-glob-to-any-file: packages/tagscript-plugin-discord/** 12 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | - name: blocked 2 | color: 'fc1423' 3 | - name: bug:confirmed 4 | color: 'd73a4a' 5 | - name: bug:unverified 6 | color: 'f9d0c4' 7 | - name: chore 8 | color: 'ffffff' 9 | - name: ci 10 | color: '0075ca' 11 | - name: dependencies 12 | color: '276bd1' 13 | - name: discussion 14 | color: 'b6b1f9' 15 | - name: documentation 16 | color: '0075ca' 17 | - name: duplicate 18 | color: 'cfd3d7' 19 | - name: error handling 20 | color: 'ffff00' 21 | - name: feature 22 | color: 'ffff00' 23 | - name: hacktoberfest-accepted 24 | color: 'EE4700' 25 | - name: has PR 26 | color: '4b1f8e' 27 | - name: help wanted 28 | color: '008672' 29 | - name: in progress 30 | color: 'ffccd7' 31 | - name: in review 32 | color: 'aed5fc' 33 | - name: invalid 34 | color: 'e4e669' 35 | - name: need repro 36 | color: 'c66037' 37 | - name: needs rebase & merge 38 | color: '24853c' 39 | - name: performance 40 | color: '80c042' 41 | - name: priority:critical 42 | color: 'b60205' 43 | - name: priority:high 44 | color: 'fc1423' 45 | - name: refactor 46 | color: '1d637f' 47 | - name: regression 48 | color: 'ea8785' 49 | - name: semver:major 50 | color: 'c10f47' 51 | - name: semver:minor 52 | color: 'e4f486' 53 | - name: semver:patch 54 | color: 'e8be8b' 55 | - name: tests 56 | color: 'f06dff' 57 | - name: typings 58 | color: '80c042' 59 | - name: wontfix 60 | color: 'ffffff' 61 | - name: 'apps:website' 62 | color: '7f00ff' 63 | - name: 'package:tagscript' 64 | color: '7f00ff' 65 | - name: 'plugin-discord' 66 | color: '7f00ff' 67 | -------------------------------------------------------------------------------- /.github/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranbarbhuiya/TagScript/750c86fe12879c44fc4ec3b2628d921c1b290359/.github/logo.png -------------------------------------------------------------------------------- /.github/logo_short.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranbarbhuiya/TagScript/750c86fe12879c44fc4ec3b2628d921c1b290359/.github/logo_short.png -------------------------------------------------------------------------------- /.github/problemMatchers/eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "eslint-stylish", 5 | "pattern": [ 6 | { 7 | "regexp": "^([^\\s].*)$", 8 | "file": 1 9 | }, 10 | { 11 | "regexp": "^\\s+(\\d+):(\\d+)\\s+(error|warning|info)\\s+(.*)\\s\\s+(.*)$", 12 | "line": 1, 13 | "column": 2, 14 | "severity": 3, 15 | "message": 4, 16 | "code": 5, 17 | "loop": true 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.github/problemMatchers/tsc.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "tsc", 5 | "pattern": [ 6 | { 7 | "regexp": "^(?:\\s+\\d+\\>)?([^\\s].*)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\)\\s*:\\s+(error|warning|info)\\s+(\\w{1,2}\\d+)\\s*:\\s*(.*)$", 8 | "file": 1, 9 | "location": 2, 10 | "severity": 3, 11 | "code": 4, 12 | "message": 5 13 | } 14 | ] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["github>imranbarbhuiya/dotfiles:parbez-renovate"] 4 | } 5 | -------------------------------------------------------------------------------- /.github/workflows/auto-deprecate.yml: -------------------------------------------------------------------------------- 1 | name: NPM Auto Deprecate 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | jobs: 8 | auto-deprecate: 9 | name: NPM Auto Deprecate 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout Project 13 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 14 | - name: Use Node.js v22 15 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 16 | with: 17 | node-version: 22 18 | cache: yarn 19 | registry-url: https://registry.npmjs.org/ 20 | - name: Install Dependencies 21 | uses: ./scripts/yarnCache 22 | - name: Build Dependencies 23 | run: yarn turbo run build --filter=tagscript 24 | - name: Deprecate versions 25 | run: yarn npm-deprecate 26 | env: 27 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 28 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: Code Scanning 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | schedule: 11 | - cron: '30 1 * * 0' 12 | 13 | jobs: 14 | CodeQL: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 20 | 21 | - name: Initialize CodeQL 22 | uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3 23 | 24 | - name: Autobuild 25 | uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3 26 | 27 | - name: Perform CodeQL Analysis 28 | uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3 29 | -------------------------------------------------------------------------------- /.github/workflows/continuous-delivery.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Delivery 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | prNumber: 7 | description: The number of the PR that is being deployed 8 | required: true 9 | ref: 10 | description: The branch that is being deployed. Should be a branch on the given repository 11 | required: false 12 | default: main 13 | repository: 14 | description: The {owner}/{repository} that is being deployed. 15 | required: false 16 | default: imranbarbhuiya/tagscript 17 | push: 18 | branches: 19 | - main 20 | 21 | jobs: 22 | Publish: 23 | name: Publish Next to npm 24 | permissions: 25 | id-token: write 26 | runs-on: ubuntu-latest 27 | if: github.repository_owner == 'imranbarbhuiya' 28 | steps: 29 | - name: Checkout Project 30 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 31 | with: 32 | fetch-depth: 0 33 | repository: ${{ github.event.inputs.repository || 'imranbarbhuiya/tagscript' }} 34 | ref: ${{ github.event.inputs.ref || 'main' }} 35 | - name: Add TypeScript problem matcher 36 | run: echo "::add-matcher::.github/problemMatchers/tsc.json" 37 | - name: Use Node.js v22 38 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 39 | with: 40 | node-version: 22 41 | cache: yarn 42 | registry-url: https://registry.yarnpkg.com/ 43 | - name: Install Dependencies 44 | uses: ./scripts/yarnCache 45 | - name: Build all packages 46 | run: yarn build --filter="./packages/*" 47 | - name: Bump Versions & Publish 48 | run: | 49 | # Resolve the tag to be used. "next" for push events, "pr-{prNumber}" for dispatch events. 50 | TAG=$([[ ${{ github.event_name }} == 'push' ]] && echo 'next' || echo 'pr-${{ github.event.inputs.prNumber }}') 51 | yarn config set npmAuthToken ${NODE_AUTH_TOKEN} 52 | yarn config set npmPublishRegistry "https://registry.yarnpkg.com" 53 | for d in packages/*; do 54 | pushd $d 55 | yarn bump --preid "${TAG}.$(git rev-parse --verify --short HEAD)" --skip-changelog 56 | yarn npm publish --tag ${TAG} 57 | popd 58 | done 59 | env: 60 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | NPM_CONFIG_PROVENANCE: true 63 | -------------------------------------------------------------------------------- /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | Linting: 15 | name: Linting 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout Project 19 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 20 | - name: Add problem matcher 21 | run: echo "::add-matcher::.github/problemMatchers/eslint.json" 22 | - name: Use Node.js v22 23 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 24 | with: 25 | node-version: 22 26 | cache: yarn 27 | registry-url: https://registry.npmjs.org/ 28 | - name: Install Dependencies 29 | uses: ./scripts/yarnCache 30 | - name: Run ESLint 31 | run: yarn lint -- --fix=false 32 | 33 | Building: 34 | name: Compile source code 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: Checkout Project 38 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 39 | - name: Add problem matcher 40 | run: echo "::add-matcher::.github/problemMatchers/tsc.json" 41 | - name: Use Node.js v22 42 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 43 | with: 44 | node-version: 22 45 | cache: yarn 46 | registry-url: https://registry.npmjs.org/ 47 | - name: Install Dependencies 48 | uses: ./scripts/yarnCache 49 | - name: Compile Projects 50 | run: yarn build 51 | - name: Upload tagscript dist folders to artifacts 52 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 53 | with: 54 | name: dist 55 | path: packages/tagscript/dist/ 56 | if-no-files-found: error 57 | 58 | Testing: 59 | name: Unit Tests 60 | runs-on: ubuntu-latest 61 | needs: Building 62 | steps: 63 | - name: Checkout Project 64 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 65 | - name: Download tagscript dist folder from artifacts 66 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 67 | with: 68 | name: dist 69 | path: packages/tagscript/dist/ 70 | - name: Use Node.js v22 71 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 72 | with: 73 | node-version: 22 74 | cache: yarn 75 | registry-url: https://registry.npmjs.org/ 76 | - name: Install Dependencies 77 | uses: ./scripts/yarnCache 78 | 79 | - name: Run tests 80 | run: yarn test -- --coverage 81 | 82 | - name: Upload Coverage 83 | if: github.repository_owner == 'imranbarbhuiya' 84 | uses: ./scripts/uploadCoverage 85 | env: 86 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 87 | -------------------------------------------------------------------------------- /.github/workflows/deprecate-on-merge.yml: -------------------------------------------------------------------------------- 1 | name: NPM Deprecate PR versions On Merge 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - closed 7 | 8 | jobs: 9 | deprecate-on-merge: 10 | name: NPM Deprecate PR versions On Merge 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Project 14 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 15 | - name: Use Node.js v22 16 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 17 | with: 18 | node-version: 22 19 | cache: yarn 20 | registry-url: https://registry.npmjs.org/ 21 | - name: Install Dependencies 22 | uses: ./scripts/yarnCache 23 | - name: Build Dependencies 24 | run: yarn turbo run build --filter=tagscript 25 | - name: Deprecate versions 26 | run: yarn npm-deprecate --name "*pr-${PR_NUMBER}*" -d -v 27 | env: 28 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 29 | PR_NUMBER: ${{ github.event.number }} 30 | -------------------------------------------------------------------------------- /.github/workflows/labelsync.yml: -------------------------------------------------------------------------------- 1 | name: Automatic Label Sync 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | label_sync: 10 | name: Automatic Label Synchronization 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Project 14 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 15 | - name: Run Label Sync 16 | uses: crazy-max/ghaction-github-labeler@24d110aa46a59976b8a7f35518cb7f14f434c916 # v5 17 | with: 18 | github-token: ${{ secrets.GITHUB_TOKEN }} 19 | yaml-file: .github/labels.yml 20 | -------------------------------------------------------------------------------- /.github/workflows/pr-triage.yml: -------------------------------------------------------------------------------- 1 | name: 'PR Triage' 2 | on: 3 | pull_request_target: 4 | jobs: 5 | pr-triage: 6 | name: PR Triage 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Automatically label PR 10 | uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5 11 | with: 12 | repo-token: '${{ secrets.GITHUB_TOKEN }}' 13 | sync-labels: true 14 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | PublishPackage: 8 | name: Publish 9 | runs-on: ubuntu-latest 10 | if: github.repository_owner == 'imranbarbhuiya' 11 | steps: 12 | - name: Checkout Project 13 | uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | ref: main 17 | - name: Use Node.js v22 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 22 21 | cache: yarn 22 | registry-url: https://registry.yarnpkg.com/ 23 | - name: Install Dependencies 24 | uses: ./scripts/yarnCache 25 | - name: Build all packages 26 | run: yarn build --filter="./packages/*" 27 | - name: Configure Git 28 | run: | 29 | git remote set-url origin "https://${GITHUB_TOKEN}:x-oauth-basic@github.com/${GITHUB_REPOSITORY}.git" 30 | git config --local user.email "${GITHUB_EMAIL}" 31 | git config --local user.name "${GITHUB_USER}" 32 | env: 33 | GITHUB_USER: github-actions[bot] 34 | GITHUB_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | - name: Bump Versions and make release 37 | run: yarn bump 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | - name: Publish to NPM 41 | run: | 42 | yarn config set npmAuthToken ${NODE_AUTH_TOKEN} 43 | yarn config set npmPublishRegistry "https://registry.yarnpkg.com" 44 | for d in packages/*; do 45 | pushd $d 46 | yarn bump 47 | yarn npm publish 48 | popd 49 | done 50 | env: 51 | NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} 52 | NPM_CONFIG_PROVENANCE: true 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | 10 | # Diagnostic reports () 11 | 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | 32 | .nyc_output 33 | 34 | # Grunt intermediate storage () 35 | 36 | .grunt 37 | 38 | # Bower dependency directory () 39 | 40 | bower_components 41 | 42 | # node-waf configuration 43 | 44 | .lock-wscript 45 | 46 | # Compiled binary addons () 47 | 48 | build/Release 49 | 50 | # Dependency directories 51 | 52 | node_modules/ 53 | jspm_packages/ 54 | 55 | # Snowpack dependency directory () 56 | 57 | web_modules/ 58 | 59 | # TypeScript cache 60 | 61 | *.tsbuildinfo 62 | 63 | # Optional npm cache directory 64 | 65 | .npm 66 | 67 | # Optional eslint and prettier cache 68 | 69 | .eslintcache 70 | .prettiercache 71 | 72 | # Output of 'npm pack' 73 | 74 | *.tgz 75 | 76 | # Yarn Integrity file 77 | 78 | .yarn-integrity 79 | 80 | # dotenv environment variables file 81 | 82 | .env 83 | .env.local 84 | 85 | 86 | # Next.js build output 87 | 88 | .next 89 | dist 90 | 91 | # Gatsby files 92 | 93 | .cache/ 94 | 95 | # yarn v2 96 | 97 | .yarn/cache 98 | .yarn/unplugged 99 | .yarn/build-state.yml 100 | .yarn/install-state.gz 101 | .pnp.* 102 | 103 | # Typedoc output 104 | apps/website/content/docs/api/**/*.md 105 | 106 | # turbo cache 107 | .turbo 108 | 109 | # fumadocs source 110 | .source -------------------------------------------------------------------------------- /.npm-deprecaterc.yml: -------------------------------------------------------------------------------- 1 | name: '*next*' 2 | verbose: true 3 | package: 4 | - 'tagscript' 5 | - '@tagscript/plugin-discord' 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .yarn/ 2 | coverage/ 3 | docs/ 4 | !docs/**/*.json 5 | .yarnrc.yml 6 | **/CHANGELOG.md -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "printWidth": 150, 4 | "quoteProps": "as-needed", 5 | "semi": true, 6 | "singleQuote": true, 7 | "tabWidth": 4, 8 | "trailingComma": "none", 9 | "useTabs": true 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "dbaeumer.vscode-eslint", 5 | "github.vscode-pull-request-github", 6 | "eamodio.gitlens", 7 | "christian-kohler.npm-intellisense", 8 | "christian-kohler.path-intellisense", 9 | "unifiedjs.vscode-mdx", 10 | "bradlc.vscode-tailwindcss" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 4, 3 | "editor.useTabStops": true, 4 | "editor.insertSpaces": false, 5 | "editor.detectIndentation": false, 6 | "files.eol": "\n", 7 | "cSpell.words": ["cooldown", "tagscript"], 8 | "editor.codeActionsOnSave": { 9 | "source.fixAll.eslint": "explicit" 10 | }, 11 | "eslint.validate": ["markdown", "mdx", "javascript", "javascriptreact", "typescript", "typescriptreact"], 12 | "files.associations": { 13 | "*.css": "tailwindcss" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | logFilters: 4 | - code: YN0002 5 | level: discard 6 | - code: YN0060 7 | level: discard 8 | 9 | nodeLinker: node-modules 10 | 11 | yarnPath: .yarn/releases/yarn-4.9.1.cjs 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Parbez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | TagScript Logo 4 | 5 | # TagScript 6 | 7 | **A simple and safe template engine.** 8 | 9 | [![Continuous Integration](https://github.com/imranbarbhuiya/TagScript/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/imranbarbhuiya/TagScript/actions/workflows/continuous-integration.yml) 10 | [![codecov](https://codecov.io/gh/imranbarbhuiya/tagscript/branch/main/graph/badge.svg?precision=2)](https://codecov.io/gh/imranbarbhuiya/tagscript) 11 | 12 | ## Packages 13 | 14 | [![npm](https://img.shields.io/npm/v/tagscript?color=crimson&logo=npm&style=flat-square)](https://www.npmjs.com/package/tagscript) 15 | [![codecov](https://codecov.io/gh/imranbarbhuiya/tagscript/branch/main/graph/badge.svg?precision=2&flag=tagscript)](https://codecov.io/gh/imranbarbhuiya/tagscript) 16 | [![npm-tagscript](https://img.shields.io/npm/dw/tagscript)](https://www.npmjs.com/package/tagscript) 17 | [![npm](https://img.shields.io/npm/v/@tagscript/plugin-discord?color=crimson&logo=npm&style=flat-square&label=@tagscript/plugin-discord)](https://www.npmjs.com/package/@tagscript/plugin-discord) 18 | [![codecov-tagscript](https://codecov.io/gh/imranbarbhuiya/tagscript/branch/main/graph/badge.svg?precision=2&flag=plugin-discord)](https://codecov.io/gh/imranbarbhuiya/tagscript) 19 | [![npm-tagscript-plugin-discord](https://img.shields.io/npm/dw/@tagscript/plugin-discord)](https://www.npmjs.com/package/@tagscript/plugin-discord) 20 | 21 |
22 | 23 | ## Description 24 | 25 | TagScript is a drop in easy to use string interpreter that lets you provide users with ways of customizing their profiles or chat rooms with interactive text. 26 | 27 | Read Full Documentation [here](https://tagscript.js.org/). 28 | 29 | ## Features 30 | 31 | - Written In Typescript 32 | - Offers CJS, ESM and UMD builds 33 | - Full TypeScript & JavaScript support 34 | - Blazingly Fast ⚡ 35 | - Simple, expressive and safe template engine. 36 | - Supports many [plugins](https://github.com/imranbarbhuiya/tagscript/packages/). 37 | 38 | ## Usage 39 | 40 | --- 41 | 42 | **Note:** While examples uses `import`, it maps 1:1 with CommonJS' require syntax. For example, 43 | 44 | ```ts 45 | import { Interpreter } from 'tagscript'; 46 | ``` 47 | 48 | is the same as 49 | 50 | ```js 51 | const { Interpreter } = require('tagscript'); 52 | ``` 53 | 54 | --- 55 | 56 | ```ts showLineNumbers 57 | import { Interpreter, RandomParser, RangeParser, FiftyFiftyParser, IfStatementParser, SliceParser } from 'tagscript'; 58 | const ts = new Interpreter(new SliceParser(), new FiftyFiftyParser(), new RandomParser(), new IfStatementParser()); 59 | 60 | const result = await ts.run( 61 | ` 62 | {random: Parbez,Rkn,Priyansh} attempts to pick the lock!, 63 | I pick {if({5050:.}!=):heads|tails} 64 | ` 65 | ); // Parbez attempts to pick the lock!, I pick heads 66 | ``` 67 | 68 | For more usage, check out the documentation [here](https://tagscript.js.org/). 69 | 70 | ## Buy me some doughnuts 71 | 72 | If you want to support me by donating, you can do so by using any of the following methods. Thank you very much in advance! 73 | 74 | Buy Me A Coffee 75 | Buy Me a Coffee at ko-fi.com 76 | 77 | ## Contributors ✨ 78 | 79 | Thanks goes to these wonderful people: 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /apps/website/app/[[...slugs]]/Edit.tsx: -------------------------------------------------------------------------------- 1 | export const Edit = (props: React.SVGProps) => ( 2 | 15 | 16 | 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /apps/website/app/[[...slugs]]/page.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-unstable-nested-components */ 2 | import { Callout } from 'fumadocs-ui/components/callout'; 3 | import { Pre, CodeBlock } from 'fumadocs-ui/components/codeblock'; 4 | import { ImageZoom } from 'fumadocs-ui/components/image-zoom'; 5 | import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; 6 | import defaultComponents from 'fumadocs-ui/mdx'; 7 | import { DocsPage, DocsBody } from 'fumadocs-ui/page'; 8 | import { notFound } from 'next/navigation'; 9 | 10 | import { source } from '@/app/source'; 11 | 12 | import { Edit } from './Edit'; 13 | 14 | import type { MDXComponents, MDXContent } from 'mdx/types'; 15 | import type { Metadata } from 'next'; 16 | import type { ReactNode } from 'react'; 17 | 18 | export default async function Page(props: { readonly params: Promise<{ slugs?: string[] }> }) { 19 | const params = await props.params; 20 | const page = source.getPage(params.slugs); 21 | 22 | if (!page) notFound(); 23 | 24 | const Mdx = page.data.body as MDXContent; 25 | 26 | const path = `apps/website/content/docs/${page.file.path}`; 27 | const footer = path.includes('/api/') ? null : ( 28 | 34 | 35 | Edit on Github 36 | 37 | ); 38 | 39 | return ( 40 | 47 | 48 | ( 52 | 53 |
{rest.children}
54 |
55 | ), 56 | Tab, 57 | Tabs, 58 | InstallTabs: ({ items, children }: { readonly children: ReactNode; readonly items: string[] }) => ( 59 | 60 | {children} 61 | 62 | ), 63 | blockquote: (props) => {props.children}, 64 | img: (props) => 65 | }} 66 | /> 67 |
68 |
69 | ); 70 | } 71 | 72 | export async function generateStaticParams() { 73 | return source.generateParams(); 74 | } 75 | 76 | export async function generateMetadata(props: { params: Promise<{ slugs?: string[] }> }) { 77 | const params = await props.params; 78 | const page = source.getPage(params.slugs); 79 | 80 | if (!page) notFound(); 81 | 82 | const imageParams = new URLSearchParams(); 83 | imageParams.set('title', page.data.title); 84 | imageParams.set('description', page.data.description ?? 'Tagscript is a simple, lightweight, and easy to use templating language.'); 85 | 86 | const image = { 87 | alt: 'Banner', 88 | url: `/api/og/?${imageParams.toString()}`, 89 | width: 1_200, 90 | height: 630 91 | }; 92 | 93 | return { 94 | title: page.data.title, 95 | description: page.data.description, 96 | openGraph: { 97 | url: `/docs/${page.slugs.join('/')}`, 98 | images: image 99 | }, 100 | twitter: { 101 | images: image 102 | } 103 | } satisfies Metadata; 104 | } 105 | -------------------------------------------------------------------------------- /apps/website/app/api/og/Inter-SemiBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranbarbhuiya/TagScript/750c86fe12879c44fc4ec3b2628d921c1b290359/apps/website/app/api/og/Inter-SemiBold.otf -------------------------------------------------------------------------------- /apps/website/app/api/og/route.tsx: -------------------------------------------------------------------------------- 1 | import { ImageResponse } from 'next/og'; 2 | 3 | export const runtime = 'edge'; 4 | 5 | // eslint-disable-next-line unicorn/prefer-top-level-await 6 | const font = fetch(new URL('Inter-SemiBold.otf', import.meta.url)).then((res) => res.arrayBuffer()); 7 | 8 | export async function GET(req: Request) { 9 | const inter = await font; 10 | 11 | const { searchParams } = new URL(req.url); 12 | 13 | const hasTitle = searchParams.has('title'); 14 | const title = hasTitle ? searchParams.get('title')?.slice(0, 100) : 'Tagscript'; 15 | const description = searchParams.get('description') ?? 'Tagscript is a simple, lightweight, and easy to use templating language.'; 16 | 17 | return new ImageResponse( 18 | ( 19 |
37 | Tagscript 38 |

48 | {description} 49 |

50 |

62 | {title} 63 |

64 |
65 | ), 66 | { 67 | width: 1_200, 68 | height: 630, 69 | fonts: [ 70 | { 71 | name: 'inter', 72 | data: inter, 73 | style: 'normal' 74 | } 75 | ] 76 | } 77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /apps/website/app/api/search/route.ts: -------------------------------------------------------------------------------- 1 | import { createSearchAPI } from 'fumadocs-core/search/server'; 2 | 3 | import { source } from '@/app/source'; 4 | 5 | export const { GET } = createSearchAPI('advanced', { 6 | indexes: source.getPages().map((page) => ({ 7 | title: page.data.title, 8 | description: page.data.description, 9 | structuredData: page.data.structuredData, 10 | id: page.url, 11 | url: page.url 12 | })) 13 | }); 14 | -------------------------------------------------------------------------------- /apps/website/app/globals.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | 3 | @plugin 'tailwindcss-animate'; 4 | @import 'fumadocs-ui/css/neutral.css'; 5 | 6 | @custom-variant dark (&:is(.dark *)); 7 | 8 | /* 9 | The default border color has changed to `currentColor` in Tailwind CSS v4, 10 | so we've added these compatibility styles to make sure everything still 11 | looks the same as it did with Tailwind CSS v3. 12 | 13 | If we ever want to remove these styles, we need to add an explicit border 14 | color utility to any element that depends on these defaults. 15 | */ 16 | @layer base { 17 | *, 18 | ::after, 19 | ::before, 20 | ::backdrop, 21 | ::file-selector-button { 22 | border-color: var(--color-gray-200, currentColor); 23 | } 24 | } 25 | 26 | :root { 27 | --primary: 220deg 91% 54% !important; 28 | } 29 | 30 | .dark { 31 | --primary: 217deg 92% 76% !important; 32 | } 33 | -------------------------------------------------------------------------------- /apps/website/app/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return

Tagscript

; 3 | } 4 | -------------------------------------------------------------------------------- /apps/website/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { RootToggle } from 'fumadocs-ui/components/layout/root-toggle'; 2 | import { DocsLayout } from 'fumadocs-ui/layouts/notebook'; 3 | import { GeistMono } from 'geist/font/mono'; 4 | import { GeistSans } from 'geist/font/sans'; 5 | 6 | import { source } from '@/app/source'; 7 | 8 | import { Provider } from './provider'; 9 | 10 | import type { Metadata, Viewport } from 'next'; 11 | import type { ReactNode } from 'react'; 12 | 13 | import 'fumadocs-ui/style.css'; 14 | import './globals.css'; 15 | 16 | export default function RootLayout({ children }: { readonly children: ReactNode }) { 17 | return ( 18 | 19 | 20 | 21 | 48 | ) 49 | }} 50 | tree={source.pageTree} 51 | > 52 | {children} 53 | 54 | 55 | 56 | 57 | ); 58 | } 59 | 60 | export const metadata: Metadata = { 61 | title: 'Tagscript', 62 | description: 'Tagscript is a simple, lightweight, and easy to use templating language.', 63 | metadataBase: process.env.NODE_ENV === 'development' ? new URL('http://localhost:3000') : new URL(`https://${process.env.VERCEL_URL!}`), 64 | openGraph: { 65 | images: ['https://raw.githubusercontent.com/imranbarbhuiya/TagScript/main/.github/logo_short.png'] 66 | }, 67 | twitter: { 68 | images: ['https://raw.githubusercontent.com/imranbarbhuiya/TagScript/main/.github/logo_short.png'] 69 | }, 70 | icons: ['https://raw.githubusercontent.com/imranbarbhuiya/TagScript/main/.github/logo_short.png'] 71 | }; 72 | 73 | export const viewport: Viewport = { 74 | themeColor: [ 75 | { media: '(prefers-color-scheme: dark)', color: '#0A0A0A' }, 76 | { media: '(prefers-color-scheme: light)', color: '#fff' } 77 | ] 78 | }; 79 | -------------------------------------------------------------------------------- /apps/website/app/provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | // eslint-disable-next-line import-x/no-extraneous-dependencies 4 | import { Root } from '@radix-ui/react-dialog'; 5 | import DefaultSearchDialog, { type DefaultSearchDialogProps } from 'fumadocs-ui/components/dialog/search-default'; 6 | import { RootProvider } from 'fumadocs-ui/provider'; 7 | 8 | export function Provider({ children }: { readonly children: React.ReactNode }) { 9 | return ( 10 | 15 | {children} 16 | 17 | ); 18 | } 19 | 20 | function SearchDialog({ api, ...props }: DefaultSearchDialogProps): React.ReactElement { 21 | return ( 22 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /apps/website/app/source.ts: -------------------------------------------------------------------------------- 1 | import { loader } from 'fumadocs-core/source'; 2 | import { createMDXSource } from 'fumadocs-mdx'; 3 | 4 | import { docs, meta } from '@/.source'; 5 | 6 | export const source = loader({ 7 | baseUrl: '/', 8 | // rootDir: 'docs', 9 | source: createMDXSource(docs, meta) 10 | }); 11 | -------------------------------------------------------------------------------- /apps/website/content/docs/api/@tagscript/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Plugins" 3 | } 4 | -------------------------------------------------------------------------------- /apps/website/content/docs/api/@tagscript/plugin-discord/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Discord Plugin" 3 | } 4 | -------------------------------------------------------------------------------- /apps/website/content/docs/api/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: TagScript API Docs 3 | description: Documentation for TagScript API 4 | --- 5 | 6 | import Image from 'next/image'; 7 | 8 | import Logo from '../../../../../.github/logo_short.png'; 9 | 10 |
11 | 12 | TagScript Logo 13 | 14 | # TagScript 15 | 16 | **A simple and safe template engine.** 17 | 18 |
19 | 20 | ## Description 21 | 22 | TagScript is a drop in easy to use string interpreter that lets you provide users with ways of customizing their profiles or chat rooms with interactive text. 23 | 24 | ## Api Documentation 25 | 26 | - [TagScript](/typedoc-api/tagscript) 27 | 28 | ### Plugins 29 | 30 | - [Discord Plugin](/typedoc-api/plugins/plugin-discord) 31 | -------------------------------------------------------------------------------- /apps/website/content/docs/api/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages":["tagscript", "plugins"] 3 | } -------------------------------------------------------------------------------- /apps/website/content/docs/api/plugins/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Discord Plugin" 3 | } 4 | -------------------------------------------------------------------------------- /apps/website/content/docs/api/tagscript/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Getting Started" 3 | } 4 | -------------------------------------------------------------------------------- /apps/website/content/docs/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages":["---Quick Start---", "tagscript", "---Plugins---", "plugins", "---API Docs---", "api", "---Links---", "[Github](https://github.com/imranbarbhuiya/tagscript)", "[Author Github](https://github.com/imranbarbhuiya)", "[X (Twitter)](https://x.com/notparbez)"] 3 | } -------------------------------------------------------------------------------- /apps/website/content/docs/plugins/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: TagScript Plugins 3 | description: Plugins for TagScript 4 | --- 5 | 6 | import Image from 'next/image'; 7 | 8 | import Logo from '../../../../../.github/logo_short.png'; 9 | 10 |
11 | 12 | TagScript Logo 13 | 14 | # TagScript Plugins 15 | 16 |
17 | 18 | Tagscript supports plugins to better customize your experience. You can create your own plugins or use the ones that are already available. 19 | 20 | ## Built-in Plugins 21 | 22 | - [Plugin Discord](/plugins/plugin-discord) - Provides Discord.js related parsers and transformers. 23 | 24 | ## Creating Plugins 25 | 26 | A guide will be added in future. For now you can check source code of [plugin-discord](https://github.com/imranbarbhuiya/TagScript/tree/main/packages/tagscript-plugin-discord) for an example. 27 | -------------------------------------------------------------------------------- /apps/website/content/docs/plugins/plugin-discord/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Plugin Discord 3 | description: Tagscript plugin to work with discord.js 4 | --- 5 | 6 |
7 | 8 | # @tagscript/plugin-discord 9 | 10 | **A tagscript plugin to work with discord.js** 11 | 12 |
13 | 14 | ## Description 15 | 16 | A Plugin for [TagScript](https://www.npmjs.com/package/tagscript) to work with discord.js related structures. 17 | 18 | ## Features 19 | 20 | - Written In Typescript 21 | - Offers CJS, and ESM builds 22 | - Full TypeScript & JavaScript support 23 | 24 | ## Installation 25 | 26 | `@tagscript/plugin-discord` depends on the following packages. Be sure to install these along with this package! 27 | 28 | - [tagscript](https://www.npmjs.com/package/tagscript) 29 | - [discord.js](https://www.npmjs.com/package/discord.js) 30 | 31 | You can use the following command to install this package, or replace npm install with your package manager of choice. 32 | 33 | ```package-install 34 | @tagscript/plugin-discord 35 | ``` 36 | 37 | ## Usage 38 | 39 | ```ts showLineNumbers 40 | import { Interpreter, StrictVarsParser } from 'tagscript'; 41 | import { MemberTransformer } from '@tagscript/plugin-discord'; 42 | 43 | const ts = new Interpreter(new StrictVarsParser()); 44 | 45 | await ts.run('Hi {member.username}', { member: new MemberTransformer(GuildMember) }); 46 | // Hi P 47 | ``` 48 | 49 | ## Buy me some doughnuts 50 | 51 | If you want to support me by donating, you can do so by using any of the following methods. Thank you very much in advance! 52 | 53 | 67 | -------------------------------------------------------------------------------- /apps/website/content/docs/plugins/plugin-discord/meta.json: -------------------------------------------------------------------------------- 1 | { "title": "Discord Plugin" } 2 | -------------------------------------------------------------------------------- /apps/website/content/docs/plugins/plugin-discord/parsers/embed.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: EmbedParser 3 | --- 4 | 5 | # EmbedParser 6 | -------------------------------------------------------------------------------- /apps/website/content/docs/plugins/plugin-discord/parsers/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: TagScript Parsers 3 | description: Tagscript parsers are used to parse a tag and return a value based on the tag. 4 | --- 5 | 6 | # Parsers 7 | 8 | Discord Plugin provides the following parsers which you can use to parse tags. 9 | 10 | - [EmbedParser](/plugins/plugin-discord/parsers) 11 | -------------------------------------------------------------------------------- /apps/website/content/docs/tagscript/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: TagScript 3 | description: Tagscript is a simple, lightweight, and easy to use templating language. 4 | --- 5 | 6 | import { Tabs } from 'fumadocs-ui/components/tabs'; 7 | import Image from 'next/image'; 8 | 9 | import Logo from '../../../../../.github/logo_short.png'; 10 | 11 |
12 | 13 | TagScript Logo 14 | 15 | # TagScript 16 | 17 | **A simple and safe template engine.** 18 | 19 |
20 | 21 | ## Description 22 | 23 | TagScript is a drop in easy to use string interpreter that lets you provide users with ways of customizing their profiles or chat rooms with interactive text. 24 | 25 | Read Full Documentation [here](https://tagscript.js.org/). 26 | 27 | --- 28 | 29 | ## Features 30 | 31 | - Written In Typescript 32 | - Offers CJS, ESM and UMD builds 33 | - Full TypeScript & JavaScript support 34 | - Faster than ⚡ 35 | - Simple, expressive and safe template engine. 36 | - Supports your own parsers and transformers 37 | 38 | ## Installation 39 | 40 | ```package-install 41 | tagscript 42 | ``` 43 | 44 | ## Usage 45 | 46 | 47 | ```ts showLineNumbers tab="ESM" 48 | import { Interpreter, Interpreter, RandomParser, RangeParser, FiftyFiftyParser, IfStatementParser, SliceParser } from 'tagscript'; 49 | 50 | const ts = new Interpreter(new SliceParser(), new FiftyFiftyParser(), new RandomParser(), new IfStatementParser()); 51 | 52 | const result = await ts.run(`{random: Parbez,Rkn,Priyansh} attempts to pick the lock!, I pick {if({5050:.}!=):heads|tails}`); 53 | // Parbez attempts to pick the lock!, I pick heads 54 | ``` 55 | ```js showLineNumbers tab="CommonJs" 56 | const { Interpreter, RandomParser, RangeParser, FiftyFiftyParser, IfStatementParser, SliceParser } = require('tagscript'); 57 | 58 | const ts = new Interpreter(new SliceParser(), new FiftyFiftyParser(), new RandomParser(), new IfStatementParser()); 59 | 60 | const result = await ts.run(`{random: Parbez,Rkn,Priyansh} attempts to pick the lock!, I pick {if({5050:.}!=):heads|tails}`); 61 | // Parbez attempts to pick the lock!, I pick heads 62 | ``` 63 | 64 | 65 | 66 | ## Buy me some doughnuts 67 | 68 | If you want to support me by donating, you can do so by using any of the following methods. Thank you very much in advance! 69 | 70 | 84 | 85 | ## Special Thanks 86 | 87 | - [JonSnowbd](https://github.com/JonSnowbd/) for creating TagScript in python. 88 | -------------------------------------------------------------------------------- /apps/website/content/docs/tagscript/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Getting Started", 3 | "defaultOpen": true 4 | } 5 | -------------------------------------------------------------------------------- /apps/website/content/docs/tagscript/parsers/break.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: BreakParser 3 | --- 4 | 5 | # BreakParser 6 | 7 | import { Callout } from 'fumadocs-ui/components/callout'; 8 | import { Tabs } from 'fumadocs-ui/components/tabs'; 9 | 10 | The break tag will force the tag output to only be the payload of this tag, if the passed expression evaluates true. 11 | If no message is provided to the payload, the tag output will be empty. 12 | This differs from the [StopParser](/tagscript/parser/stop) as the stop tag stops all TagScript parsing and returns its message while the break tag continues to parse tags. 13 | If any other tags exist after the break tag, they will still execute. 14 | 15 | ## Usage 16 | 17 | 18 | ```ts showLineNumbers tab="ESM" 19 | import { Interpreter, BreakParser } from 'tagscript'; 20 | const ts = new Interpreter(new BreakParser()); 21 | ``` 22 | ```js showLineNumbers tab="CommonJs" 23 | const { Interpreter, BreakParser } = require('tagscript'); 24 | const ts = new Interpreter(new BreakParser()); 25 | ``` 26 | 27 | 28 | ## API 29 | 30 | Check [BreakParser](../../typedoc-api/tagscript/classes/BreakParser) for the API documentation. 31 | 32 | ## For End Users 33 | 34 | 35 | ### Syntax 36 | ```yaml 37 | {break(expression):message} 38 | ``` 39 | 40 | ### Examples 41 | 42 | ```yaml 43 | {break({args}==):You did not provide any input.} 44 | ``` 45 | 46 | 47 | -------------------------------------------------------------------------------- /apps/website/content/docs/tagscript/parsers/if-statement.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: IfStatementParser 3 | --- 4 | 5 | # IfStatementParser 6 | 7 | import { Callout } from 'fumadocs-ui/components/callout'; 8 | import { Tabs } from 'fumadocs-ui/components/tabs'; 9 | 10 | The if tag returns a message based on the passed expression to the parameter. 11 | An expression is represented by two values compared with an operator. 12 | The payload is a required message that must be split by pipe (`|`). 13 | If the expression evaluates true, then the message before the pipe (`|`) is returned, else the message after is returned. 14 | 15 | ## Usage 16 | 17 | 18 | ```ts showLineNumbers tab="ESM" 19 | import { Interpreter, IfStatementParser } from 'tagscript'; 20 | const ts = new Interpreter(new IfStatementParser()); 21 | ``` 22 | ```js showLineNumbers tab="CommonJs" 23 | const { Interpreter, IfStatementParser } = require('tagscript'); 24 | const ts = new Interpreter(new IfStatementParser()); 25 | ``` 26 | 27 | 28 | ## API 29 | 30 | Check [IfStatementParser](../../typedoc-api/tagscript/classes/IfStatementParser) for the API documentation. 31 | 32 | ## For End Users 33 | 34 | 35 | 36 | ### Syntax 37 | {/* prettier-ignore-start */} 38 | ```yaml 39 | {if(expression):message} 40 | ``` 41 | {/* prettier-ignore-end */} 42 | 43 | ### Examples 44 | 45 | ```yaml 46 | {if({args}==63):You guessed it! The number I was thinking of was 63!|Too {if({args}<63):low|high}, try again.} 47 | # if args is 63 48 | You guessed it! The number I was thinking of was 63! 49 | # if args is 73 50 | Too high, try again. 51 | # if args is 14 52 | Too low, try again. 53 | ``` 54 | 55 | ### Operators 56 | 57 | { 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
OperatorCheckExampleDescription
==equalitya==avalue 1 is equal to value 2
!=inequalitya!=bvalue 1 is not equal to value 2
>greater than5>3value 1 is greater than value 2
<less than4<8value 1 is less than value 2
>=greater than or equality10>=10value 1 is greater than or equal to value 2
<=less than or equality5<=6value 1 is less than or equal to value 2
72 | } 73 | 74 |
75 | -------------------------------------------------------------------------------- /apps/website/content/docs/tagscript/parsers/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: TagScript Parsers 3 | description: Tagscript parsers are used to parse a tag and return a value based on the tag. 4 | --- 5 | 6 | # Parsers 7 | 8 | import { Tabs } from 'fumadocs-ui/components/tabs'; 9 | 10 | Parsers are used to parse a tag and return a value based on the tag. You can use [builtin parsers](#builtin-parsers) or write your [own parser](#custom-parser). 11 | 12 | 13 | ```ts showLineNumbers tab="ESM" 14 | // [!code word:Parser] 15 | import { Interpreter, RandomParser, RangeParser, FiftyFiftyParser, IfStatementParser, SliceParser } from 'tagscript'; 16 | const ts = new Interpreter(new 17 | SliceParser(), new FiftyFiftyParser(), new RandomParser(), new IfStatementParser()); 18 | ``` 19 | ```js showLineNumbers tab="CommonJs" 20 | // [!code word:Parser] 21 | const { Interpreter, RandomParser, RangeParser, FiftyFiftyParser, IfStatementParser, SliceParser } = require('tagscript'); 22 | const ts = new Interpreter(new SliceParser(), new FiftyFiftyParser(), new RandomParser(), new IfStatementParser()); 23 | ``` 24 | 25 | 26 | ## Builtin Parsers 27 | 28 | Following is the list of builtin parsers: 29 | 30 | - [BreakParser](/tagscript/parsers/break) 31 | - [IfStatementParser](/tagscript/parsers/if-statement) 32 | - [UnionStatementParser](/tagscript/parsers/union-statement) 33 | - [IntersectionStatementParser](/tagscript/parsers/intersection-statement) 34 | 35 | ## Custom Parser 36 | 37 | You can write your own parsers by implementing [IParser](/typedoc-api/tagscript/interfaces/IParser) interface. 38 | 39 | 40 | ```ts showLineNumbers tab="Typescript" 41 | // [!code word:Parser] 42 | import { BaseParser, type Context, type IParser } from 'tagscript'; 43 | 44 | export class FetchParser extends BaseParser implements IParser { 45 | public constructor() { 46 | super(['fetch'], false, true); 47 | } 48 | 49 | public parse(ctx: Context) { 50 | return fetch(ctx.tag.payload!.trim()).then((res) => res.text()); 51 | } 52 | } 53 | ``` 54 | ```js showLineNumbers tab="CommonJs" 55 | // [!code word:Parser] 56 | const { BaseParser } = require('tagscript'); 57 | 58 | module.exports = class FetchParser extends BaseParser { 59 | constructor() { 60 | super(['fetch'], false, true); 61 | } 62 | 63 | parse(ctx) { 64 | return fetch(ctx.tag.payload.trim()).then((res) => res.text()); 65 | } 66 | } 67 | ``` 68 | 69 | 70 | -------------------------------------------------------------------------------- /apps/website/content/docs/tagscript/parsers/intersection-statement.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: IntersectionStatementParser 3 | --- 4 | 5 | # IntersectionStatementParser 6 | 7 | import { Callout } from 'fumadocs-ui/components/callout'; 8 | import { Tabs } from 'fumadocs-ui/components/tabs'; 9 | 10 | The intersection parser checks that all of the passed expressions are true. 11 | Multiple expressions can be passed to the parameter by splitting them with pipe (`|`). 12 | The payload is a required message that must be split by pipe (`|`). 13 | If the expression evaluates true, then the message before the pipe (`|`) is returned, else the message after is returned. 14 | 15 | ## Usage 16 | 17 | 18 | ```ts showLineNumbers tab="ESM" 19 | import { Interpreter, IntersectionStatementParser } from 'tagscript'; 20 | const ts = new Interpreter(new IntersectionStatementParser()); 21 | ``` 22 | ```js showLineNumbers tab="CommonJs" 23 | const { Interpreter, IntersectionStatementParser } = require('tagscript'); 24 | const ts = new Interpreter(new IntersectionStatementParser()); 25 | ``` 26 | 27 | 28 | ## API 29 | 30 | Check [IntersectionStatementParser](../../typedoc-api/tagscript/classes/IntersectionStatementParser) for the API documentation. 31 | 32 | ## For End Users 33 | 34 | 35 | ### Syntax 36 | {/* prettier-ignore-start */} 37 | ```yaml 38 | {all(expression|expression|...):message} 39 | ``` 40 | {/* prettier-ignore-end */} 41 | 42 | ### Examples 43 | 44 | ```yaml 45 | {all({args}>=100|{args}<=1000):You picked {args}.|You must provide a number between 100 and 1000.} 46 | # if {args} is 52 47 | You must provide a number between 100 and 1000. 48 | # if {args} is 282 49 | You picked 282. 50 | ``` 51 | 52 | ### Aliases 53 | 54 | `intersection`, `all`, `and` 55 | 56 | 57 | -------------------------------------------------------------------------------- /apps/website/content/docs/tagscript/parsers/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "break": "Break Parser", 3 | "if-statement": "If Statement Parser", 4 | "intersection-statement": "Intersection Statement Parser", 5 | "union-statement": "Union Statement Parser" 6 | } 7 | -------------------------------------------------------------------------------- /apps/website/content/docs/tagscript/parsers/union-statement.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: UnionStatementParser 3 | --- 4 | 5 | # UnionStatementParser 6 | 7 | import { Callout } from 'fumadocs-ui/components/callout'; 8 | import { Tabs } from 'fumadocs-ui/components/tabs'; 9 | 10 | The union parser checks that any of the passed expressions are true. 11 | Multiple expressions can be passed to the parameter by splitting them with pipe (`|`). 12 | The payload is a required message that must be split by pipe (`|`). 13 | If the expression evaluates true, then the message before the pipe (`|`) is returned, else the message after is returned. 14 | 15 | ## Usage 16 | 17 | 18 | ```ts showLineNumbers tab="ESM" 19 | import { Interpreter, UnionStatementParser } from 'tagscript'; 20 | const ts = new Interpreter(new UnionStatementParser()); 21 | ``` 22 | ```js showLineNumbers tab="CommonJs" 23 | const { Interpreter, UnionStatementParser } = require('tagscript'); 24 | const ts = new Interpreter(new UnionStatementParser()); 25 | ``` 26 | 27 | 28 | ## API 29 | 30 | Check [UnionStatementParser](../../typedoc-api/tagscript/classes/UnionStatementParser) for the API documentation. 31 | 32 | ## For End Users 33 | 34 | 35 | ### Syntax 36 | {/* prettier-ignore-start */} 37 | ```yaml 38 | {any(expression|expression|...):message} 39 | ``` 40 | {/* prettier-ignore-end */} 41 | 42 | ### Examples 43 | 44 | ```yaml 45 | {any({args}==hi|{args}==hello|{args}==hey):Hello {user}!|How rude.} 46 | # if {args} is hi 47 | Hello Mr. Priyansh#2063! 48 | # if {args} is what's up 49 | How rude. 50 | ``` 51 | 52 | ### Aliases 53 | 54 | `union`, `any`, `or` 55 | 56 | 57 | -------------------------------------------------------------------------------- /apps/website/content/docs/tagscript/transformers/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: TagScript Transformers 3 | description: Tagscript transformers are used to transform a value based on the tag at runtime. 4 | --- 5 | 6 | # Transformers 7 | 8 | import { Tabs } from 'fumadocs-ui/components/tabs'; 9 | 10 | Transformers are used to transform a value based on the tag at runtime. You can use [builtin transformers](#builtin-transformers) or write your [own transformer](#custom-transformer). 11 | 12 | 13 | ```ts showLineNumbers tab="ESM" 14 | // [!code word:Transformer] 15 | import { Interpreter, StringTransformer, StrictVarsParser } from 'tagscript'; 16 | const ts = new Interpreter(new StrictVarsParser()); 17 | 18 | await ts.run('Hi {user}', { user: new StringTransformer(args) }); 19 | // Hi Parbez 20 | ``` 21 | ```js showLineNumbers tab="CommonJs" 22 | // [!code word:Transformer] 23 | const { Interpreter, StringTransformer, StrictVarsParser } = require('tagscript'); 24 | const ts = new Interpreter(new StrictVarsParser()); 25 | 26 | await ts.run('Hi {user}', { user: new StringTransformer(args) }); 27 | // Hi Parbez 28 | ``` 29 | 30 | 31 | ## Builtin Transformers 32 | 33 | Following is the list of builtin transformers: 34 | 35 | {/* - [BreakParser](/tagscript/parsers/break) */} 36 | 37 | ## Custom Transformer 38 | 39 | You can write your own transformer by implementing [ITransformer](/typedoc-api/tagscript/interfaces/ITransformer) interface. 40 | 41 | 42 | ```ts showLineNumbers /ITransformer/ tab="Typescript" 43 | import type { ITransformer, Lexer } from 'tagscript'; 44 | 45 | /** 46 | * Integer transformer transforms an integer based on the given parameters. 47 | * 48 | * If no parameters are given, the integer will be returned as is. 49 | * 50 | * if `++` parameter is given, the integer will be incremented. 51 | * if `--` parameter is given, the integer will be decremented. 52 | */ 53 | export class IntegerTransformer implements ITransformer { 54 | private integer: number; 55 | 56 | public constructor(int: `${bigint | number}`) { 57 | this.integer = Number.parseInt(int, 10); 58 | } 59 | 60 | public transform(tag: Lexer) { 61 | if (tag.parameter === '++') { 62 | return `${++this.integer}`; 63 | } 64 | 65 | if (tag.parameter === '--') { 66 | return `${--this.integer}`; 67 | } 68 | 69 | return `${this.integer}`; 70 | } 71 | } 72 | ``` 73 | ```js showLineNumbers tab="CommonJs" 74 | /** 75 | * Integer transformer transforms an integer based on the given parameters. 76 | * 77 | * If no parameters are given, the integer will be returned as is. 78 | * 79 | * if `++` parameter is given, the integer will be incremented. 80 | * if `--` parameter is given, the integer will be decremented. 81 | */ 82 | module.exports = class IntegerTransformer { 83 | constructor(int) { 84 | this.integer = Number.parseInt(int, 10); 85 | } 86 | 87 | transform(tag) { 88 | if (tag.parameter === '++') { 89 | return `${++this.integer}`; 90 | } 91 | 92 | if (tag.parameter === '--') { 93 | return `${--this.integer}`; 94 | } 95 | 96 | return `${this.integer}`; 97 | } 98 | } 99 | ``` 100 | 101 | -------------------------------------------------------------------------------- /apps/website/content/docs/tagscript/transformers/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": "String Transformer" 3 | } 4 | -------------------------------------------------------------------------------- /apps/website/content/docs/tagscript/transformers/string.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: StringTransformer 3 | --- 4 | 5 | # StringTransformer 6 | 7 | import { Callout } from 'fumadocs-ui/components/callout'; 8 | 9 | String transformer transforms a string based on the given parameters. 10 | 11 | If no parameters are given, the string will be returned as is. 12 | If an integer parameter is given, the string will be splitted into an array of strings using payload or space as a separator. 13 | And will return the element at the given index (integer parameter). 14 | 15 | Use a `+` before the index to reference every element up to and including the index value. 16 | 17 | Use a `+` after the index to reference the index value and every element after it. 18 | a 19 | 20 | You need to use `StrictVarsParser` parser to use this transformer. 21 | -------------------------------------------------------------------------------- /apps/website/eslint.config.js: -------------------------------------------------------------------------------- 1 | import common from 'eslint-config-mahir/common'; 2 | import edge from 'eslint-config-mahir/edge'; 3 | import mdx from 'eslint-config-mahir/mdx'; 4 | import module from 'eslint-config-mahir/module'; 5 | import next from 'eslint-config-mahir/next'; 6 | import node from 'eslint-config-mahir/node'; 7 | import react from 'eslint-config-mahir/react'; 8 | import typescript from 'eslint-config-mahir/typescript'; 9 | 10 | /** 11 | * @type {import('@typescript-eslint/utils').TSESLint.FlatConfig.ConfigArray} 12 | */ 13 | export default [ 14 | ...common, 15 | ...node, 16 | ...module, 17 | ...react, 18 | ...next, 19 | ...edge, 20 | ...mdx.map((config) => ({ 21 | files: ['**/*.mdx'], 22 | ...config 23 | })), 24 | ...typescript.map((config) => ({ 25 | files: ['**/*.tsx', '**/*.ts', '**/*.cjs', '**/*.jsx', '**/*.js'], 26 | ...config 27 | })), 28 | { 29 | ignores: ['next-env.d.ts', '**/*.md', '.source', '.next'] 30 | } 31 | ]; 32 | -------------------------------------------------------------------------------- /apps/website/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranbarbhuiya/TagScript/750c86fe12879c44fc4ec3b2628d921c1b290359/apps/website/images/logo.png -------------------------------------------------------------------------------- /apps/website/images/logo_short.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranbarbhuiya/TagScript/750c86fe12879c44fc4ec3b2628d921c1b290359/apps/website/images/logo_short.png -------------------------------------------------------------------------------- /apps/website/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/website/next.config.js: -------------------------------------------------------------------------------- 1 | import { createMDX } from 'fumadocs-mdx/next'; 2 | 3 | const withMDX = createMDX({}); 4 | 5 | /** @type {import('next').NextConfig} */ 6 | const config = {}; 7 | 8 | export default withMDX(config); 9 | -------------------------------------------------------------------------------- /apps/website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tagscript/website", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "yarn run --top-level docs && next build", 8 | "start": "next start", 9 | "lint": "TIMING=1 eslint . --fix", 10 | "format": "prettier --cache --write .", 11 | "postinstall": "fumadocs-mdx" 12 | }, 13 | "dependencies": { 14 | "@types/mdx": "^2.0.13", 15 | "fumadocs-core": "^15.4.2", 16 | "fumadocs-docgen": "^2.0.0", 17 | "fumadocs-mdx": "^11.6.6", 18 | "fumadocs-ui": "^15.4.2", 19 | "geist": "^1.4.2", 20 | "next": "15.3.3", 21 | "react": "19.1.0", 22 | "react-dom": "19.1.0", 23 | "sharp": "^0.34.2" 24 | }, 25 | "devDependencies": { 26 | "@fumadocs/cli": "^0.2.0", 27 | "@tagscript/plugin-discord": "workspace:^", 28 | "@tailwindcss/postcss": "^4.1.8", 29 | "@types/react": "19.1.6", 30 | "@types/react-dom": "19.1.5", 31 | "eslint-config-mahir": "^1.0.7", 32 | "tagscript": "workspace:^", 33 | "tailwindcss": "^4.1.8", 34 | "tailwindcss-animate": "^1.0.7" 35 | }, 36 | "nextBundleAnalysis": {} 37 | } 38 | -------------------------------------------------------------------------------- /apps/website/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | '@tailwindcss/postcss': {} 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /apps/website/source.config.ts: -------------------------------------------------------------------------------- 1 | import { remarkInstall } from 'fumadocs-docgen'; 2 | import { defineConfig, defineDocs } from 'fumadocs-mdx/config'; 3 | 4 | export const { docs, meta } = defineDocs({ dir: 'content/docs' }); 5 | 6 | export default defineConfig({ 7 | lastModifiedTime: 'git', 8 | mdxOptions: { 9 | rehypeCodeOptions: { themes: { light: 'catppuccin-latte', dark: 'catppuccin-mocha' } }, 10 | remarkPlugins: [[remarkInstall, { Tabs: 'InstallTabs' }]] 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /apps/website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "noEmit": true, 9 | "incremental": true, 10 | "esModuleInterop": true, 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "moduleResolution": "bundler", 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx", "**/*.jsx", "**/*.js", "**/*.cjs"] 25 | } 26 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import common from 'eslint-config-mahir/common'; 2 | import module from 'eslint-config-mahir/module'; 3 | import node from 'eslint-config-mahir/node'; 4 | import tsdoc from 'eslint-config-mahir/tsdoc'; 5 | import typescript from 'eslint-config-mahir/typescript'; 6 | 7 | /** 8 | * @type {import('@typescript-eslint/utils').TSESLint.FlatConfig.ConfigArray} 9 | */ 10 | export default [ 11 | ...common, 12 | ...node, 13 | ...module, 14 | ...typescript, 15 | ...tsdoc, 16 | { 17 | settings: { 18 | next: { 19 | rootDir: ['apps/*/'] 20 | } 21 | }, 22 | ignores: ['node_modules/', '**/dist/', '**/docs/', '**/build/', '**/*.d.ts'], 23 | languageOptions: { 24 | parserOptions: { 25 | projectService: false, 26 | tsconfigRootDir: import.meta.dirname, 27 | project: ['./tsconfig.json', './apps/*/tsconfig.json', './packages/*/tsconfig.typecheck.json'] 28 | } 29 | } 30 | } 31 | ]; 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root-tagscript", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/*", 6 | "apps/*" 7 | ], 8 | "scripts": { 9 | "build": "turbo run build", 10 | "bump": "turbo run bump --concurrency=1", 11 | "check-update": "turbo run check-update", 12 | "clean": "node scripts/clean.mjs", 13 | "clean:full": "node scripts/clean-full.mjs", 14 | "docs": "node scripts/generateDocs.mjs", 15 | "format": "prettier --cache --write .", 16 | "lint": "turbo run lint", 17 | "update": "yarn upgrade-interactive", 18 | "postinstall": "husky install .github/husky", 19 | "test": "turbo run test", 20 | "test:watch": "turbo run test:watch", 21 | "typecheck": "turbo run typecheck" 22 | }, 23 | "devDependencies": { 24 | "@commitlint/cli": "^19.8.1", 25 | "@commitlint/config-conventional": "^19.8.1", 26 | "@favware/cliff-jumper": "^6.0.0", 27 | "@favware/colorette-spinner": "^1.0.1", 28 | "@favware/npm-deprecate": "^2.0.0", 29 | "@types/node": "^22.15.29", 30 | "@types/prompts": "^2.4.9", 31 | "@vitest/coverage-v8": "^3.1.4", 32 | "cz-conventional-changelog": "^3.3.0", 33 | "eslint": "^9.28.0", 34 | "eslint-config-mahir": "^1.0.7", 35 | "husky": "^9.1.7", 36 | "lint-staged": "^16.1.0", 37 | "prettier": "^3.5.3", 38 | "prompts": "^2.4.2", 39 | "tsup": "^8.5.0", 40 | "turbo": "^2.5.4", 41 | "typedoc": "^0.28.5", 42 | "typedoc-plugin-external-link": "^3.0.2", 43 | "typedoc-plugin-markdown": "4.6.3", 44 | "typedoc-plugin-mdn-links": "^5.0.2", 45 | "typescript": "^5.8.3", 46 | "vitest": "^3.1.4" 47 | }, 48 | "repository": { 49 | "type": "git", 50 | "url": "git+https://github.com/imranbarbhuiya/tagscript.git" 51 | }, 52 | "engines": { 53 | "node": ">=v14.0.0", 54 | "npm": ">=7.0.0" 55 | }, 56 | "commitlint": { 57 | "extends": [ 58 | "@commitlint/config-conventional" 59 | ] 60 | }, 61 | "lint-staged": { 62 | "*.{mjs,js,ts}": "eslint --fix --ext mjs,js,ts" 63 | }, 64 | "config": { 65 | "commitizen": { 66 | "path": "./node_modules/cz-conventional-changelog" 67 | } 68 | }, 69 | "packageManager": "yarn@4.9.1", 70 | "nextBundleAnalysis": {} 71 | } 72 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/.cliff-jumperrc.yml: -------------------------------------------------------------------------------- 1 | name: plugin-discord 2 | org: tagscript 3 | monoRepo: true 4 | packagePath: packages/tagscript-plugin-discord 5 | tagTemplate: '{{full-name}}@{{new-version}}' 6 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Parbez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # @tagscript/plugin-discord 4 | 5 | **A tagscript plugin to work with discord.js** 6 | 7 |
8 | 9 | ## Description 10 | 11 | A Plugin for [TagScript](https://www.npmjs.com/package/tagscript) to work with discord.js related structures. 12 | 13 | ## Features 14 | 15 | - Written In Typescript 16 | - Offers CJS, and ESM builds 17 | - Full TypeScript & JavaScript support 18 | 19 | ## Installation 20 | 21 | `@tagscript/plugin-discord` depends on the following packages. Be sure to install these along with this package! 22 | 23 | - [tagscript](https://www.npmjs.com/package/tagscript) 24 | - [discord.js](https://www.npmjs.com/package/discord.js) 25 | 26 | You can use the following command to install this package, or replace npm install with your package manager of choice. 27 | 28 | ```bash 29 | npm install @tagscript/plugin-discord tagscript discord.js 30 | 31 | ``` 32 | 33 | ## Usage 34 | 35 | ```ts showLineNumbers 36 | import { Interpreter, StrictVarsParser } from 'tagscript'; 37 | import { MemberTransformer } from '@tagscript/plugin-discord'; 38 | 39 | const ts = new Interpreter(new StrictVarsParser()); 40 | 41 | await ts.run('Hi {member.username}', { member: new MemberTransformer(GuildMember) }); 42 | // Hi P 43 | ``` 44 | 45 | ## Buy me some doughnuts 46 | 47 | If you want to support me by donating, you can do so by using any of the following methods. Thank you very much in advance! 48 | 49 | Buy Me A Coffee 50 | Buy Me a Coffee at ko-fi.com 51 | 52 | ## Contributors ✨ 53 | 54 | Thanks goes to these wonderful people: 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/cliff.toml: -------------------------------------------------------------------------------- 1 | [changelog] 2 | header = """ 3 | # Changelog 4 | 5 | All notable changes to this project will be documented in this file.\n 6 | """ 7 | body = """ 8 | {% if version %}\ 9 | # [{{ version | trim_start_matches(pat="v") }}]\ 10 | {% if previous %}\ 11 | {% if previous.version %}\ 12 | (https://github.com/imranbarbhuiya/tagscript/compare/{{ previous.version }}...{{ version }})\ 13 | {% else %}\ 14 | (https://github.com/imranbarbhuiya/tagscript/tree/{{ version }})\ 15 | {% endif %}\ 16 | {% endif %} \ 17 | - ({{ timestamp | date(format="%Y-%m-%d") }}) 18 | {% else %}\ 19 | # [unreleased] 20 | {% endif %}\ 21 | {% for group, commits in commits | group_by(attribute="group") %} 22 | ## {{ group | upper_first }} 23 | {% for commit in commits %} 24 | - {% if commit.scope %}\ 25 | **{{commit.scope}}:** \ 26 | {% endif %}\ 27 | {{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/imranbarbhuiya/tagscript/commit/{{ commit.id }}))\ 28 | {% if commit.breaking %}\ 29 | {% for breakingChange in commit.footers %}\ 30 | \n{% raw %} {% endraw %}- 💥 **{{ breakingChange.token }}{{ breakingChange.separator }}** {{ breakingChange.value }}\ 31 | {% endfor %}\ 32 | {% endif %}\ 33 | {% endfor %} 34 | {% endfor %}\n 35 | """ 36 | trim = true 37 | footer = "" 38 | 39 | [git] 40 | conventional_commits = true 41 | filter_unconventional = true 42 | commit_parsers = [ 43 | { message = "^feat", group = "🚀 Features" }, 44 | { message = "^fix", group = "🐛 Bug Fixes" }, 45 | { message = "^docs", group = "📝 Documentation" }, 46 | { message = "^perf", group = "🏃 Performance" }, 47 | { message = "^refactor", group = "🏠 Refactor" }, 48 | { message = "^typings", group = "⌨️ Typings" }, 49 | { message = "^types", group = "⌨️ Typings" }, 50 | { message = ".*deprecated", body = ".*deprecated", group = "🚨 Deprecation" }, 51 | { message = "^revert", skip = true }, 52 | { message = "^style", group = "🪞 Styling" }, 53 | { message = "^test", group = "🧪 Testing" }, 54 | { message = "^chore", skip = true }, 55 | { message = "^ci", skip = true }, 56 | { message = "^build", skip = true }, 57 | { body = ".*security", group = "🛡️ Security" }, 58 | ] 59 | filter_commits = true 60 | tag_pattern = "@tagscript/plugin-discord@[0-9]*" 61 | ignore_tags = "" 62 | topo_order = false 63 | sort_commits = "newest" 64 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tagscript/plugin-discord", 3 | "version": "3.1.0", 4 | "description": "A plugin for tagscript to work with discord.js.", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "publishConfig": { 9 | "access": "public" 10 | }, 11 | "exports": { 12 | "types": "./dist/index.d.ts", 13 | "import": "./dist/index.mjs", 14 | "require": "./dist/index.js" 15 | }, 16 | "sideEffects": false, 17 | "author": "@imranbarbhuiya", 18 | "license": "MIT", 19 | "scripts": { 20 | "lint": "TIMING=1 eslint src --fix --cache -c ../../eslint.config.mjs", 21 | "build": "tsup", 22 | "prepack": "yarn build", 23 | "bump": "cliff-jumper", 24 | "check-update": "cliff-jumper --dry-run", 25 | "test": "vitest run", 26 | "typecheck": "tsc -p tsconfig.typecheck.json --noEmit" 27 | }, 28 | "keywords": [ 29 | "tagscript", 30 | "string parser", 31 | "safe string", 32 | "typescript", 33 | "template engine", 34 | "template", 35 | "template string", 36 | "tagscript discord", 37 | "discord.js", 38 | "tag", 39 | "bot tag" 40 | ], 41 | "devDependencies": { 42 | "@favware/cliff-jumper": "^6.0.0", 43 | "discord.js": "^14.19.3", 44 | "tagscript": "workspace:^", 45 | "tsup": "^8.5.0", 46 | "typescript": "^5.8.3" 47 | }, 48 | "peerDependencies": { 49 | "discord.js": "^14.0.0", 50 | "tagscript": "*" 51 | }, 52 | "engines": { 53 | "node": ">=v16.9.0" 54 | }, 55 | "files": [ 56 | "dist/**/*.js*", 57 | "dist/**/*.mjs*", 58 | "dist/**/*.d*" 59 | ], 60 | "repository": { 61 | "type": "git", 62 | "url": "git+https://github.com/imranbarbhuiya/tagscript.git", 63 | "directory": "packages/tagscript-plugin-discord" 64 | }, 65 | "bugs": { 66 | "url": "https://github.com/imranbarbhuiya/tagscript/issues" 67 | }, 68 | "homepage": "https://tagscript.js.org/" 69 | } 70 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/index.ts: -------------------------------------------------------------------------------- 1 | export type * from './lib/interfaces'; 2 | export * from './lib/Transformer'; 3 | export * from './lib/Parsers'; 4 | export * from './lib/Utils'; 5 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Parsers/AllowDeny.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser, type IParser, type Context } from 'tagscript'; 2 | /** 3 | * The require tag will attempt to convert the given parameter into a channel, user 4 | * or role, using name or Id. If the user running the tag is not in the targeted 5 | * channel or doesn't have the targeted role or their id isn't same as targeted user's id, the tag will stop processing and 6 | * it will send the response if one is given. Multiple role, user or channel 7 | * requirements can be given, and should be split by a `,`. 8 | * 9 | * Aliases: allowlist, whitelist 10 | * 11 | * @example 12 | * ```yaml 13 | * {require(user,role,channel):response} 14 | * ``` 15 | * @example 16 | * ```yaml 17 | * {require(Moderator)} 18 | * {require(#general, #bot-commands):This tag can only be run in #general and #bot-cmds.} 19 | * {require(757425366209134764, 668713062186090506, 737961895356792882):You aren't allowed to use this tag.} 20 | * ``` 21 | * 22 | * Developers need to add the check themselves. 23 | * @example 24 | * ```ts showLineNumbers 25 | * const { Interpreter } = require("tagscript") 26 | * const { RequiredParser } = require("@tagscript/plugin-discord") 27 | * 28 | * const ts = new Interpreter(new RequiredParser()) 29 | * 30 | * const result = await ts.run("{require(id1, id2):You aren't allowed to use this tag.}") 31 | * 32 | * if (!result.actions.require.ids.includes(interaction.user.id)) { 33 | * // add channel, role check here or check using name instead of id 34 | * return interaction.reply(result.actions.require.message) 35 | * } 36 | * 37 | * ``` 38 | */ 39 | export class RequiredParser extends BaseParser implements IParser { 40 | public constructor() { 41 | super(['require', 'allowlist', 'whitelist'], true); 42 | } 43 | 44 | public parse(ctx: Context) { 45 | if (ctx.response.actions.require) return null; 46 | const ids = ctx.tag.parameter!.split(',').map((param) => param.trim()); 47 | ctx.response.actions.require = { ids, message: ctx.tag.payload }; 48 | return ''; 49 | } 50 | } 51 | 52 | /** 53 | * The blacklist tag will attempt to convert the given parameter into a channel, 54 | * role, or user using name or Id. If the user running the tag is in the targeted 55 | * channel or has the targeted role or their id isn't same as targeted user's id, the tag will stop processing and 56 | * it will send the response if one is given. Multiple user, role or channel 57 | * requirements can be given, and should be split by a `,`. 58 | * 59 | * @example 60 | * ```yaml 61 | * {deny(user,role,channel):response} 62 | * ``` 63 | * Aliases: denylist, blacklist 64 | * @example 65 | * ```yaml 66 | * {deny(Moderator)} 67 | * {deny(#general, #chat):This tag can't be run in #general and #chat.} 68 | * {deny(757425366209134764, 668713062186090506, 737961895356792882):You aren't allowed to use this tag.} 69 | * ``` 70 | * 71 | * Developers need to add the check themselves. 72 | * @example 73 | * ```ts showLineNumbers 74 | * const { Interpreter } = require("tagscript") 75 | * const { DenyParser } = require("@tagscript/plugin-discord") 76 | * 77 | * const ts = new Interpreter(new DenyParser()) 78 | * 79 | * const result = await ts.run("{require(id1, id2):You aren't allowed to use this tag.}") 80 | * 81 | * if (result.actions.deny.ids.includes(interaction.user.id)) { 82 | * // add channel, role check here or check using name instead of id 83 | * return interaction.reply(result.actions.deny.message) 84 | * } 85 | * ``` 86 | */ 87 | 88 | export class DenyParser extends BaseParser implements IParser { 89 | public constructor() { 90 | super(['denylist', 'deny', 'blacklist'], true); 91 | } 92 | 93 | public parse(ctx: Context) { 94 | if (ctx.response.actions.deny) return null; 95 | const ids = ctx.tag.parameter!.split(',').map((param) => param.trim()); 96 | ctx.response.actions.deny = { ids, message: ctx.tag.payload }; 97 | return ''; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Parsers/Cooldown.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser, type Context, type IParser } from 'tagscript'; 2 | 3 | /** 4 | * The cooldown tag implements cooldowns when running a tag. 5 | * The parameter is the number of seconds to wait before running the tag again. 6 | * The payload is the optional cooldown message to send if the tag is on cooldown. 7 | * Payload can have `{retryAfter}`, `{name}` which will be replaced with the time remaining 8 | * and the name of the tag respectively. 9 | * 10 | * @example 11 | * ```yaml 12 | * { cooldown(seconds): response } 13 | * ``` 14 | * Aliases: cd 15 | * @example 16 | * ```yaml 17 | * { cooldown(5): This tag is on cooldown. } 18 | * { cooldown(5): The tag {name} is on cooldown. Please try again in {retryAfter}. } 19 | * ``` 20 | */ 21 | export class CooldownParser extends BaseParser implements IParser { 22 | public constructor() { 23 | super(['cooldown', 'cd'], true); 24 | } 25 | 26 | public parse(ctx: Context) { 27 | const { parameter, payload } = ctx.tag; 28 | const cooldown = Number.parseInt(parameter!, 10); 29 | 30 | ctx.response.actions.cooldown = { 31 | cooldown, 32 | message: payload 33 | }; 34 | return ''; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Parsers/Delete.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser, type Context, type IParser } from 'tagscript'; 2 | 3 | /** 4 | * Delete the triggered message. 5 | * 6 | * @see Devs need to check for this property in [Response.actions](https://tagscript.js.org/typedoc-api/tagscript/classes/Response#actions) and if true, delete the message. 7 | */ 8 | export class DeleteParser extends BaseParser implements IParser { 9 | public constructor() { 10 | super(['delete', 'del']); 11 | } 12 | 13 | public parse(ctx: Context) { 14 | ctx.response.actions.deleteMessage = true; 15 | 16 | return ''; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Parsers/Files.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser, split, type Context, type IParser } from 'tagscript'; 2 | 3 | /** 4 | * This parser allows sending files along with message using file url. 5 | */ 6 | export class FilesParser extends BaseParser implements IParser { 7 | public constructor() { 8 | super(['files'], false, true); 9 | } 10 | 11 | public parse(ctx: Context) { 12 | ctx.response.actions.files = split(ctx.tag.payload!, true); 13 | return ''; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Parsers/Format.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser, type Context, type IParser } from 'tagscript'; 2 | 3 | export class DateFormatParser extends BaseParser implements IParser { 4 | public constructor() { 5 | super(['date', 'unix', 'currenttime']); 6 | } 7 | 8 | public parse(ctx: Context) { 9 | const { declaration } = ctx.tag; 10 | if (['unix', 'currenttime'].includes(declaration!)) return Date.now().toString(); 11 | 12 | const parameter = ctx.tag.parameter ?? 'f'; 13 | if (!['f', 'F', 't', 'T', 'R'].includes(parameter)) return null; 14 | let payload: number | string = ctx.tag.payload ?? Date.now().toString(); 15 | if (!/^\d+$/.test(payload)) payload = new Date(payload).getTime().toString(); 16 | 17 | if (payload.length > 10) payload = Math.floor(Number(payload) / 1_000); 18 | 19 | return ``; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Parsers/Silent.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser, type Context, type IParser } from 'tagscript'; 2 | 3 | /** 4 | * Silence the used command outputs. 5 | * 6 | * @see Devs need to check for this property in [Response.actions](https://tagscript.js.org/typedoc-api/tagscript/classes/Response#actions) and if true, don't output of the command used. 7 | */ 8 | export class SilentParser extends BaseParser implements IParser { 9 | public constructor() { 10 | super(['silent']); 11 | } 12 | 13 | public parse(ctx: Context) { 14 | ctx.response.actions.silentResponse = true; 15 | 16 | return ''; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Parsers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AllowDeny'; 2 | export * from './Cooldown'; 3 | export * from './Delete'; 4 | export * from './Embed'; 5 | export * from './Files'; 6 | export * from './Format'; 7 | export * from './Silent'; 8 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Transformer/Base.ts: -------------------------------------------------------------------------------- 1 | import type { GuildChannel } from '../interfaces'; 2 | import type { Role, User, GuildMember, Guild, CommandInteraction } from 'discord.js'; 3 | import type { Lexer, ITransformer } from 'tagscript'; 4 | 5 | export type outputResolvable = boolean | number | string | null | undefined; 6 | 7 | /** 8 | * A key value pair without sensitive information. 9 | * 10 | * @typeParam T - The base type. 11 | */ 12 | export interface SafeValues { 13 | [key: string]: outputResolvable | ((base: T) => outputResolvable); 14 | } 15 | 16 | /** 17 | * Transformer for {@link https://discord.js.org | discord.js} objects. 18 | * 19 | * @typeParam T - The base type. 20 | */ 21 | export abstract class BaseTransformer implements ITransformer { 22 | protected base: T; 23 | 24 | protected safeValues: SafeValues = {}; 25 | 26 | public constructor(base: T, safeValues: SafeValues = {}) { 27 | this.base = base; 28 | this.safeValues.id = this.base.id; 29 | // eslint-disable-next-line @typescript-eslint/no-base-to-string 30 | this.safeValues.mention = base.toString(); 31 | this.safeValues.name = 'name' in base ? base.name : ''; 32 | this.updateSafeValues(); 33 | this.safeValues = { ...this.safeValues, ...safeValues }; 34 | } 35 | 36 | public transform(tag: Lexer) { 37 | if (!tag.parameter) return this.safeValues.mention as string; 38 | let value = this.safeValues[tag.parameter]; 39 | if (typeof value === 'function') value = value(this.base); 40 | if (value === undefined) return null; 41 | return `${value ?? ''}`; 42 | } 43 | 44 | public toJSON() { 45 | return this.safeValues; 46 | } 47 | 48 | protected updateSafeValues() { 49 | // 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Transformer/GuildMember.ts: -------------------------------------------------------------------------------- 1 | import { BaseTransformer } from './Base'; 2 | 3 | import type { GuildMember } from 'discord.js'; 4 | 5 | /** 6 | * Transformer for Discord {@link GuildMember}. 7 | * 8 | * Properties: 9 | * ```yaml 10 | * id: Gives member id. 11 | * mention: Mentions the member. 12 | * username: Gives username of the member. 13 | * discriminator: Gives discriminator of the member 14 | * tag: Gives username#discriminator 15 | * avatar: Gives member's custom avatar if they have one. Else it'll be an empty string. 16 | * displayAvatar: Gives member's avatar URL if they have one else gives member's default avatar. 17 | * nickname: Gives member's nickname. 18 | * displayName: Gives member's display name. (nickname if they have one else username) 19 | * joinedAt: Gives member's join date. 20 | * joinedTimestamp: Gives member's join date in ms 21 | * createdAt: Gives member's account create date. 22 | * createdTimestamp: Gives member's account created date in ms 23 | * bot: Gives true if the member is a bot else false. 24 | * color: Gives member's highest role color. 25 | * position: Gives member's highest role position. 26 | * roles: Gives member's roles. 27 | * roleIds: Gives member's roles ids. 28 | * roleNames: Gives member's roles names. 29 | * topRole: Gives member's highest role name. 30 | * timeoutUntil: Gives member's timeout until date. 31 | * timeoutUntilTimestamp: Gives member's timeout until date in ms. 32 | * ``` 33 | * 34 | * @remarks 35 | * You need to use `StrictVarsParser` parser to use this transformer. 36 | * @example 37 | * ```ts showLineNumbers 38 | * import { Interpreter, StrictVarsParser } from 'tagscript'; 39 | * import { MemberTransformer } from '@tagscript/plugin-discord'; 40 | * 41 | * const ts = new Interpreter(new StrictVarsParser()); 42 | * 43 | * await ts.run('Hi {member}', { member: new MemberTransformer(GuildMember) }); 44 | * // Hi <@758880890159235083> 45 | * ``` 46 | */ 47 | export class MemberTransformer extends BaseTransformer { 48 | protected override updateSafeValues() { 49 | this.safeValues.username = this.base.user.username; 50 | this.safeValues.discriminator = this.base.user.discriminator; 51 | this.safeValues.tag = this.base.user.tag; 52 | this.safeValues.avatar = this.base.avatarURL(); 53 | this.safeValues.displayAvatar = this.base.displayAvatarURL(); 54 | this.safeValues.nickname = this.base.nickname; 55 | this.safeValues.displayName = this.base.displayName; 56 | this.safeValues.joinedAt = this.base.joinedAt?.toISOString() ?? ''; 57 | this.safeValues.joinedTimestamp = this.base.joinedTimestamp; 58 | this.safeValues.createdAt = this.base.user.createdAt.toISOString(); 59 | this.safeValues.createdTimestamp = this.base.user.createdTimestamp; 60 | this.safeValues.bot = this.base.user.bot; 61 | this.safeValues.color = this.base.roles.color?.hexColor ?? ''; 62 | this.safeValues.position = this.base.roles.highest.position; 63 | this.safeValues.roles = this.base.roles.cache.map((role) => role).join(' '); 64 | this.safeValues.roleIds = this.base.roles.cache.map((role) => role.id).join(', '); 65 | this.safeValues.roleNames = this.base.roles.cache.map((role) => role.name).join(', '); 66 | this.safeValues.topRole = this.base.roles.highest.name; 67 | this.safeValues.timeoutUntil = this.base.communicationDisabledUntil?.toISOString() ?? ''; 68 | this.safeValues.timeoutUntilTimestamp = this.base.communicationDisabledUntilTimestamp; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Transformer/GuildTextBasedChannel.ts: -------------------------------------------------------------------------------- 1 | import { BaseTransformer } from './Base'; 2 | 3 | import type { GuildChannel } from '../interfaces'; 4 | 5 | /** 6 | * Transformer for Discord {@link GuildChannel} 7 | * 8 | * Properties: 9 | * ```yaml 10 | * id: Gives channel id. 11 | * mention: Mentions the channel. 12 | * name: Gives channel name. 13 | * topic: Gives channel topic. 14 | * type: Gives channel type. 15 | * position: Gives channel position. 16 | * nsfw: Gives true if the channel is nsfw else false. 17 | * parentId: Gives channel parent id. 18 | * parentName: Gives channel parent name. 19 | * parentType: Gives channel parent type. 20 | * parentPosition: Gives channel parent position. 21 | * createdAt: Gives channel create date. 22 | * createdTimestamp: Gives channel create date in ms. 23 | * slowmode: Gives channel slowmode. 24 | * ``` 25 | * 26 | * @remarks 27 | * You need to use `StrictVarsParser` parser to use this transformer. 28 | * @example 29 | * ```ts showLineNumbers 30 | * import { Interpreter, StrictVarsParser } from 'tagscript'; 31 | * import { ChannelTransformer } from '@tagscript/plugin-discord'; 32 | * 33 | * const ts = new Interpreter(new StrictVarsParser()); 34 | * 35 | * await ts.run('channel: {channel}', { channel: new ChannelTransformer(message.channel) }); 36 | * // channel: <#870354581115256852> 37 | * ``` 38 | */ 39 | export class ChannelTransformer extends BaseTransformer { 40 | protected override updateSafeValues() { 41 | this.safeValues.topic = 'topic' in this.base ? this.base.topic : ''; 42 | this.safeValues.type = this.base.type; 43 | this.safeValues.position = 'position' in this.base ? this.base.position : 0; 44 | this.safeValues.nsfw = 'nsfw' in this.base ? this.base.nsfw : this.base.parent && 'nsfw' in this.base.parent ? this.base.parent.nsfw : false; 45 | this.safeValues.parentId = this.base.parentId; 46 | this.safeValues.parentName = this.base.parent?.name ?? ''; 47 | this.safeValues.parentType = this.base.parent?.type ?? ''; 48 | this.safeValues.parentPosition = this.base.parent?.position ?? 0; 49 | this.safeValues.createdAt = this.base.createdAt?.toISOString() ?? ''; 50 | this.safeValues.createdTimestamp = this.base.createdTimestamp; 51 | this.safeValues.slowmode = 'rateLimitPerUser' in this.base ? this.base.rateLimitPerUser : 0; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Transformer/Interaction.ts: -------------------------------------------------------------------------------- 1 | import { BaseTransformer } from './Base'; 2 | 3 | import type { CommandInteraction } from 'discord.js'; 4 | 5 | /** 6 | * Transformer for Discord {@link CommandInteraction} 7 | * 8 | * @remarks 9 | * You need to use `StrictVarsParser` parser to use this transformer. 10 | * @example 11 | * ```ts showLineNumbers 12 | * import { Interpreter, StrictVarsParser } from 'tagscript'; 13 | * import { InteractionTransformer } from '@tagscript/plugin-discord'; 14 | * 15 | * const ts = new Interpreter(new StrictVarsParser()); 16 | * 17 | * await ts.run('You've used the command `{command.name}`', { command: new InteractionTransformer(Role) }); 18 | * // You've used the command `ping` 19 | * ``` 20 | */ 21 | export class InteractionTransformer extends BaseTransformer { 22 | protected override updateSafeValues() { 23 | this.safeValues.applicationId = this.base.applicationId; 24 | this.safeValues.channelId = this.base.channelId; 25 | this.safeValues.guildId = this.base.guildId; 26 | this.safeValues.commandId = this.base.commandId; 27 | this.safeValues.commandName = this.base.commandName; 28 | this.safeValues.locale = this.base.locale; 29 | this.safeValues.guildLocale = this.base.guildLocale; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Transformer/Role.ts: -------------------------------------------------------------------------------- 1 | import { BaseTransformer } from './Base'; 2 | 3 | import type { Role } from 'discord.js'; 4 | 5 | /** 6 | * Transformer for Discord {@link Role}. 7 | * 8 | * Properties: 9 | * ```yaml 10 | * id: Gives role id. 11 | * name: Gives role name. 12 | * mention: Mentions the role. 13 | * color: Gives role color. 14 | * hoist: Gives true if the role is hoisted else false. 15 | * mentionable: Gives true if the role is mentionable else false. 16 | * position: Gives role position. 17 | * permissions: Gives role permissions. 18 | * createdAt: Gives role create date. 19 | * createdTimestamp: Gives role create date in ms. 20 | * memberCount: Gives role member count. 21 | * ``` 22 | * 23 | * @remarks 24 | * You need to use `StrictVarsParser` parser to use this transformer. 25 | * @example 26 | * ```ts showLineNumbers 27 | * import { Interpreter, StrictVarsParser } from 'tagscript'; 28 | * import { RoleTransformer } from '@tagscript/plugin-discord'; 29 | * 30 | * const ts = new Interpreter(new StrictVarsParser()); 31 | * 32 | * await ts.run('Ping {role}', { role: new RoleTransformer(Role) }); 33 | * // Ping <@&868430685231271966> 34 | * ``` 35 | */ 36 | export class RoleTransformer extends BaseTransformer { 37 | protected override updateSafeValues() { 38 | this.safeValues.color = this.base.color.toString(); 39 | this.safeValues.hoist = this.base.hoist; 40 | this.safeValues.mentionable = this.base.mentionable; 41 | this.safeValues.position = this.base.position; 42 | this.safeValues.permissions = this.base.permissions.toArray().join(', '); 43 | this.safeValues.createdAt = this.base.createdAt.toISOString(); 44 | this.safeValues.createdTimestamp = this.base.createdTimestamp; 45 | this.safeValues.memberCount = this.base.members.size; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Transformer/User.ts: -------------------------------------------------------------------------------- 1 | import { BaseTransformer } from './Base'; 2 | 3 | import type { User } from 'discord.js'; 4 | 5 | /** 6 | * Transformer for Discord {@link User}. 7 | * 8 | * Properties: 9 | * ```yaml 10 | * id: Gives user id. 11 | * mention: Mentions the user. 12 | * globalName: Gives user's global name. 13 | * username: Gives username of the user. 14 | * discriminator: Gives discriminator of the user 15 | * tag: Gives username#discriminator 16 | * avatar: Gives user's custom avatar if they have one. Else it'll be an empty string. 17 | * displayAvatar: Gives user's avatar URL if they have one else gives user's default avatar. 18 | * createdAt: Gives user's account create date. 19 | * createdTimestamp: Gives user's account created date in ms 20 | * bot: Gives true if the user is a bot else false. 21 | * ``` 22 | * 23 | * @remarks 24 | * You need to use `StrictVarsParser` parser to use this transformer. 25 | * @example 26 | * ```ts showLineNumbers 27 | * import { Interpreter, StrictVarsParser } from 'tagscript'; 28 | * import { UserTransformer } from '@tagscript/plugin-discord'; 29 | * 30 | * const ts = new Interpreter(new StrictVarsParser()); 31 | * 32 | * await ts.run('Hi {user}', { user: new UserTransformer(message.author) }); 33 | * // Hi <@758880890159235083> 34 | * ``` 35 | */ 36 | export class UserTransformer extends BaseTransformer { 37 | protected override updateSafeValues() { 38 | this.safeValues.globalName = this.base.globalName; 39 | this.safeValues.username = this.base.username; 40 | this.safeValues.discriminator = this.base.discriminator; 41 | this.safeValues.tag = this.base.tag; 42 | this.safeValues.avatar = this.base.avatarURL(); 43 | this.safeValues.displayAvatar = this.base.displayAvatarURL(); 44 | this.safeValues.createdAt = this.base.createdAt.toISOString(); 45 | this.safeValues.createdTimestamp = this.base.createdTimestamp; 46 | this.safeValues.bot = this.base.bot; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Transformer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Base'; 2 | export * from './Guild'; 3 | export * from './GuildMember'; 4 | export * from './GuildTextBasedChannel'; 5 | export * from './Role'; 6 | export * from './User'; 7 | export * from './Interaction'; 8 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Utils/CommandInteraction.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApplicationCommandOptionType, 3 | BaseChannel, 4 | GuildMember, 5 | Role, 6 | User, 7 | type CommandInteractionOption, 8 | type CommandInteractionOptionResolver 9 | } from 'discord.js'; 10 | import { IntegerTransformer, StringTransformer, type ITransformer } from 'tagscript'; 11 | 12 | import { ChannelTransformer, MemberTransformer, RoleTransformer, UserTransformer } from '../Transformer'; 13 | 14 | export const mapOptions = (options: readonly CommandInteractionOption[], transformers: Record, prefix = '') => { 15 | for (const data of options) { 16 | switch (data.type) { 17 | case ApplicationCommandOptionType.SubcommandGroup: 18 | transformers.subCommandGroup = new StringTransformer(data.value as string); 19 | mapOptions(data.options!, transformers, `${data.name}-`); 20 | break; 21 | case ApplicationCommandOptionType.Subcommand: 22 | transformers.subCommand = new StringTransformer(data.value as string); 23 | mapOptions(data.options!, transformers, `${prefix}${data.name}-`); 24 | break; 25 | case ApplicationCommandOptionType.String: 26 | transformers[prefix + data.name] = new StringTransformer(data.value as string); 27 | break; 28 | case ApplicationCommandOptionType.Boolean: 29 | transformers[prefix + data.name] = new StringTransformer(data.value as string); 30 | break; 31 | case ApplicationCommandOptionType.Integer: 32 | transformers[prefix + data.name] = new IntegerTransformer(data.value as `${number}`); 33 | break; 34 | case ApplicationCommandOptionType.Number: 35 | transformers[prefix + data.name] = new IntegerTransformer(data.value as `${number}`); 36 | break; 37 | case ApplicationCommandOptionType.Mentionable: 38 | transformers[prefix + data.name] = 39 | data.member instanceof GuildMember 40 | ? new MemberTransformer(data.member) 41 | : data.role instanceof Role 42 | ? new RoleTransformer(data.role) 43 | : data.user instanceof User 44 | ? new UserTransformer(data.user) 45 | : // FIXME: added only for test. Will be removed after rewriting these tests 46 | new StringTransformer(data.value as string); 47 | break; 48 | case ApplicationCommandOptionType.User: 49 | transformers[prefix + data.name] = 50 | data.member instanceof GuildMember 51 | ? new MemberTransformer(data.member) 52 | : data.user 53 | ? new UserTransformer(data.user) 54 | : // FIXME: added only for test. Will be removed after rewriting these tests 55 | new StringTransformer(data.value as string); 56 | break; 57 | case ApplicationCommandOptionType.Role: 58 | if (data.role instanceof Role) transformers[prefix + data.name] = new RoleTransformer(data.role); 59 | break; 60 | case ApplicationCommandOptionType.Channel: 61 | if (data.channel instanceof BaseChannel) transformers[prefix + data.name] = new ChannelTransformer(data.channel); 62 | break; 63 | case ApplicationCommandOptionType.Attachment: 64 | transformers[prefix + data.name] = new StringTransformer(data.attachment!.url); 65 | } 66 | } 67 | }; 68 | 69 | /** 70 | * 71 | * Resolves {@link CommandInteractionOptionResolver} options to transformers. 72 | * 73 | * @example 74 | * ```ts showLineNumbers 75 | * client.on('interactionCreate', async interaction => { 76 | * if (!interaction.isCommand()) return; 77 | * 78 | * if (interaction.commandName === 'ping') { 79 | * const result = await ts.run(str, resolveCommandOptions(interaction.options)); 80 | * await interaction.reply(result.body); 81 | * } 82 | * }); 83 | * ``` 84 | */ 85 | export const resolveCommandOptions = (options: Omit) => { 86 | const optionData = options.data; 87 | 88 | const transformers: Record = {}; 89 | 90 | mapOptions(optionData, transformers); 91 | 92 | return transformers; 93 | }; 94 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CommandInteraction'; 2 | export * from './resolveColor'; 3 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/Utils/resolveColor.ts: -------------------------------------------------------------------------------- 1 | import { resolveColor as DJSResolveColor, type ColorResolvable } from 'discord.js'; 2 | 3 | /** 4 | * Resolves a color to a number. This function doesn't throw for invalid colors but returns the input 5 | * 6 | * @param color - The color to resolve 7 | * @returns 8 | */ 9 | export const resolveColor = (color: string): number | string => { 10 | try { 11 | return Number(color) || DJSResolveColor(color as ColorResolvable); 12 | } catch { 13 | return color; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/lib/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | import type { EmbedData, APIEmbed, Channel, Guild } from 'discord.js'; 2 | import 'tagscript'; 3 | 4 | declare module 'tagscript' { 5 | export interface IActions { 6 | cooldown?: { 7 | cooldown: number; 8 | message: string | null; 9 | }; 10 | deleteMessage?: boolean; 11 | embed?: APIEmbed | EmbedData; 12 | files?: string[]; 13 | silentResponse?: boolean; 14 | } 15 | } 16 | 17 | export type GuildChannel = Extract; 18 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./", 5 | "outDir": "../dist", 6 | "incremental": false 7 | }, 8 | "include": ["."] 9 | } 10 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Parsers/AllowDeny.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter } from 'tagscript'; 2 | 3 | import { RequiredParser, DenyParser } from '../../src'; 4 | 5 | describe('RequiredParser', () => { 6 | const ts = new Interpreter(new RequiredParser()); 7 | 8 | test('GIVEN a require tag THEN return empty string and require info', async () => { 9 | expect((await ts.run("{require(758880890159235083):You aren't allowed to use this tag.}")).actions).toStrictEqual({ 10 | require: { 11 | ids: ['758880890159235083'], 12 | message: "You aren't allowed to use this tag." 13 | } 14 | }); 15 | }); 16 | 17 | test('GIVEN a require tag twice THEN ignore 2nd one', async () => { 18 | expect((await ts.run('{require(757164765582721054)} {require(758880890159235083)}')).actions).toStrictEqual({ 19 | require: { 20 | ids: ['757164765582721054'], 21 | message: null 22 | } 23 | }); 24 | }); 25 | }); 26 | 27 | describe('DenyParser', () => { 28 | const ts = new Interpreter(new DenyParser()); 29 | 30 | test('GIVEN a deny tag THEN return empty string and deny info', async () => { 31 | expect((await ts.run("{deny(758880890159235083):You aren't allowed to use this tag.}")).actions).toStrictEqual({ 32 | deny: { 33 | ids: ['758880890159235083'], 34 | message: "You aren't allowed to use this tag." 35 | } 36 | }); 37 | }); 38 | 39 | test('GIVEN a deny tag twice THEN ignore 2nd one', async () => { 40 | expect((await ts.run('{deny(757164765582721054)} {deny(758880890159235083)}')).actions).toStrictEqual({ 41 | deny: { 42 | ids: ['757164765582721054'], 43 | message: null 44 | } 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Parsers/Cooldown.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter } from 'tagscript'; 2 | 3 | import { CooldownParser } from '../../src'; 4 | 5 | describe('CooldownParser', () => { 6 | const ts = new Interpreter(new CooldownParser()); 7 | 8 | test('GIVEN a cooldown tag THEN return empty string and cooldown info', async () => { 9 | expect((await ts.run('{cd(5):You are in cd}')).actions).toStrictEqual({ 10 | cooldown: { 11 | cooldown: 5, 12 | message: 'You are in cd' 13 | } 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Parsers/Delete.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter } from 'tagscript'; 2 | 3 | import { DeleteParser } from '../../src'; 4 | 5 | describe('DeleteParser', () => { 6 | const ts = new Interpreter(new DeleteParser()); 7 | 8 | test('GIVEN a Delete tag THEN return empty string and Delete: true in actions', async () => { 9 | expect((await ts.run('{delete}')).actions).toStrictEqual({ 10 | deleteMessage: true 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Parsers/Embed.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter } from 'tagscript'; 2 | 3 | import { EmbedParser } from '../../src'; 4 | 5 | const ts = new Interpreter(new EmbedParser()); 6 | describe('EmbedParser', () => { 7 | test('GIVEN a JSON in embed tag THEN parse the JSON correctly', async () => { 8 | const text = '{embed:{"title":"Hello!", "description":"This is a test embed."}}'; 9 | 10 | expect((await ts.run(text)).actions.embed).toStrictEqual({ 11 | title: 'Hello!', 12 | description: 'This is a test embed.' 13 | }); 14 | }); 15 | 16 | test('GIVEN any property in embed tag parameter THEN give embed with that property', async () => { 17 | const tText = '{embed(title):Rules}'; 18 | 19 | expect((await ts.run(tText)).actions.embed).toStrictEqual({ 20 | title: 'Rules' 21 | }); 22 | 23 | const dText = '{embed(description):Follow these rules to ensure a good experience in our server!}'; 24 | 25 | expect((await ts.run(dText)).actions.embed).toStrictEqual({ 26 | description: 'Follow these rules to ensure a good experience in our server!' 27 | }); 28 | 29 | const fText = '{embed(field):Rule 1|Respect everyone you speak to.|false}'; 30 | 31 | expect((await ts.run(fText)).actions.embed).toStrictEqual({ 32 | fields: [ 33 | { 34 | name: 'Rule 1', 35 | value: 'Respect everyone you speak to.', 36 | inline: false 37 | } 38 | ] 39 | }); 40 | 41 | const f2Text = '{embed(field):Rule 1}'; 42 | 43 | expect((await ts.run(f2Text)).actions.embed).toBeUndefined(); 44 | 45 | const f3Text = "{embed(field):Rule 2|Don't spam.|true}"; 46 | 47 | expect((await ts.run(fText + f3Text)).actions.embed).toStrictEqual({ 48 | fields: [ 49 | { 50 | name: 'Rule 1', 51 | value: 'Respect everyone you speak to.', 52 | inline: false 53 | }, 54 | { 55 | name: 'Rule 2', 56 | value: "Don't spam.", 57 | inline: true 58 | } 59 | ] 60 | }); 61 | 62 | expect((await ts.run(tText + f3Text)).actions.embed).toStrictEqual({ 63 | title: 'Rules', 64 | fields: [ 65 | { 66 | name: 'Rule 2', 67 | value: "Don't spam.", 68 | inline: true 69 | } 70 | ] 71 | }); 72 | }); 73 | 74 | test('GIVEN image or thumbnail in object form in embed tag parameter THEN give embed with that property', async () => { 75 | const iText = '{embed(image):{"url":"https://example.com/image.png"}}'; 76 | 77 | expect((await ts.run(iText)).actions.embed).toStrictEqual({ 78 | image: { 79 | url: 'https://example.com/image.png' 80 | } 81 | }); 82 | 83 | const tText = '{embed(thumbnail):{"url":"https://example.com/image.png"}}'; 84 | 85 | expect((await ts.run(tText)).actions.embed).toStrictEqual({ 86 | thumbnail: { 87 | url: 'https://example.com/image.png' 88 | } 89 | }); 90 | 91 | const aText = '{embed(author):{"name":"Mahir", "icon_url":"https://example.com/image.png"}}'; 92 | 93 | expect((await ts.run(aText)).actions.embed).toStrictEqual({ 94 | author: { name: 'Mahir', icon_url: 'https://example.com/image.png' } 95 | }); 96 | }); 97 | 98 | test('GIVEN color in JSON THEN resolve it to hex color', async () => { 99 | const text = '{embed:{"color":"0x00ff00"}}'; 100 | 101 | expect((await ts.run(text)).actions.embed).toStrictEqual({ 102 | color: 65_280 103 | }); 104 | }); 105 | 106 | test.each(['Red', '#ed4245', 0xed4245])('GIVEN color %j in property THEN resolve it to hex color', async (color) => { 107 | expect((await ts.run(`{embed(color):${color}}`)).actions.embed).toStrictEqual({ 108 | color: 0xed4245 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Parsers/Files.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter } from 'tagscript'; 2 | 3 | import { FilesParser } from '../../src'; 4 | 5 | describe('Files', () => { 6 | test('GIVEN file url WHEN parse THEN return empty string and url in actions', async () => { 7 | const ts = new Interpreter(new FilesParser()); 8 | const text = '{files:https://example.com/file.txt,https://example.com/file2.txt}'; 9 | 10 | expect((await ts.run(text)).actions.files).toStrictEqual(['https://example.com/file.txt', 'https://example.com/file2.txt']); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Parsers/Format.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter } from 'tagscript'; 2 | 3 | import { DateFormatParser } from '../../src'; 4 | 5 | const ts = new Interpreter(new DateFormatParser()); 6 | describe('currentTime', () => { 7 | beforeEach(() => { 8 | vi.useFakeTimers(); 9 | }); 10 | afterEach(() => { 11 | vi.useRealTimers(); 12 | }); 13 | test('GIVEN currentTime or unix THEN return current timestamp in ms', async () => { 14 | const mockedDate = new Date(2_022, 1, 1, 13); 15 | vi.setSystemTime(mockedDate); 16 | expect(Number((await ts.run('{unix}')).body)).toBeCloseTo(Date.now(), -1); 17 | expect(Number((await ts.run('{currenttime}')).body)).toBeCloseTo(Date.now(), -1); 18 | }); 19 | }); 20 | 21 | describe('DateFormat', () => { 22 | test('GIVEN date THEN return formatted date', async () => { 23 | expect((await ts.run('{date}')).body).toBe(``); 24 | expect((await ts.run('{date:2020-01-01}')).body).toBe(''); 25 | expect((await ts.run('{date(k):2020-01-01}')).body).toBe('{date(k):2020-01-01}'); 26 | expect((await ts.run('{date(F):2020-01-01}')).body).toBe(''); 27 | expect((await ts.run('{date(t):2020-01-01}')).body).toBe(''); 28 | expect((await ts.run('{date(T):2020-01-01}')).body).toBe(''); 29 | expect((await ts.run('{date(R):2020-01-01}')).body).toBe(''); 30 | expect((await ts.run('{date(R):1577836800000}')).body).toBe(''); 31 | expect((await ts.run('{date(R):1577836800}')).body).toBe(''); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Parsers/Silent.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter } from 'tagscript'; 2 | 3 | import { SilentParser } from '../../src'; 4 | 5 | describe('SilentParser', () => { 6 | const ts = new Interpreter(new SilentParser()); 7 | 8 | test('GIVEN a Silent tag THEN return empty string and Silent: true in actions', async () => { 9 | expect((await ts.run('{silent}')).actions).toStrictEqual({ 10 | silentResponse: true 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Transformer/Guild.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter, StrictVarsParser } from 'tagscript'; 2 | 3 | import { GuildTransformer } from '../../src'; 4 | import { guild } from '../Structures/Structures'; 5 | 6 | const ts = new Interpreter(new StrictVarsParser()); 7 | describe('GuildTransformer', () => { 8 | test('GIVEN a guild tag THEN return value from guild variable', async () => { 9 | expect((await ts.run('{guild}', { guild: new GuildTransformer(guild) })).body).toBe('My Guild'); 10 | }); 11 | 12 | it('should match the snapshot', async () => { 13 | const transformer = new GuildTransformer(guild); 14 | 15 | expect(transformer.toJSON()).toMatchSnapshot(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Transformer/GuildMember.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter, StrictVarsParser } from 'tagscript'; 2 | 3 | import { MemberTransformer } from '../../src'; 4 | import { member } from '../Structures/Structures'; 5 | 6 | const ts = new Interpreter(new StrictVarsParser()); 7 | 8 | describe('MemberTransformer', () => { 9 | test('GIVEN a member tag THEN return value from member variable', async () => { 10 | expect((await ts.run('{member}', { member: new MemberTransformer(member) })).body).toBe('<@758880890159235083>'); 11 | expect((await ts.run('{member(nickname)}', { member: new MemberTransformer(member) })).body).toBe(''); 12 | }); 13 | 14 | it('should match the snapshot', async () => { 15 | const userTransformer = new MemberTransformer(member); 16 | 17 | expect(userTransformer.toJSON()).toMatchSnapshot(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Transformer/GuildTextBasedChannel.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter, StrictVarsParser } from 'tagscript'; 2 | 3 | import { ChannelTransformer } from '../../src'; 4 | import { channel, channel2 } from '../Structures/Structures'; 5 | 6 | const ts = new Interpreter(new StrictVarsParser()); 7 | 8 | describe('ChannelTransformer', () => { 9 | test('GIVEN a channel tag THEN return value from channel variable', async () => { 10 | expect((await ts.run('{channel}', { channel: new ChannelTransformer(channel), channel2: new ChannelTransformer(channel2) })).body).toBe( 11 | '<#933395546138357800>' 12 | ); 13 | }); 14 | 15 | it('should match the snapshot', async () => { 16 | const userTransformer = new ChannelTransformer(channel); 17 | 18 | expect(userTransformer.toJSON()).toMatchSnapshot(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Transformer/Interaction.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter, StrictVarsParser } from 'tagscript'; 2 | 3 | import { InteractionTransformer } from '../../src'; 4 | import { interaction } from '../Structures/Structures'; 5 | 6 | const ts = new Interpreter(new StrictVarsParser()); 7 | 8 | // TODO(@imranbarbhuiya): Add tests for all the properties. 9 | describe('InteractionTransformer', () => { 10 | test('GIVEN tag THEN return value from guild variable', async () => { 11 | expect((await ts.run('{interaction.locale}', { interaction: new InteractionTransformer(interaction) })).body).toBe('en-US'); 12 | }); 13 | 14 | it('should match the snapshot', async () => { 15 | const userTransformer = new InteractionTransformer(interaction); 16 | 17 | expect(userTransformer.toJSON()).toMatchSnapshot(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Transformer/Role.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter, StrictVarsParser } from 'tagscript'; 2 | 3 | import { RoleTransformer } from '../../src'; 4 | import { role } from '../Structures/Structures'; 5 | 6 | const ts = new Interpreter(new StrictVarsParser()); 7 | 8 | describe('RoleTransformer', () => { 9 | test('GIVEN a role tag THEN return value from Role variable', async () => { 10 | expect((await ts.run('{role}', { role: new RoleTransformer(role) })).body).toBe('<@&933378013154906142>'); 11 | }); 12 | 13 | it('should match the snapshot', async () => { 14 | const userTransformer = new RoleTransformer(role); 15 | 16 | expect(userTransformer.toJSON()).toMatchSnapshot(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Transformer/User.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter, StrictVarsParser } from 'tagscript'; 2 | 3 | import { UserTransformer } from '../../src'; 4 | import { user } from '../Structures/Structures'; 5 | 6 | const ts = new Interpreter(new StrictVarsParser()); 7 | 8 | describe('UserTransformer', () => { 9 | test('GIVEN a user tag THEN return value from user variable', async () => { 10 | expect((await ts.run('{user}', { user: new UserTransformer(user) })).body).toBe('<@758880890159235083>'); 11 | expect((await ts.run('{user(username)}', { user: new UserTransformer(user) })).body).toBe('parbez'); 12 | expect((await ts.run('{user(a)}', { user: new UserTransformer(user) })).body).toBe('{user(a)}'); 13 | expect((await ts.run('{user(b)}', { user: new UserTransformer(user, { b: (user) => user.defaultAvatarURL }) })).body).toBe( 14 | 'https://cdn.discordapp.com/embed/avatars/0.png' 15 | ); 16 | }); 17 | 18 | it('should match the snapshot', async () => { 19 | const transformer = new UserTransformer(user); 20 | 21 | expect(transformer.toJSON()).toMatchSnapshot(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Transformer/__snapshots__/Guild.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`GuildTransformer > should match the snapshot 1`] = ` 4 | { 5 | "afkChannel": "null", 6 | "afkTimeout": 300, 7 | "banner": null, 8 | "bots": 0, 9 | "channelCount": 0, 10 | "channelIds": "\`None\`", 11 | "channelNames": "\`None\`", 12 | "channels": "\`None\`", 13 | "createdAt": "2022-01-19T14:32:47.467Z", 14 | "createdTimestamp": 1642602767467, 15 | "description": null, 16 | "emojiCount": 0, 17 | "features": "\`None\`", 18 | "humans": 0, 19 | "icon": "https://cdn.discordapp.com/icons/933368398996447292/396ee43e3064f8ec805fede6f3bcdc6d.webp", 20 | "id": "933368398996447292", 21 | "large": undefined, 22 | "memberCount": undefined, 23 | "mention": "My Guild", 24 | "name": "My Guild", 25 | "ownerId": "938716130720235601", 26 | "random": "", 27 | "roleCount": 2, 28 | "roleIds": "933378013154906142, 933368398996447292", 29 | "roleNames": "., @everyone", 30 | "roles": "<@&933378013154906142> @everyone", 31 | "splash": null, 32 | "stickerCount": 0, 33 | "verificationLevel": 0, 34 | } 35 | `; 36 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Transformer/__snapshots__/GuildMember.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`MemberTransformer > should match the snapshot 1`] = ` 4 | { 5 | "avatar": null, 6 | "bot": false, 7 | "color": "", 8 | "createdAt": "2020-09-25T02:41:43.539Z", 9 | "createdTimestamp": 1601001703539, 10 | "discriminator": "0000", 11 | "displayAvatar": "https://cdn.discordapp.com/avatars/758880890159235083/17ac5f89d5f8b08b5bbd6cc43c930399.webp", 12 | "displayName": "Parbez", 13 | "id": "758880890159235083", 14 | "joinedAt": "2022-01-19T16:52:53.953Z", 15 | "joinedTimestamp": 1642611173953, 16 | "mention": "<@758880890159235083>", 17 | "name": "", 18 | "nickname": null, 19 | "position": 1, 20 | "roleIds": "933378013154906142, 933368398996447292", 21 | "roleNames": "., @everyone", 22 | "roles": "<@&933378013154906142> @everyone", 23 | "tag": "parbez#0000", 24 | "timeoutUntil": "", 25 | "timeoutUntilTimestamp": null, 26 | "topRole": ".", 27 | "username": "parbez", 28 | } 29 | `; 30 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Transformer/__snapshots__/GuildTextBasedChannel.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`ChannelTransformer > should match the snapshot 1`] = ` 4 | { 5 | "createdAt": "2022-01-19T16:20:39.850Z", 6 | "createdTimestamp": 1642609239850, 7 | "id": "933395546138357800", 8 | "mention": "<#933395546138357800>", 9 | "name": "test", 10 | "nsfw": false, 11 | "parentId": null, 12 | "parentName": "", 13 | "parentPosition": 0, 14 | "parentType": "", 15 | "position": 0, 16 | "slowmode": 0, 17 | "topic": "A test channel", 18 | "type": 0, 19 | } 20 | `; 21 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Transformer/__snapshots__/Interaction.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`InteractionTransformer > should match the snapshot 1`] = ` 4 | { 5 | "applicationId": "938716130720235601", 6 | "channelId": "933395546138357800", 7 | "commandId": "938716130720235601", 8 | "commandName": "ping", 9 | "guildId": "933368398996447292", 10 | "guildLocale": "en-US", 11 | "id": "933368398996447292", 12 | "locale": "en-US", 13 | "mention": "/ping sub-command member:758880890159235083", 14 | "name": "", 15 | } 16 | `; 17 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Transformer/__snapshots__/Role.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`RoleTransformer > should match the snapshot 1`] = ` 4 | { 5 | "color": "0", 6 | "createdAt": "2022-01-19T15:10:59.661Z", 7 | "createdTimestamp": 1642605059661, 8 | "hoist": false, 9 | "id": "933378013154906142", 10 | "memberCount": 0, 11 | "mention": "<@&933378013154906142>", 12 | "mentionable": false, 13 | "name": ".", 14 | "permissions": "Administrator", 15 | "position": 1, 16 | } 17 | `; 18 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Transformer/__snapshots__/User.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`UserTransformer > should match the snapshot 1`] = ` 4 | { 5 | "avatar": "https://cdn.discordapp.com/avatars/758880890159235083/17ac5f89d5f8b08b5bbd6cc43c930399.webp", 6 | "bot": false, 7 | "createdAt": "2020-09-25T02:41:43.539Z", 8 | "createdTimestamp": 1601001703539, 9 | "discriminator": "0000", 10 | "displayAvatar": "https://cdn.discordapp.com/avatars/758880890159235083/17ac5f89d5f8b08b5bbd6cc43c930399.webp", 11 | "globalName": "Parbez", 12 | "id": "758880890159235083", 13 | "mention": "<@758880890159235083>", 14 | "name": "", 15 | "tag": "parbez#0000", 16 | "username": "parbez", 17 | } 18 | `; 19 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Utils/CommandInteraction.test.ts: -------------------------------------------------------------------------------- 1 | import { resolveCommandOptions } from '../../src'; 2 | import { interaction } from '../Structures/Structures'; 3 | 4 | // TODO(@imranbarbhuiya): Separate tests for each property. 5 | describe('resolveCommandOptions', () => { 6 | test('GIVEN a interaction tag THEN return value from interaction variable', () => { 7 | // expect(resolveCommandOptions(interaction.options)).toBe({ 8 | // subCommand: new StringTransformer('sub-command'), 9 | // subCommandGroup: new StringTransformer('sub-command-group'), 10 | // 'sub-command-member': new MemberTransformer(member), 11 | // 'sub-command-group-sub-command-member': new MemberTransformer(member), 12 | // string: new StringTransformer('Hello'), 13 | // channel: new ChannelTransformer(channel), 14 | // role: new RoleTransformer(role), 15 | // mentionable: new RoleTransformer(role), 16 | // mentionable2: new MemberTransformer(member), 17 | // boolean: new StringTransformer('true'), 18 | // number: new StringTransformer('1.1'), 19 | // integer: new StringTransformer('1') 20 | // }); 21 | expect(Object.keys(resolveCommandOptions(interaction.options))).toStrictEqual([ 22 | 'subCommand', 23 | 'sub-command-member', 24 | 'subCommandGroup', 25 | 'sub-command-group-sub-command-channel', 26 | 'string', 27 | 'mentionable', 28 | 'mentionable-2', 29 | 'boolean', 30 | 'number', 31 | 'integer', 32 | 'attachment', 33 | 'user' 34 | ]); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/Utils/resolveColor.test.ts: -------------------------------------------------------------------------------- 1 | import { resolveColor } from '../../src'; 2 | 3 | describe('ResolveColor', () => { 4 | test('GIVEN a color name THEN return valid hex code', () => { 5 | expect(resolveColor('Red')).toBe(0xed4245); 6 | }); 7 | 8 | test('GIVEN a hex code starts with # THEN return valid hex code', () => { 9 | expect(resolveColor('#FF0000')).toBe(0xff0000); 10 | }); 11 | 12 | test('GIVEN a hex code starts with 0x THEN return valid hex code', () => { 13 | expect(resolveColor('0xFF0000')).toBe(0xff0000); 14 | }); 15 | 16 | test('GIVEN an invalid color THEN return the input', () => { 17 | expect(resolveColor('invalid')).toBe('invalid'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "include": ["."], 4 | "compilerOptions": { 5 | "types": ["vitest/globals"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tsconfig.typecheck.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*.ts", "tests/**/*.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { createTsupConfig } from '../../scripts/tsup.config'; 2 | 3 | export default createTsupConfig({ format: ['esm', 'cjs'] }); 4 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["./src/index.ts"], 3 | "readme": "./README.md", 4 | "tsconfig": "./src/tsconfig.json", 5 | "externalLinkPath": "../../scripts/externalConfig.mjs", 6 | "additionalModuleSources": [] 7 | } 8 | -------------------------------------------------------------------------------- /packages/tagscript-plugin-discord/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { createVitestConfig } from '../../scripts/vitest.config'; 2 | 3 | export default createVitestConfig(); 4 | -------------------------------------------------------------------------------- /packages/tagscript/.cliff-jumperrc.yml: -------------------------------------------------------------------------------- 1 | name: tagscript 2 | monoRepo: true 3 | packagePath: packages/tagscript 4 | tagTemplate: '{{full-name}}@{{new-version}}' 5 | -------------------------------------------------------------------------------- /packages/tagscript/LICENSE: -------------------------------------------------------------------------------- 1 | This work is licensed under the Creative Commons Attribution 4.0 International License. To view a of this license, visit http://creativecommons.org/licenses/by/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 2 | -------------------------------------------------------------------------------- /packages/tagscript/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # TagScript 4 | 5 | **A simple and safe template engine.** 6 | 7 |
8 | 9 | ## Description 10 | 11 | TagScript is a drop in easy to use string interpreter that lets you provide users with ways of customizing their profiles or chat rooms with interactive text. 12 | 13 | Read Full Documentation [here](https://tagscript.js.org/). 14 | 15 | --- 16 | 17 | ## Features 18 | 19 | - Written In Typescript 20 | - Offers CJS, ESM and UMD builds 21 | - Full TypeScript & JavaScript support 22 | - Faster than ⚡ 23 | - Simple, expressive and safe template engine. 24 | - Supports your own parsers and transformers 25 | 26 | ## Installation 27 | 28 | ```bash 29 | # npm 30 | npm i tagscript 31 | 32 | # yarn 33 | yarn add tagscript 34 | 35 | ``` 36 | 37 | ## Usage 38 | 39 | --- 40 | 41 | **Note:** While examples uses `import`, it maps 1:1 with CommonJS' require syntax. For example, 42 | 43 | ```ts 44 | import { Interpreter } from 'tagscript'; 45 | ``` 46 | 47 | is the same as 48 | 49 | ```js 50 | const { Interpreter } = require('tagscript'); 51 | ``` 52 | 53 | --- 54 | 55 | ```ts showLineNumbers 56 | import { Interpreter, RandomParser, RangeParser, FiftyFiftyParser, IfStatementParser, SliceParser } from 'tagscript'; 57 | const ts = new Interpreter(new SliceParser(), new FiftyFiftyParser(), new RandomParser(), new IfStatementParser()); 58 | 59 | const result = await ts.run( 60 | ` 61 | {random: Parbez,Rkn,Priyansh} attempts to pick the lock!, 62 | I pick {if({5050:.}!=):heads|tails} 63 | ` 64 | ); 65 | ``` 66 | 67 | ## Parsers 68 | 69 | Parsers are used to parse a tag and return a value based on the tag. You can use our [builtin parsers](https://tagscript.js.org/typedoc-api/tagscript/interfaces/IParser#implemented-by) or write your own parsers. 70 | Your own parser should implement [IParser](https://tagscript.js.org/typedoc-api/tagscript/interfaces/IParser) interface. 71 | 72 | ```ts showLineNumbers 73 | import { Interpreter, RandomParser, RangeParser, FiftyFiftyParser, IfStatementParser, SliceParser } from 'tagscript'; 74 | const ts = new Interpreter(new SliceParser(), new FiftyFiftyParser(), new RandomParser(), new IfStatementParser()); 75 | ``` 76 | 77 | ## Transformers 78 | 79 | Transformers are used to transform a value based on the tag at runtime. You can use our [builtin transformers](https://tagscript.js.org/typedoc-api/tagscript/interfaces/ITransformer#implimented-by) or write your own transformers. 80 | Your own transformer should implement [ITransformer](https://tagscript.js.org/typedoc-api/tagscript/interfaces/ITransformer) interface. 81 | 82 | ```ts showLineNumbers 83 | import { Interpreter, StringTransformer, StrictVarsParser } from 'tagscript'; 84 | const ts = new Interpreter(new StrictVarsParser()); 85 | 86 | await ts.run('Hi {user}', { user: new StringTransformer(args) }); 87 | // Hi Parbez 88 | ``` 89 | 90 | ## Buy me some doughnuts 91 | 92 | If you want to support me by donating, you can do so by using any of the following methods. Thank you very much in advance! 93 | 94 | Buy Me A Coffee 95 | Buy Me a Coffee at ko-fi.com 96 | 97 | ## Contributors ✨ 98 | 99 | Thanks goes to these wonderful people: 100 | 101 | 102 | 103 | 104 | 105 | ## Special Thanks 106 | 107 | - [JonSnowbd](https://github.com/JonSnowbd/) for creating TagScript in python. 108 | -------------------------------------------------------------------------------- /packages/tagscript/cliff.toml: -------------------------------------------------------------------------------- 1 | [changelog] 2 | header = """ 3 | # Changelog 4 | 5 | All notable changes to this project will be documented in this file.\n 6 | """ 7 | body = """ 8 | {% if version %}\ 9 | # [{{ version | trim_start_matches(pat="v") }}]\ 10 | {% if previous %}\ 11 | {% if previous.version %}\ 12 | (https://github.com/imranbarbhuiya/tagscript/compare/{{ previous.version }}...{{ version }})\ 13 | {% else %}\ 14 | (https://github.com/imranbarbhuiya/tagscript/tree/{{ version }})\ 15 | {% endif %}\ 16 | {% endif %} \ 17 | - ({{ timestamp | date(format="%Y-%m-%d") }}) 18 | {% else %}\ 19 | # [unreleased] 20 | {% endif %}\ 21 | {% for group, commits in commits | group_by(attribute="group") %} 22 | ## {{ group | upper_first }} 23 | {% for commit in commits %} 24 | - {% if commit.scope %}\ 25 | **{{commit.scope}}:** \ 26 | {% endif %}\ 27 | {{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/imranbarbhuiya/tagscript/commit/{{ commit.id }}))\ 28 | {% if commit.breaking %}\ 29 | {% for breakingChange in commit.footers %}\ 30 | \n{% raw %} {% endraw %}- 💥 **{{ breakingChange.token }}{{ breakingChange.separator }}** {{ breakingChange.value }}\ 31 | {% endfor %}\ 32 | {% endif %}\ 33 | {% endfor %} 34 | {% endfor %}\n 35 | """ 36 | trim = true 37 | footer = "" 38 | 39 | [git] 40 | conventional_commits = true 41 | filter_unconventional = true 42 | commit_parsers = [ 43 | { message = "^feat", group = "🚀 Features" }, 44 | { message = "^fix", group = "🐛 Bug Fixes" }, 45 | { message = "^docs", group = "📝 Documentation" }, 46 | { message = "^perf", group = "🏃 Performance" }, 47 | { message = "^refactor", group = "🏠 Refactor" }, 48 | { message = "^typings", group = "⌨️ Typings" }, 49 | { message = "^types", group = "⌨️ Typings" }, 50 | { message = ".*deprecated", body = ".*deprecated", group = "🚨 Deprecation" }, 51 | { message = "^revert", skip = true }, 52 | { message = "^style", group = "🪞 Styling" }, 53 | { message = "^test", group = "🧪 Testing" }, 54 | { message = "^chore", skip = true }, 55 | { message = "^ci", skip = true }, 56 | { message = "^build", skip = true }, 57 | { body = ".*security", group = "🛡️ Security" }, 58 | ] 59 | filter_commits = true 60 | tag_pattern = "tagscript@[0-9]*" 61 | ignore_tags = "" 62 | topo_order = false 63 | sort_commits = "newest" 64 | -------------------------------------------------------------------------------- /packages/tagscript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tagscript", 3 | "version": "1.4.0", 4 | "description": "A simple and safe template engine.", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "browser": "dist/index.global.js", 8 | "unpkg": "dist/index.global.js", 9 | "types": "dist/index.d.ts", 10 | "exports": { 11 | "types": "./dist/index.d.ts", 12 | "import": "./dist/index.mjs", 13 | "require": "./dist/index.js" 14 | }, 15 | "sideEffects": false, 16 | "author": "@imranbarbhuiya", 17 | "scripts": { 18 | "lint": "TIMING=1 eslint src --fix --cache -c ../../eslint.config.mjs", 19 | "build": "tsup", 20 | "prepack": "yarn build", 21 | "bump": "cliff-jumper", 22 | "check-update": "cliff-jumper --dry-run", 23 | "test": "vitest run", 24 | "typecheck": "tsc -p tsconfig.typecheck.json --noEmit" 25 | }, 26 | "keywords": [ 27 | "tagscript", 28 | "string parser", 29 | "safe string", 30 | "typescript", 31 | "template engine", 32 | "template", 33 | "template string", 34 | "tag" 35 | ], 36 | "devDependencies": { 37 | "@favware/cliff-jumper": "^6.0.0", 38 | "tsup": "^8.5.0", 39 | "typescript": "^5.8.3" 40 | }, 41 | "engines": { 42 | "node": ">=v14.0.0" 43 | }, 44 | "files": [ 45 | "dist/**/*.js*", 46 | "dist/**/*.mjs*", 47 | "dist/**/*.d*" 48 | ], 49 | "repository": { 50 | "type": "git", 51 | "url": "git+https://github.com/imranbarbhuiya/tagscript.git", 52 | "directory": "packages/tagscript" 53 | }, 54 | "bugs": { 55 | "url": "https://github.com/imranbarbhuiya/tagscript/issues" 56 | }, 57 | "homepage": "https://tagscript.js.org/" 58 | } 59 | -------------------------------------------------------------------------------- /packages/tagscript/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/Interpreter'; 2 | export * from './lib/Transformer'; 3 | export * from './lib/Parsers'; 4 | export type * from './lib/interfaces'; 5 | export * from './lib/Utils/Util'; 6 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Interpreter/Context.ts: -------------------------------------------------------------------------------- 1 | import type { Lexer, Interpreter, Response } from '.'; 2 | 3 | /** 4 | * An object containing data of the TagScript tag processed by the interpreter. 5 | * This class is passed to transformers and parsers during parsing. 6 | */ 7 | export class Context { 8 | /** 9 | * The tag object representing a TagScript tag. 10 | */ 11 | public tag: Lexer; 12 | 13 | public response: Response; 14 | 15 | /** 16 | * The original message passed to the interpreter. 17 | */ 18 | public originalMessage: string; 19 | 20 | /** 21 | * The interpreter parsing the TagScript. 22 | */ 23 | public interpreter: Interpreter; 24 | 25 | public constructor(tag: Lexer, response: Response, interpreter: Interpreter, originalMessage: string) { 26 | this.tag = tag; 27 | this.originalMessage = originalMessage; 28 | this.interpreter = interpreter; 29 | this.response = response; 30 | } 31 | 32 | public toJSON() { 33 | return { 34 | tag: this.tag, 35 | originalMessage: this.originalMessage, 36 | interpreter: this.interpreter, 37 | response: this.response 38 | }; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Interpreter/Node.ts: -------------------------------------------------------------------------------- 1 | import type { Lexer } from './Lexer'; 2 | 3 | /** 4 | * A low-level object representing a bracketed tag. 5 | */ 6 | export class Node { 7 | public coordinates: [number, number]; 8 | 9 | public tag: Lexer | null; 10 | 11 | public output: string | null; 12 | 13 | public constructor(coordinates: [number, number], tag: Lexer | null) { 14 | this.output = null; 15 | this.tag = tag; 16 | this.coordinates = coordinates; 17 | } 18 | 19 | public toString() { 20 | const [start, end] = this.coordinates; 21 | return `${this.tag ?? ''} at ${start}-${end}`; 22 | } 23 | 24 | public toJSON() { 25 | return { 26 | output: this.output, 27 | tag: this.tag, 28 | coordinates: this.coordinates 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Interpreter/Response.ts: -------------------------------------------------------------------------------- 1 | import type { ITransformer, IKeyValues, IActions } from '../interfaces'; 2 | 3 | /** 4 | * An object containing information on a completed TagScript process. 5 | */ 6 | export class Response { 7 | /** 8 | * The raw string that was used to generate this response. 9 | */ 10 | public raw!: string; 11 | 12 | /** 13 | * The cleaned message with all tags interpreted. 14 | */ 15 | public body: string | null; 16 | 17 | /** 18 | * An object with all the variables that parsers such as the `LooseVarsParser` can access. 19 | */ 20 | public variables: { [key: string]: ITransformer }; 21 | 22 | /** 23 | * An object containing information on a completed TagScript process. 24 | * If you are creating a parser where you need to store info in actions, 25 | * then you need to extend this interface. 26 | * 27 | * ```ts showLineNumbers 28 | * import 'tagscript'; 29 | * declare module 'tagscript' { 30 | * interface IActions { 31 | * foo?: string; 32 | * } 33 | * } 34 | * ``` 35 | */ 36 | public actions: IActions; 37 | 38 | public keyValues: IKeyValues; 39 | 40 | public constructor(variables: { [key: string]: ITransformer } = {}, keyValues: IKeyValues = {}) { 41 | this.body = null; 42 | this.actions = {}; 43 | this.variables = variables; 44 | this.keyValues = keyValues; 45 | } 46 | 47 | public setValues(output: string, raw: string) { 48 | if (this.body === null) this.body = output.trim(); 49 | else this.body = this.body.trim(); 50 | 51 | this.raw = raw; 52 | return this; 53 | } 54 | 55 | public toJSON() { 56 | return { 57 | body: this.body, 58 | raw: this.raw, 59 | actions: this.actions, 60 | variables: this.variables, 61 | keyValues: this.keyValues 62 | }; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Interpreter/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Context'; 2 | export * from './Interpreter'; 3 | export * from './Lexer'; 4 | export * from './Node'; 5 | export * from './Response'; 6 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Parsers/Base.ts: -------------------------------------------------------------------------------- 1 | import type { Context } from '../Interpreter'; 2 | 3 | /** 4 | * A base class for all transformers. 5 | * 6 | */ 7 | export abstract class BaseParser { 8 | protected acceptedNames: string[]; 9 | 10 | protected requiredParameter: boolean; 11 | 12 | protected requiredPayload: boolean; 13 | 14 | public constructor(acceptedNames: string[], requiredParameter = false, requiredPayload = false) { 15 | this.acceptedNames = acceptedNames; 16 | this.requiredParameter = requiredParameter; 17 | this.requiredPayload = requiredPayload; 18 | } 19 | 20 | public willAccept(ctx: Context): boolean { 21 | const dec = ctx.tag.declaration?.toLowerCase(); 22 | return ( 23 | this.acceptedNames.includes(dec!) && 24 | Boolean(!this.requiredParameter || ctx.tag.parameter) && 25 | Boolean(!this.requiredPayload || ctx.tag.payload) 26 | ); 27 | } 28 | 29 | public toJSON() { 30 | return { 31 | acceptedNames: this.acceptedNames, 32 | requiredParameter: this.requiredParameter, 33 | requiredPayload: this.requiredPayload 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Parsers/Break.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser } from './Base'; 2 | 3 | import { parseIf } from '../Utils/Util'; 4 | 5 | import type { IParser } from '../interfaces'; 6 | import type { Context } from '../Interpreter'; 7 | 8 | /** 9 | * The break parser will force the tag output to only be the payload of this tag, if the passed 10 | * expression evaluates true. 11 | * If no message is provided to the payload, the tag output will be empty. 12 | * This differs from the `StopParser` as the stop tag stops all TagScript parsing and returns 13 | * its message while the break tag continues to parse tags. If any other tags exist after 14 | * the break tag, they will still execute. 15 | * 16 | * @example 17 | * ```yaml 18 | * {break(expression):message} 19 | * ``` 20 | * @example 21 | * ```yaml 22 | * {break({args}==):You did not provide any input.} 23 | * ``` 24 | */ 25 | export class BreakParser extends BaseParser implements IParser { 26 | public constructor() { 27 | super(['break']); 28 | } 29 | 30 | public parse(ctx: Context) { 31 | if (parseIf(ctx.tag.parameter!)) ctx.response.body = ctx.tag.payload ?? ''; 32 | 33 | return ''; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Parsers/Define.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser } from './Base'; 2 | 3 | import { StringTransformer } from '../Transformer'; 4 | 5 | import type { IParser } from '../interfaces'; 6 | import type { Context } from '../Interpreter'; 7 | 8 | /** 9 | * Variables are useful for choosing a value and referencing it later in a tag. 10 | * It can be referenced using brackets as any other tag. 11 | * 12 | * Aliases: assign, let, var 13 | * 14 | * @example 15 | * ```yaml 16 | * {=(name):value} 17 | * ``` 18 | * @example 19 | * ```yaml 20 | * {=(prefix):!} 21 | * The prefix here is `{prefix}`. 22 | * ``` 23 | */ 24 | export class DefineParser extends BaseParser implements IParser { 25 | public constructor() { 26 | super(['=', 'assign', 'let', 'var'], true); 27 | } 28 | 29 | public parse(ctx: Context) { 30 | ctx.response.variables[ctx.tag.parameter!] = new StringTransformer(ctx.tag.payload!); 31 | return ''; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Parsers/FiftyFifty.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser } from './Base'; 2 | 3 | import type { IParser } from '../interfaces'; 4 | import type { Context } from '../Interpreter'; 5 | 6 | /** 7 | * The fifty-fifty tag has a 50% change of returning the payload, and 50% chance of returning empty string. 8 | * 9 | * @example 10 | * ```yaml 11 | * {5050:message} 12 | * ``` 13 | * Aliases: 50, ? 14 | * @example 15 | * ```yaml 16 | * I pick {if({5050:.}!=):heads|tails} 17 | * ``` 18 | */ 19 | export class FiftyFiftyParser extends BaseParser implements IParser { 20 | public constructor() { 21 | super(['5050', '50', '?'], false, true); 22 | } 23 | 24 | public parse(ctx: Context) { 25 | const spl = ['', ctx.tag.payload]; 26 | 27 | const index = Math.floor(Math.random() * spl.length); 28 | return spl[index]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Parsers/Format.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser } from './Base'; 2 | 3 | import { escapeContent } from '../Utils/Util'; 4 | 5 | import type { IParser } from '../interfaces'; 6 | import type { Context } from '../Interpreter'; 7 | 8 | /** 9 | * This tag formats a given string. 10 | * 11 | * @example 12 | * ```yaml 13 | * {lower:Hello Parbez!} 14 | * # hello parbez! 15 | * {upper:Hello Parbez!} 16 | * # HELLO PARBEZ! 17 | * {capitalize:hello parbez!} 18 | * # Hello parbez! 19 | * {escape:Hello| Parbez!} 20 | * # Hello\\| Parbez! 21 | * ``` 22 | */ 23 | export class StringFormatParser extends BaseParser implements IParser { 24 | public constructor() { 25 | super(['lower', 'upper', 'capitalize', 'escape'], false, true); 26 | } 27 | 28 | public parse(ctx: Context) { 29 | const { declaration, payload } = ctx.tag; 30 | switch (declaration as 'capitalize' | 'escape' | 'lower' | 'upper') { 31 | case 'lower': 32 | return payload!.toLowerCase(); 33 | case 'upper': 34 | return payload!.toUpperCase(); 35 | case 'capitalize': 36 | return payload!.charAt(0).toUpperCase() + payload!.slice(1).toLowerCase(); 37 | case 'escape': 38 | return escapeContent(payload!); 39 | } 40 | } 41 | } 42 | 43 | export class OrdinalFormatParser extends BaseParser implements IParser { 44 | public constructor() { 45 | super(['ordinal', 'ord'], false, true); 46 | } 47 | 48 | public parse(ctx: Context) { 49 | const { payload } = ctx.tag; 50 | if (Number.isNaN(Number(payload))) return payload; 51 | const lastDigit = payload!.slice(-1); 52 | const suffix = lastDigit === '1' ? 'st' : lastDigit === '2' ? 'nd' : lastDigit === '3' ? 'rd' : 'th'; 53 | return `${payload}${suffix}`; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Parsers/Includes.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser } from './Base'; 2 | 3 | import type { IParser } from '../interfaces'; 4 | import type { Context } from '../Interpreter'; 5 | /** 6 | * This tag serves four different purposes depending on the alias that is used. 7 | * The `in` and `includes` alias checks if the parameter is anywhere in the payload. 8 | * `contain` strictly checks if the parameter is the payload, split by whitespace. 9 | * `index` finds the location of the parameter in the payload, split by whitespace. 10 | * `lindex` finds the location of the parameter in the payload. 11 | * 12 | * Aliases: includes, in, index, lindex, contain, 13 | * 14 | * @example 15 | * ```yaml 16 | * {in(there):Hi there!} 17 | * # true 18 | * {contain(there):Hi there!} 19 | * # false 20 | * {contain(there!):Hi there!} 21 | * # true 22 | * {index(there):Hi there!} 23 | * # 1 24 | * {lindex(t):Hi there!} 25 | * # 4 26 | * ``` 27 | */ 28 | export class IncludesParser extends BaseParser implements IParser { 29 | public constructor() { 30 | super(['includes', 'in', 'contain', 'index', 'lindex'], true, true); 31 | } 32 | 33 | public parse(ctx: Context) { 34 | const dec = ctx.tag.declaration!.toLowerCase(); 35 | if (['includes', 'in'].includes(dec)) return `${ctx.tag.payload!.includes(ctx.tag.parameter!)}`; 36 | 37 | if (dec === 'contain') { 38 | const index = ctx.tag.payload!.split(/\s+/).includes(ctx.tag.parameter!); 39 | return `${index}`; 40 | } 41 | 42 | if (dec === 'index') { 43 | const index = ctx.tag.payload!.split(/\s+/).indexOf(ctx.tag.parameter!); 44 | return `${index}`; 45 | } 46 | 47 | return `${ctx.tag.payload!.indexOf(ctx.tag.parameter!)}`; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Parsers/JSONVar.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser } from './Base'; 2 | 3 | import { SafeObjectTransformer } from '../Transformer'; 4 | 5 | import type { IParser } from '../interfaces'; 6 | import type { Context } from '../Interpreter'; 7 | 8 | /** 9 | * JSON is useful when using fetch. You can get all the properties of a JSON object using parameters. 10 | * 11 | * @example 12 | * ```yaml 13 | * {json(name):value} 14 | * ``` 15 | * @example 16 | * ```yaml 17 | * {json(data):{"name": "John Doe", "age": 30}} 18 | * Your age is `{data.age}`. 19 | * ``` 20 | */ 21 | export class JSONVarParser extends BaseParser implements IParser { 22 | public constructor() { 23 | super(['json'], true, true); 24 | } 25 | 26 | public parse(ctx: Context) { 27 | ctx.response.variables[ctx.tag.parameter!] = new SafeObjectTransformer(ctx.tag.payload!); 28 | return ''; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Parsers/LooseVars.ts: -------------------------------------------------------------------------------- 1 | import type { IParser } from '../interfaces'; 2 | import type { Context } from '../Interpreter'; 3 | 4 | /** 5 | * The loose variable tag represents the transformer for any seeded or defined variables. 6 | * This variable implementation is considered `loose` since it checks whether the variable is 7 | * valid during `parsing`, rather than `willAccept`. 8 | * 9 | * @example 10 | * ```yaml 11 | * # Note:- this example assumes you are using define parser 12 | * {=(var):This is my variable.} 13 | * {var} 14 | * # This is my variable. 15 | * ``` 16 | */ 17 | export class LooseVarsParser implements IParser { 18 | public willAccept() { 19 | return true; 20 | } 21 | 22 | public parse(ctx: Context) { 23 | const declaration = ctx.tag.declaration!; 24 | if (declaration in ctx.response.variables) return ctx.response.variables[declaration].transform(ctx.tag); 25 | return null; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Parsers/Random.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser } from './Base'; 2 | 3 | import { split } from '../Utils/Util'; 4 | 5 | import type { IParser } from '../interfaces'; 6 | import type { Context } from '../Interpreter'; 7 | 8 | /** 9 | * Pick a random item from a list of strings, split by either `~` or `,`. 10 | * 11 | * Aliases: rand 12 | * 13 | * @example 14 | * ```yaml 15 | * {random:foo, bar} 16 | * ``` 17 | */ 18 | export class RandomParser extends BaseParser implements IParser { 19 | public constructor() { 20 | super(['random', 'rand'], false, true); 21 | } 22 | 23 | public parse(ctx: Context) { 24 | const spl = split(ctx.tag.payload!, true); 25 | 26 | const index = Math.floor(Math.random() * spl.length); 27 | return spl[index]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Parsers/Range.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser } from './Base'; 2 | 3 | import type { IParser } from '../interfaces'; 4 | import type { Context } from '../Interpreter'; 5 | 6 | /** 7 | * The range tag picks a random number from a range of numbers separated by `-`. 8 | * The number range is inclusive, so it can pick the starting/ending number as well. 9 | * Using the rangef tag will pick a number to the tenth decimal place. 10 | * 11 | * Aliases: rangef 12 | * 13 | * @example 14 | * ```yaml 15 | * {range(1-3)} 16 | * ``` 17 | * @example 18 | * ```yaml 19 | * Your lucky number is {range:10-30}! 20 | * # Your lucky number is 14! 21 | * # Your lucky number is 25! 22 | * {=(height):{rangef:5-7}} 23 | * I am guessing your height is {height}ft. 24 | * # I am guessing your height is 5.3ft. 25 | * ``` 26 | */ 27 | export class RangeParser extends BaseParser implements IParser { 28 | public constructor() { 29 | super(['rangef', 'range'], false, true); 30 | } 31 | 32 | public parse(ctx: Context) { 33 | const spl = ctx.tag.payload?.includes('-') ? ctx.tag.payload.split('-') : ctx.tag.payload!.split(','); 34 | if (ctx.tag.declaration!.toLowerCase() === 'rangef') { 35 | const lower = Number.parseFloat(spl[0]); 36 | const upper = Number.parseFloat(spl[1]); 37 | const base = Math.floor(Math.random() * (upper * 10 - lower * 10) + lower * 10) / 10; 38 | return `${base}`; 39 | } 40 | 41 | const lower = Number.parseInt(spl[0], 10); 42 | const upper = Number.parseInt(spl[1], 10); 43 | const base = Math.floor(Math.random() * (upper - lower) + lower); 44 | return `${base}`; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Parsers/Replace.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser } from './Base'; 2 | 3 | import type { IParser } from '../interfaces'; 4 | import type { Context } from '../Interpreter'; 5 | 6 | /** 7 | * The replace tag will replace specific characters in a string. 8 | * The parameter should split by a `,`, containing the characters to find 9 | * before the command and the replacements after. 10 | * 11 | * @example 12 | * ```yaml 13 | * {replace(o,i):welcome to the server} 14 | * # welcime ti the server 15 | * {replace(1,6):{args}} 16 | * # if {args} is 1637812 17 | * # 6637862 18 | * {replace(, ):Test} 19 | * # T e s t 20 | * ``` 21 | */ 22 | export class ReplaceParser extends BaseParser implements IParser { 23 | public constructor() { 24 | super(['replace'], true, true); 25 | } 26 | 27 | public parse(ctx: Context) { 28 | const [before, ...rest] = ctx.tag.parameter!.split(','); 29 | const after = rest.join(','); 30 | 31 | return ctx.tag.payload!.replaceAll(before, after); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Parsers/Slice.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser } from './Base'; 2 | 3 | import type { IParser } from '../interfaces'; 4 | import type { Context } from '../Interpreter'; 5 | 6 | /** 7 | * Parser for the `slice` tag. 8 | * This tag will take a string and return a substring of it. 9 | * 10 | * Aliases: slice, substr, substring 11 | * 12 | * @example 13 | * ```yaml 14 | * {slice(3): Hello World} 15 | * {slice(3, 7): Hello World} 16 | * {slice(3-7): Hello World} 17 | * ``` 18 | */ 19 | export class SliceParser extends BaseParser implements IParser { 20 | public constructor() { 21 | super(['slice', 'substr', 'substring'], true, true); 22 | } 23 | 24 | public parse(ctx: Context) { 25 | if (!ctx.tag.parameter!.includes('-') && !ctx.tag.parameter!.includes(',')) 26 | return ctx.tag.payload!.slice(Number.parseInt(ctx.tag.parameter!, 10)); 27 | 28 | const spl = ctx.tag.parameter!.includes('-') ? ctx.tag.parameter!.split('-') : ctx.tag.parameter!.split(','); 29 | const start = Number.parseInt(spl[0], 10); 30 | const end = Number.parseInt(spl[1], 10); 31 | return ctx.tag.payload!.slice(start, end); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Parsers/Stop.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser } from './Base'; 2 | 3 | import { parseIf } from '../Utils/Util'; 4 | 5 | import type { IParser } from '../interfaces'; 6 | import type { Context } from '../Interpreter'; 7 | 8 | /** 9 | * The stop tag stops tag processing if the given parameter is true. 10 | * If a message is passed to the payload it will return that message. 11 | * 12 | * Aliases: halt, stop 13 | * 14 | * @example 15 | * ```yaml 16 | * {stop({args}!=10):You didn't provided valid input.} 17 | * ``` 18 | */ 19 | export class StopParser extends BaseParser implements IParser { 20 | public constructor() { 21 | super(['stop', 'halt', 'error'], true); 22 | } 23 | 24 | public parse(ctx: Context) { 25 | if (parseIf(ctx.tag.parameter!)) throw new Error(ctx.tag.payload ?? ''); 26 | return ''; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Parsers/StrictVars.ts: -------------------------------------------------------------------------------- 1 | import type { IParser } from '../interfaces'; 2 | import type { Context } from '../Interpreter'; 3 | 4 | /** 5 | * The strict variable tag represents the transformer for any seeded or defined variables. 6 | * This variable implementation is considered `strict` since it checks whether the variable is 7 | * valid during `willAccept` and is only parsed if the declaration refers to a valid 8 | * variable. 9 | * 10 | * @example 11 | * ```yaml 12 | * # Note:- this example assumes you are using define parser 13 | * {=(var):This is my variable.} 14 | * {var} 15 | * # This is my variable. 16 | * ``` 17 | */ 18 | export class StrictVarsParser implements IParser { 19 | public willAccept(ctx: Context) { 20 | const declaration = ctx.tag.declaration!; 21 | return declaration in ctx.response.variables; 22 | } 23 | 24 | public parse(ctx: Context) { 25 | return ctx.response.variables[ctx.tag.declaration!].transform(ctx.tag); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Parsers/UrlEncoded.ts: -------------------------------------------------------------------------------- 1 | import { BaseParser } from './Base'; 2 | 3 | import type { IParser } from '../interfaces'; 4 | import type { Context } from '../Interpreter'; 5 | 6 | /** 7 | * This tag will encode a given string into a properly formatted url 8 | * with non-url compliant characters replaced. Using `+` as the parameter 9 | * will replace spaces with `+` rather than `%20`. 10 | * 11 | * Aliases: - encodeuri 12 | * 13 | * @example 14 | * ```yaml 15 | * {urlencode:Hello World} 16 | * ``` 17 | */ 18 | export class UrlEncodeParser extends BaseParser implements IParser { 19 | public constructor() { 20 | super(['urlencode', 'encodeuri'], false, true); 21 | } 22 | 23 | public parse(ctx: Context) { 24 | return ctx.tag.parameter === '+' ? encodeURI(ctx.tag.payload!.replaceAll(/ +/g, ctx.tag.parameter)) : encodeURI(ctx.tag.payload!); 25 | } 26 | } 27 | 28 | /** 29 | * This tag will decode a given url into a string 30 | * with non-url compliant characters replaced. 31 | * Using `+` as the parameter will replace `+` with space. 32 | * 33 | * @example 34 | * ```yaml 35 | * {urldecode:Hello%20World} 36 | * ``` 37 | */ 38 | export class UrlDecodeParser extends BaseParser implements IParser { 39 | public constructor() { 40 | super(['urldecode'], false, true); 41 | } 42 | 43 | public parse(ctx: Context) { 44 | return ctx.tag.parameter === '+' ? decodeURI(ctx.tag.payload!.replaceAll(ctx.tag.parameter, ' ')) : decodeURI(ctx.tag.payload!); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Parsers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Base'; 2 | export * from './Break'; 3 | export * from './Control'; 4 | export * from './Define'; 5 | export * from './FiftyFifty'; 6 | export * from './Format'; 7 | export * from './Includes'; 8 | export * from './JSONVar'; 9 | export * from './LooseVars'; 10 | export * from './Random'; 11 | export * from './Range'; 12 | export * from './Replace'; 13 | export * from './Slice'; 14 | export * from './Stop'; 15 | export * from './StrictVars'; 16 | export * from './UrlEncoded'; 17 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Transformer/Function.ts: -------------------------------------------------------------------------------- 1 | import type { ITransformer } from '../interfaces'; 2 | import type { Lexer } from '../Interpreter'; 3 | 4 | export type TransformerFunction = (tag: Lexer) => string; 5 | 6 | export class FunctionTransformer implements ITransformer { 7 | private readonly fn: TransformerFunction; 8 | 9 | public constructor(fn: TransformerFunction) { 10 | this.fn = fn; 11 | } 12 | 13 | public transform(tag: Lexer) { 14 | return this.fn(tag); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Transformer/Integer.ts: -------------------------------------------------------------------------------- 1 | import type { ITransformer } from '../interfaces'; 2 | import type { Lexer } from '../Interpreter'; 3 | 4 | /** 5 | * Integer transformer transforms an integer based on the given parameters. 6 | * 7 | * If no parameters are given, the integer will be returned as is. 8 | * 9 | * if `++` parameter is given, the integer will be incremented. 10 | * if `--` parameter is given, the integer will be decremented. 11 | */ 12 | export class IntegerTransformer implements ITransformer { 13 | private integer: number; 14 | 15 | public constructor(int: `${bigint | number}`) { 16 | this.integer = Number.parseInt(int, 10); 17 | } 18 | 19 | public transform(tag: Lexer) { 20 | if (tag.parameter === '++') return `${++this.integer}`; 21 | 22 | if (tag.parameter === '--') return `${--this.integer}`; 23 | 24 | return `${this.integer}`; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Transformer/Object.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-base-to-string */ 2 | import type { ITransformer } from '../interfaces'; 3 | import type { Lexer } from '../Interpreter'; 4 | 5 | /** 6 | * Object transformer safely transforms an object by removing all the methods (except toString), private properties and based on the given parameters. 7 | */ 8 | export class SafeObjectTransformer implements ITransformer { 9 | private readonly obj: Record; 10 | 11 | public constructor(obj: Record | string) { 12 | this.obj = this.makeObject(obj); 13 | } 14 | 15 | public transform(tag: Lexer) { 16 | if (tag.parameter === null) return `${this.obj}`; 17 | if (tag.parameter.startsWith('_')) return null; 18 | 19 | const attribute = this.getValue(this.obj, tag.parameter); 20 | return attribute ? `${attribute}` : null; 21 | } 22 | 23 | private getValue(obj: Record, key: string) { 24 | if (key in obj) return obj[key]; 25 | if (!key.includes('.')) return null; 26 | const keys = key.split('.'); 27 | let value = obj; 28 | 29 | for (const key of keys) { 30 | if (typeof value !== 'object') return null; 31 | value = value[key] as Record; 32 | } 33 | 34 | return value; 35 | } 36 | 37 | private makeObject(obj: Record | string) { 38 | const safeObject = JSON.parse(typeof obj === 'string' ? obj : JSON.stringify(obj)) as Record; 39 | Object.defineProperty(safeObject, 'toString', { 40 | value: obj.toString.bind(obj) 41 | }); 42 | return safeObject; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Transformer/String.ts: -------------------------------------------------------------------------------- 1 | import { escapeContent } from '../Utils/Util'; 2 | 3 | import type { ITransformer } from '../interfaces'; 4 | import type { Lexer } from '../Interpreter'; 5 | 6 | /** 7 | * String transformer transforms a string based on the given parameters. 8 | * 9 | * If no parameters are given, the string will be returned as is. 10 | * If an integer parameter is given, the string will be splitted into an array of strings using payload or space as a separator. 11 | * And will return the element at the given index (integer parameter). 12 | * 13 | * Use a `+` before the index to reference every element up to and including the index value. 14 | * 15 | * Use a `+` after the index to reference the index value and every element after it. 16 | * 17 | * @remarks 18 | * You need to use `StrictVarsParser` parser to use this transformer. 19 | * @example 20 | * ```ts showLineNumbers 21 | * import { Interpreter, StringTransformer, StrictVarsParser } from 'tagscript'; 22 | * 23 | * const ts = new Interpreter(new StrictVarsParser()); 24 | * 25 | * await ts.run('{args}', { args: new StringTransformer('Hi, How are you?') }); 26 | * // Hi, How are you? 27 | * 28 | * await ts.run('{args(0)}', { args: new StringTransformer('Hi, How are you?') }); 29 | * // Hi, 30 | * 31 | * await ts.run('{args(1)}', { args: new StringTransformer('Hi, How are you?') }); 32 | * // How 33 | * 34 | * await ts.run('{args(2+)}', { args: new StringTransformer('Hi, How are you?') }); 35 | * // How are you? 36 | * 37 | * await ts.run('{args(+2)}', { args: new StringTransformer('Hi Vox, How are you?') }); 38 | * // Hi Vox, 39 | * ``` 40 | */ 41 | export class StringTransformer implements ITransformer { 42 | private readonly str: string; 43 | 44 | private readonly escape: boolean; 45 | 46 | /** 47 | * 48 | * @param str - The string to transform. 49 | * @param escape - If true, the string will be escaped. 50 | */ 51 | public constructor(str: string, escape = false) { 52 | this.str = str; 53 | this.escape = escape; 54 | } 55 | 56 | public transform(tag: Lexer) { 57 | return this.returnValue(this.handleContext(tag)); 58 | } 59 | 60 | private handleContext(tag: Lexer): string { 61 | if (tag.parameter === null) return this.str; 62 | if (!tag.parameter.includes('+')) { 63 | const index = Number.parseInt(tag.parameter, 10) - 1; 64 | const splitter = tag.payload ?? / +/; 65 | return this.str.split(splitter)[index] ?? this.str; 66 | } 67 | 68 | const index = Number.parseInt(tag.parameter.replace('+', ''), 10) - 1; 69 | const splitter = tag.payload ?? / +/; 70 | if (tag.parameter.startsWith('+')) { 71 | return this.str 72 | .split(splitter) 73 | .slice(0, index + 1) 74 | .join(tag.payload ?? ' '); 75 | } else if (tag.parameter.endsWith('+')) { 76 | return this.str 77 | .split(splitter) 78 | .slice(index) 79 | .join(tag.payload ?? ' '); 80 | } 81 | 82 | return this.str.split(splitter)[index] ?? this.str; 83 | } 84 | 85 | private returnValue(str: string): string { 86 | return this.escape ? escapeContent(str) : str; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Transformer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Function'; 2 | export * from './Integer'; 3 | export * from './Object'; 4 | export * from './String'; 5 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/Utils/Util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a type that may or may not be a promise 3 | * 4 | * @typeParam T - The type of the value 5 | */ 6 | export type Awaitable = PromiseLike | T; 7 | 8 | export const splitRegex = /(?[():{|}])/g; 10 | 11 | /** 12 | * 13 | * Identical to {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter | Array.filter()} but works with async filter functions. 14 | * 15 | * @param values - The array of values 16 | * @param fn - The filter function 17 | * @returns 18 | * @typeParam T - The type of the values 19 | */ 20 | export const asyncFilter = async (values: T[], fn: (t: T) => Awaitable) => { 21 | const promises = values.map(fn); 22 | const booleans = await Promise.all(promises); 23 | return values.filter((_, index) => booleans[index]); 24 | }; 25 | 26 | /** 27 | * 28 | * Escapes special tagscript syntax in a string. 29 | * 30 | * @param content - The content to escape 31 | * @returns 32 | */ 33 | export const escapeContent = (content: string): string => content.replaceAll(escapeRegex, '\\$1'); 34 | 35 | /** 36 | * 37 | * Checks if the given value is `'true'` or `'false'`. If it is, it returns the boolean value. Otherwise, it returns null 38 | * 39 | * @param str - The string to check 40 | * @returns 41 | */ 42 | export const implicitBool = (str: string) => { 43 | const booleans = { 44 | true: true, 45 | false: false 46 | }; 47 | const lower = str.toLowerCase(); 48 | return lower in booleans ? booleans[lower as keyof typeof booleans] : null; 49 | }; 50 | 51 | /** 52 | * 53 | * Parses an if statement. Returns a value based on the conditions. 54 | * 55 | * @param str - The string to parse 56 | * @returns 57 | */ 58 | export const parseIf = (str: string) => { 59 | const value = implicitBool(str); 60 | if (value !== null) return value; 61 | if (str.includes('!=')) { 62 | const spl = str.split('!='); 63 | return spl[0].trim() !== spl[1].trim(); 64 | } 65 | 66 | if (str.includes('==')) { 67 | const spl = str.split('=='); 68 | return spl[0].trim() === spl[1].trim(); 69 | } 70 | 71 | if (str.includes('>=')) { 72 | const spl = str.split('>='); 73 | return Number.parseFloat(spl[0].trim()) >= Number.parseFloat(spl[1].trim()); 74 | } 75 | 76 | if (str.includes('<=')) { 77 | const spl = str.split('<='); 78 | return Number.parseFloat(spl[0].trim()) <= Number.parseFloat(spl[1].trim()); 79 | } 80 | 81 | if (str.includes('>')) { 82 | const spl = str.split('>'); 83 | return Number.parseFloat(spl[0].trim()) > Number.parseFloat(spl[1].trim()); 84 | } 85 | 86 | if (str.includes('<')) { 87 | const spl = str.split('<'); 88 | return Number.parseFloat(spl[0].trim()) < Number.parseFloat(spl[1].trim()); 89 | } 90 | 91 | return true; 92 | }; 93 | 94 | /** 95 | * 96 | * Split a string by `|`, but ignore escaped characters. 97 | * 98 | * @remarks 99 | * If extended if true, it will also split by `~` and `,` 100 | * @param str - The string to split 101 | * @param extended - allow `~` or `,` as separators 102 | * @returns 103 | */ 104 | export const split = (str: string, extended = false): string[] => { 105 | if (extended && str.includes('~')) return str.split('~'); 106 | if (extended && str.includes(',')) return str.split(','); 107 | if (str.includes('|')) return str.split(splitRegex); 108 | return [str]; 109 | }; 110 | 111 | /** 112 | * 113 | * Parses multiple if statements from a string. 114 | * 115 | * @param str - The string to parse 116 | * @returns 117 | */ 118 | export const parseListIf = (str: string) => { 119 | const splitted = split(str, false); 120 | return splitted.map((node) => parseIf(node)); 121 | }; 122 | -------------------------------------------------------------------------------- /packages/tagscript/src/lib/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | import type { Context, Lexer } from '../Interpreter'; 2 | import type { Awaitable } from '../Utils/Util'; 3 | 4 | /** 5 | * Transformers are used to transform a value based on the tag at runtime. 6 | */ 7 | export interface ITransformer { 8 | /** 9 | * Transforms the given tag. 10 | * 11 | * @param tag - The tag that triggered the transformer. 12 | */ 13 | transform: (tag: Lexer) => string | null; 14 | } 15 | 16 | /** 17 | * Parsers are used to parse a tag and return a value based on the tag. 18 | */ 19 | export interface IParser { 20 | /** 21 | * Parses the given tag. 22 | * 23 | * @param ctx - The context of the tag. 24 | */ 25 | parse: (ctx: Context) => Awaitable; 26 | /** 27 | * Whether the parser can handle the given tag. 28 | * 29 | * @param ctx - The context of the tag. 30 | */ 31 | willAccept: (ctx: Context) => Awaitable; 32 | } 33 | 34 | /** 35 | * This is used to store key-value pairs and can be passed to `Interpreter` for parsers to use. 36 | * It is empty by default and so has type `{}`. 37 | * If you want to use it, you need to use [Augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) to add your own properties. 38 | */ 39 | // eslint-disable-next-line @typescript-eslint/no-empty-object-type 40 | export interface IKeyValues {} 41 | 42 | /** 43 | * This is used to store actions data by to parsers for later use by developers. 44 | * If you want to add more actions to it, you need to use [Augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation). 45 | */ 46 | export interface IActions { 47 | deny?: { ids: string[]; message: string | null }; 48 | require?: { ids: string[]; message: string | null }; 49 | } 50 | -------------------------------------------------------------------------------- /packages/tagscript/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./", 5 | "outDir": "../dist", 6 | "incremental": false 7 | }, 8 | "include": ["."] 9 | } 10 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Interpreter/Context.test.ts: -------------------------------------------------------------------------------- 1 | import { Context, Interpreter, Lexer, Response } from '../../src'; 2 | 3 | describe('Context', () => { 4 | const lexer = new Lexer('{tag}'); 5 | const interpreter = new Interpreter(); 6 | const response = new Response(); 7 | const context = new Context(lexer, response, interpreter, '{tag}'); 8 | 9 | test('GIVEN a context THEN Context#toJSON() should return all the props', () => { 10 | expect(context.toJSON()).toEqual({ 11 | tag: lexer, 12 | originalMessage: '{tag}', 13 | interpreter, 14 | response 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Interpreter/Interpreter.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter, Response, IfStatementParser, StringTransformer, DefineParser, StrictVarsParser } from '../../src'; 2 | 3 | const ts = new Interpreter(new IfStatementParser(), new DefineParser(), new StrictVarsParser()); 4 | describe('Interpreter', () => { 5 | test.each(['Parbez', '{test}', '{hi(hello)}', '{a.b}'])('GIVEN a string THEN returns the string', async (input) => { 6 | expect(await ts.run(input)).toStrictEqual(new Response().setValues(input, input)); 7 | }); 8 | 9 | test('GIVEN a string with length greater than character limit THEN throws an error', async () => { 10 | const input = '{if({args}==63):You guessed it! The number I was thinking of was 63!|Too {if({args}<63):low|high}, try again.}'; 11 | await expect(ts.run(input, { args: new StringTransformer('60') }, 1)).rejects.toThrowError( 12 | new Error('The TS interpreter had its workload exceeded. The total characters attempted were 2/1') 13 | ); 14 | }); 15 | 16 | test('GIVEN a string with length less than given character limit THEN returns the result', async () => { 17 | const input = 'Parbez'; 18 | expect(await ts.run(input, {}, 7)).toStrictEqual(new Response().setValues(input, input)); 19 | }); 20 | 21 | test('GIVEN parser at construction or using method THEN store them at parsers property', () => { 22 | const tagscript = new Interpreter(new IfStatementParser()); 23 | 24 | // eslint-disable-next-line @typescript-eslint/dot-notation 25 | expect(tagscript['parsers']).toHaveLength(1); 26 | 27 | tagscript.addParsers(new DefineParser()); 28 | // eslint-disable-next-line @typescript-eslint/dot-notation 29 | expect(tagscript['parsers']).toHaveLength(2); 30 | 31 | tagscript.setParsers(new DefineParser()); 32 | // eslint-disable-next-line @typescript-eslint/dot-notation 33 | expect(tagscript['parsers']).toHaveLength(1); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Interpreter/Node.test.ts: -------------------------------------------------------------------------------- 1 | import { Node } from '../../src'; 2 | 3 | describe('Node', () => { 4 | const node = new Node([0, 1], null); 5 | 6 | test('GIVEN a node THEN Node#toString() should return a string with start, end info', () => { 7 | expect(node.toString()).toEqual(' at 0-1'); 8 | }); 9 | 10 | test('GIVEN a node THEN Node#toJSON() should return all the props', () => { 11 | expect(node.toJSON()).toEqual({ coordinates: [0, 1], output: null, tag: null }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Interpreter/Response.test.ts: -------------------------------------------------------------------------------- 1 | import { Response } from '../../src'; 2 | 3 | describe('Response', () => { 4 | const response = new Response().setValues('Hello World', '{Hello World}'); 5 | 6 | test('GIVEN a value in setValues THEN return the same values in toJSON', () => { 7 | expect(response.toJSON()).toStrictEqual({ 8 | body: 'Hello World', 9 | raw: '{Hello World}', 10 | actions: {}, 11 | keyValues: {}, 12 | variables: {} 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Interpreter/parenType.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter, ReplaceParser, ParenType, Response } from '../../src'; 2 | 3 | const ts = new Interpreter(new ReplaceParser()); 4 | describe('ParenType', () => { 5 | test('GIVEN a string with dot param in param type dot THEN parse as dot param', async () => { 6 | expect(await ts.run('{replace.Mahrin,Mahir:Hi Mahrin}', {}, null, 2_000, ParenType.Dot)).toStrictEqual( 7 | new Response().setValues('Hi Mahir', '{replace.Mahrin,Mahir:Hi Mahrin}') 8 | ); 9 | }); 10 | 11 | test("GIVEN a string with dot param in param type parenthesis THEN don't parse as dot param", async () => { 12 | expect(await ts.run('{replace.Mahrin,Mahir:Hi Mahrin}', {}, null, 2_000, ParenType.Parenthesis)).not.toStrictEqual( 13 | new Response().setValues('Hi Mahir', '{replace.Mahrin,Mahir:Hi Mahrin}') 14 | ); 15 | }); 16 | 17 | test("GIVEN a string in both param type THEN don't mix the parsing", async () => { 18 | expect(await ts.run('{replace.Mahrin,Mahir):Hi Mahrin}')).not.toStrictEqual( 19 | new Response().setValues('Hi Mahir', '{replace.Mahrin,Mahir):Hi Mahrin}') 20 | ); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Parsers/Break.test.ts: -------------------------------------------------------------------------------- 1 | import { BreakParser, Interpreter } from '../../src'; 2 | 3 | describe('BreakParser', () => { 4 | const ts = new Interpreter(new BreakParser()); 5 | 6 | test('GIVEN a predefined input then using break THEN reset the output body', async () => { 7 | expect((await ts.run('Hi, {break(12==12):Hello World}')).body).toStrictEqual('Hello World'); 8 | expect((await ts.run('Hi, {break(false):Hello World}')).body).toStrictEqual('Hi,'); 9 | expect((await ts.run('Hi, {break(true)}')).body).toStrictEqual(''); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Parsers/Control.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DefineParser, 3 | IfStatementParser, 4 | Interpreter, 5 | IntersectionStatementParser, 6 | Response, 7 | StrictVarsParser, 8 | StringTransformer, 9 | UnionStatementParser 10 | } from '../../src'; 11 | 12 | describe('IntersectionStatementParser', () => { 13 | const ts = new Interpreter(new IntersectionStatementParser()); 14 | test('GIVEN a string in intersection parser THEN returns the string if all conditions are true else return empty string', async () => { 15 | const text1 = '{all(10>=100|10<=1000):You picked 10.|You must provide a number between 100 and 1000.}'; 16 | expect(await ts.run(text1)).toStrictEqual(new Response().setValues('You must provide a number between 100 and 1000.', text1)); 17 | 18 | const text2 = '{all(282>=100|282<=1000):You picked 282.|You must provide a number between 100 and 1000.}'; 19 | expect(await ts.run(text2)).toStrictEqual(new Response().setValues('You picked 282.', text2)); 20 | }); 21 | }); 22 | 23 | describe('UnionStatementParser', () => { 24 | const ts = new Interpreter(new UnionStatementParser()); 25 | test('GIVEN a string in union parser THEN returns the string if any conditions are true else return empty string', async () => { 26 | const text1 = '{any(hi==hi|hi==hello|hi==heyy):Hello!|How rude.}'; 27 | expect(await ts.run(text1)).toStrictEqual(new Response().setValues('Hello!', text1)); 28 | 29 | const text2 = "{any(what's up==hi|what's up==hello|what's up==hey):Hello!|How rude.}"; 30 | expect(await ts.run(text2)).toStrictEqual(new Response().setValues('How rude.', text2)); 31 | }); 32 | }); 33 | 34 | describe('IfStatementParser', () => { 35 | const ts = new Interpreter(new IfStatementParser(), new DefineParser(), new StrictVarsParser()); 36 | 37 | test('GIVEN a string in union parser THEN returns the string if any conditions are true else return empty string', async () => { 38 | const text1 = '{if(63==63):You guessed it! The number I was thinking of was 63!|Too {if(63<63):low|high}, try again.}'; 39 | expect(await ts.run(text1)).toStrictEqual(new Response().setValues('You guessed it! The number I was thinking of was 63!', text1)); 40 | 41 | const text2 = '{if(73==63):You guessed it! The number I was thinking of was 63!|Too {if(73<63):low|high}, try again.}'; 42 | expect(await ts.run(text2)).toStrictEqual(new Response().setValues('Too high, try again.', text2)); 43 | 44 | const text3 = '{if(14==63):You guessed it! The number I was thinking of was 63!|Too {if(14<63):low|high}, try again.}'; 45 | expect(await ts.run(text3)).toStrictEqual(new Response().setValues('Too low, try again.', text3)); 46 | 47 | const text4 = '{=(a):{if(52==63):You guessed it! The number I was thinking of was 63!}}{a}'; 48 | expect(await ts.run(text4)).toStrictEqual( 49 | new Response({ 50 | a: new StringTransformer('') 51 | }).setValues('', text4) 52 | ); 53 | 54 | const text5 = '{=(a):{if(63==63):You guessed it! The number I was thinking of was 63!}}{a}'; 55 | expect(await ts.run(text5)).toStrictEqual( 56 | new Response({ 57 | a: new StringTransformer('You guessed it! The number I was thinking of was 63!') 58 | }).setValues('You guessed it! The number I was thinking of was 63!', text5) 59 | ); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Parsers/Define.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter, DefineParser, LooseVarsParser, Response, StringTransformer, StrictVarsParser } from '../../src'; 2 | 3 | describe('DefineParser', () => { 4 | describe('LooseVarsParser', () => { 5 | const ts = new Interpreter(new DefineParser(), new LooseVarsParser()); 6 | test('GIVEN a string in define parser THEN returns the value instead of the variable', async () => { 7 | const text = '{=(user):mahir} {user} {ok}'; 8 | 9 | expect(await ts.run(text)).toStrictEqual( 10 | new Response({ 11 | user: new StringTransformer('mahir') 12 | }).setValues('mahir {ok}', text) 13 | ); 14 | }); 15 | }); 16 | 17 | describe('StrictVarsParser', () => { 18 | const ts = new Interpreter(new DefineParser(), new StrictVarsParser()); 19 | test('GIVEN a string in define parser THEN returns the value instead of the variable', async () => { 20 | const text1 = '{=(user):mahir} {user} {ok}'; 21 | expect(await ts.run(text1)).toStrictEqual( 22 | new Response({ 23 | user: new StringTransformer('mahir') 24 | }).setValues('mahir {ok}', text1) 25 | ); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Parsers/FiftyFifty.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter, FiftyFiftyParser } from '../../src'; 2 | 3 | const ts = new Interpreter(new FiftyFiftyParser()); 4 | describe('FiftyFiftyParser', () => { 5 | test('GIVEN a string in 5050 parser THEN returns the string or an empty string', async () => { 6 | // eslint-disable-next-line prefer-named-capture-group 7 | expect((await ts.run('{5050:user}')).body).toMatch(/(^user$|^$)/); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Parsers/Format.test.ts: -------------------------------------------------------------------------------- 1 | import { StringFormatParser, Interpreter, Response, OrdinalFormatParser } from '../../src'; 2 | 3 | describe('FormatParser', () => { 4 | test('GIVEN a string in format parser THEN return formatted string', async () => { 5 | const ts = new Interpreter(new StringFormatParser()); 6 | const text1 = '{lower:Hello Parbez!}'; 7 | expect(await ts.run(text1)).toStrictEqual(new Response().setValues('hello parbez!', text1)); 8 | 9 | const text2 = '{upper:Hello Parbez!}'; 10 | expect(await ts.run(text2)).toStrictEqual(new Response().setValues('HELLO PARBEZ!', text2)); 11 | 12 | const text3 = '{capitalize:hello parbez!}'; 13 | expect(await ts.run(text3)).toStrictEqual(new Response().setValues('Hello parbez!', text3)); 14 | 15 | const text4 = '{capitalize:HELLO}'; 16 | expect(await ts.run(text4)).toStrictEqual(new Response().setValues('Hello', text4)); 17 | 18 | const text5 = '{escape:Hello| Parbez!}'; 19 | expect(await ts.run(text5)).toStrictEqual(new Response().setValues('Hello\\| Parbez!', text5)); 20 | 21 | const text6 = '{anything:Hello| Parbez!}'; 22 | expect(await ts.run(text6)).toStrictEqual(new Response().setValues('{anything:Hello| Parbez!}', text6)); 23 | }); 24 | }); 25 | 26 | describe('OrdinalFormatParser', () => { 27 | test('GIVEN a string in ordinal format parser THEN return formatted string', async () => { 28 | const ts = new Interpreter(new OrdinalFormatParser()); 29 | expect(await ts.run('{ordinal:1}')).toStrictEqual(new Response().setValues('1st', '{ordinal:1}')); 30 | expect(await ts.run('{ordinal:2}')).toStrictEqual(new Response().setValues('2nd', '{ordinal:2}')); 31 | expect(await ts.run('{ordinal:3}')).toStrictEqual(new Response().setValues('3rd', '{ordinal:3}')); 32 | expect(await ts.run('{ordinal:4}')).toStrictEqual(new Response().setValues('4th', '{ordinal:4}')); 33 | expect(await ts.run('{ordinal:101}')).toStrictEqual(new Response().setValues('101st', '{ordinal:101}')); 34 | expect(await ts.run('{ordinal:1002}')).toStrictEqual(new Response().setValues('1002nd', '{ordinal:1002}')); 35 | expect(await ts.run('{ordinal:hello}')).toStrictEqual(new Response().setValues('hello', '{ordinal:hello}')); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Parsers/Includes.test.ts: -------------------------------------------------------------------------------- 1 | import { IncludesParser, Interpreter, Response } from '../../src'; 2 | 3 | const ts = new Interpreter(new IncludesParser()); 4 | describe('IncludesParser', () => { 5 | test('GIVEN a string THEN check if a substring exist in that string', async () => { 6 | const text1 = '{in(hi):Hello Parbez!}'; 7 | expect(await ts.run(text1)).toStrictEqual(new Response().setValues('false', text1)); 8 | 9 | const text2 = '{includes(Parbez):Hello Parbez!}'; 10 | expect(await ts.run(text2)).toStrictEqual(new Response().setValues('true', text2)); 11 | }); 12 | 13 | test('GIVEN a string THEN strictly check if a substring exist in that string', async () => { 14 | const text1 = '{contain(hi):Hello Parbez!}'; 15 | expect(await ts.run(text1)).toStrictEqual(new Response().setValues('false', text1)); 16 | 17 | const text2 = '{contain(Parbez!):Hello Parbez!}'; 18 | expect(await ts.run(text2)).toStrictEqual(new Response().setValues('true', text2)); 19 | 20 | const text3 = '{contain(Parbez):Hello Parbez!}'; 21 | expect(await ts.run(text3)).toStrictEqual(new Response().setValues('false', text3)); 22 | }); 23 | 24 | test('GIVEN a string THEN give index of the parameter from payload slitted with space', async () => { 25 | const text1 = '{index(hi):Hello Parbez!}'; 26 | expect(await ts.run(text1)).toStrictEqual(new Response().setValues('-1', text1)); 27 | 28 | const text2 = '{index(Parbez!):Hello Parbez!}'; 29 | expect(await ts.run(text2)).toStrictEqual(new Response().setValues('1', text2)); 30 | }); 31 | 32 | test('GIVEN a string THEN give index of the parameter from payload', async () => { 33 | const text1 = '{lindex(hi):Hello Parbez!}'; 34 | expect(await ts.run(text1)).toStrictEqual(new Response().setValues('-1', text1)); 35 | 36 | const text2 = '{lindex(Parbez):Hello Parbez!}'; 37 | expect(await ts.run(text2)).toStrictEqual(new Response().setValues('6', text2)); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Parsers/JSONVar.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter, JSONVarParser, Response, SafeObjectTransformer, StrictVarsParser } from '../../src'; 2 | 3 | describe('JSONVar', () => { 4 | test('GIVEN a JSON in json var THEN store it as an object and show results using the parameter', async () => { 5 | const ts = new Interpreter(new JSONVarParser(), new StrictVarsParser()); 6 | 7 | const text = '{json(data):{"name": "John Doe", "age": 30}}'; 8 | 9 | expect(await ts.run(text)).toStrictEqual( 10 | new Response({ 11 | data: new SafeObjectTransformer('{"name": "John Doe", "age": 30}') 12 | }).setValues('', text) 13 | ); 14 | 15 | const text1 = `${text}{data.name}`; 16 | 17 | expect(await ts.run(text1)).toStrictEqual( 18 | new Response({ 19 | data: new SafeObjectTransformer('{"name": "John Doe", "age": 30}') 20 | }).setValues('John Doe', text1) 21 | ); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Parsers/Random.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter, RandomParser } from '../../src'; 2 | 3 | const ts = new Interpreter(new RandomParser()); 4 | describe('RandomParser', () => { 5 | test('GIVEN some choices separated with comma in random parser THEN returns a random string from the payload', async () => { 6 | expect((await ts.run('{random:rkn,priyansh,kashish} won the game')).body).toMatch(/(?:rkn|priyansh|kashish) won the game/); 7 | }); 8 | 9 | test('GIVEN some choices separated with ~ in random parser THEN returns a random string from the payload', async () => { 10 | expect((await ts.run('{random:rkn~priyansh~kashish} won the game')).body).toMatch(/(?:rkn|priyansh|kashish) won the game/); 11 | }); 12 | 13 | test('GIVEN some choices separated with ~ as well as comma in random parser THEN respect ~ and ignore ,', async () => { 14 | expect((await ts.run('{random:rkn,priyansh~kashish} won the game')).body).toMatch(/(?:rkn,priyansh|kashish) won the game/); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Parsers/Range.test.ts: -------------------------------------------------------------------------------- 1 | import { RangeParser, Interpreter } from '../../src'; 2 | 3 | describe('RangeParser', () => { 4 | const ts = new Interpreter(new RangeParser()); 5 | 6 | test('GIVEN a number random in range tag THEN return a random number from the given numbers', async () => { 7 | expect((await ts.run('{range:1, 12}')).body).toMatch(/^1|2|3|4|5|6|7|8|9|10|11|12$/); 8 | expect((await ts.run('{rangef:1.5-3}')).body).toMatch(/^[1-3](?:.\d)?$/); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Parsers/Replace.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter, ReplaceParser, Response } from '../../src'; 2 | 3 | const ts = new Interpreter(new ReplaceParser()); 4 | describe('ReplaceParser', () => { 5 | test('GIVEN a string in replace parser THEN replace one with another and returns the string', async () => { 6 | expect(await ts.run('{replace(Mahrin,Mahir):Hi Mahrin}')).toStrictEqual( 7 | new Response().setValues('Hi Mahir', '{replace(Mahrin,Mahir):Hi Mahrin}') 8 | ); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Parsers/Slice.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter, SliceParser } from '../../src'; 2 | 3 | describe('SliceParser', () => { 4 | const ts = new Interpreter(new SliceParser()); 5 | 6 | test('GIVEN a string and a start and end index THEN returns a substring of the string', async () => { 7 | expect((await ts.run('{slice(3): Hello World}')).body).toStrictEqual('llo World'); 8 | expect((await ts.run('{slice(3,5): Hello World}')).body).toStrictEqual('ll'); 9 | expect((await ts.run('{slice(2-6): Hello World}')).body).toStrictEqual('ello'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Parsers/Stop.test.ts: -------------------------------------------------------------------------------- 1 | import { StopParser, Interpreter } from '../../src'; 2 | 3 | describe('StopParser', () => { 4 | const ts = new Interpreter(new StopParser()); 5 | 6 | test('GIVEN a predefined input then using stop THEN reset the complete output body', async () => { 7 | expect((await ts.run('Hi, {stop(12==12):Hello World}')).body).toStrictEqual('Hi, Hello World'); 8 | expect((await ts.run('Hi, {stop(valid):Hello World} Hi')).body).toStrictEqual('Hi, Hello World'); 9 | expect((await ts.run('Hi, {stop(true)}')).body).toStrictEqual('Hi,'); 10 | expect((await ts.run('}Hi, {stop(false):Hello World}')).body).toStrictEqual('}Hi,'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Parsers/UrlEncoded.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter, Response, UrlDecodeParser, UrlEncodeParser } from '../../src'; 2 | 3 | const ts = new Interpreter(new UrlEncodeParser(), new UrlDecodeParser()); 4 | 5 | describe('UrlEncodedParser', () => { 6 | test('GIVEN a string in UrlEncode parser THEN returns the urlencoded string', async () => { 7 | const text = '{urlencode:This is Rkn}'; 8 | expect(await ts.run(text)).toStrictEqual(new Response().setValues('This%20is%20Rkn', text)); 9 | }); 10 | 11 | test('GIVEN a string in UrlEncode parser with + param THEN returns the urlencoded string by replacing space with +', async () => { 12 | const text = '{urlencode(+):This is Rkn}'; 13 | expect(await ts.run(text)).toStrictEqual(new Response().setValues('This+is+Rkn', text)); 14 | }); 15 | }); 16 | 17 | describe('UrlDecodedParser', () => { 18 | test('GIVEN a string in UrlDecode parser THEN returns the urldecoded string', async () => { 19 | const text = '{urldecode:This%20is%20Rkn}'; 20 | expect(await ts.run(text)).toStrictEqual(new Response().setValues('This is Rkn', text)); 21 | }); 22 | 23 | test('GIVEN a string in UrlDecode parser with + param THEN returns the urldecoded string by replacing + with space', async () => { 24 | const text = '{urldecode(+):This+is+Rkn}'; 25 | expect(await ts.run(text)).toStrictEqual(new Response().setValues('This is Rkn', text)); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Transformer/Function.test.ts: -------------------------------------------------------------------------------- 1 | import { FunctionTransformer, Lexer } from '../../src'; 2 | 3 | describe('FunctionTransformer', () => { 4 | test('GIVEN a string in as a variable THEN returns the value instead of the variable', () => { 5 | expect(new FunctionTransformer((tag) => `${tag.declaration}: Hello World`).transform(new Lexer('{value}'))).toStrictEqual( 6 | 'value: Hello World' 7 | ); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Transformer/Integer.test.ts: -------------------------------------------------------------------------------- 1 | import { IntegerTransformer, Interpreter, Response, StrictVarsParser } from '../../src'; 2 | 3 | describe('IntegerTransformer', () => { 4 | test('GIVEN a string in as a variable THEN returns the value instead of the variable', async () => { 5 | const ts = new Interpreter(new StrictVarsParser()); 6 | expect( 7 | await ts.run('{number}', { 8 | number: new IntegerTransformer('5') 9 | }) 10 | ).toStrictEqual( 11 | new Response({ 12 | number: new IntegerTransformer('5') 13 | }).setValues('5', '{number}') 14 | ); 15 | }); 16 | 17 | test('GIVEN a string in as a variable with parameter ++ THEN returns the value by incrementing it', async () => { 18 | const ts = new Interpreter(new StrictVarsParser()); 19 | const text = '{number(++)}'; 20 | const variables = { 21 | number: new IntegerTransformer('4') 22 | }; 23 | expect(await ts.run(text, variables)).toStrictEqual(new Response(variables).setValues('5', text)); 24 | }); 25 | 26 | test('GIVEN a string in as a variable with parameter -- THEN returns the value by decrementing it', async () => { 27 | const ts = new Interpreter(new StrictVarsParser()); 28 | const text = '{number(--)}'; 29 | const variables = { 30 | number: new IntegerTransformer('4') 31 | }; 32 | expect(await ts.run(text, variables)).toStrictEqual(new Response(variables).setValues('3', text)); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Transformer/Object.test.ts: -------------------------------------------------------------------------------- 1 | import { SafeObjectTransformer, Interpreter, Response, StrictVarsParser } from '../../src'; 2 | 3 | describe('SafeObjectTransformer', () => { 4 | test('GIVEN a string in as a variable THEN returns the value instead of the variable', async () => { 5 | const ts = new Interpreter(new StrictVarsParser()); 6 | 7 | await expect( 8 | ts.run('{obj}', { 9 | obj: new SafeObjectTransformer({ toString: () => '5' }) 10 | }) 11 | ).resolves.toStrictEqual( 12 | new Response({ 13 | obj: new SafeObjectTransformer({ toString: () => '5' }) 14 | }).setValues('5', '{obj}') 15 | ); 16 | 17 | await expect( 18 | ts.run('{obj.name}', { 19 | obj: new SafeObjectTransformer({ name: '5' }) 20 | }) 21 | ).resolves.toStrictEqual( 22 | new Response({ 23 | obj: new SafeObjectTransformer({ name: '5' }) 24 | }).setValues('5', '{obj.name}') 25 | ); 26 | }); 27 | 28 | test('GIVEN an object with private properties THEN filter them out', async () => { 29 | const ts = new Interpreter(new StrictVarsParser()); 30 | await expect( 31 | ts.run('{obj._name}', { 32 | obj: new SafeObjectTransformer({ _name: '5' }) 33 | }) 34 | ).resolves.toStrictEqual( 35 | new Response({ 36 | obj: new SafeObjectTransformer({ _name: '5' }) 37 | }).setValues('{obj._name}', '{obj._name}') 38 | ); 39 | }); 40 | 41 | test('GIVEN an object with methods THEN filter them out', async () => { 42 | const ts = new Interpreter(new StrictVarsParser()); 43 | await expect( 44 | ts.run('{obj.get}', { 45 | obj: new SafeObjectTransformer({ get: () => '5' }) 46 | }) 47 | ).resolves.toStrictEqual( 48 | new Response({ 49 | obj: new SafeObjectTransformer({ get: () => '5' }) 50 | }).setValues('{obj.get}', '{obj.get}') 51 | ); 52 | }); 53 | 54 | test('GIVEN an object with an invalid key THEN return parameter', async () => { 55 | const ts = new Interpreter(new StrictVarsParser()); 56 | await expect( 57 | ts.run('{obj.name}', { 58 | obj: new SafeObjectTransformer({ age: '5' }) 59 | }) 60 | ).resolves.toStrictEqual( 61 | new Response({ 62 | obj: new SafeObjectTransformer({ age: '5' }) 63 | }).setValues('{obj.name}', '{obj.name}') 64 | ); 65 | }); 66 | 67 | test('GIVEN an object with nested key THEN return the value', async () => { 68 | const ts = new Interpreter(new StrictVarsParser()); 69 | await expect( 70 | ts.run('{obj.name.first}', { 71 | obj: new SafeObjectTransformer({ name: { first: '5' } }) 72 | }) 73 | ).resolves.toStrictEqual( 74 | new Response({ 75 | obj: new SafeObjectTransformer({ name: { first: '5' } }) 76 | }).setValues('5', '{obj.name.first}') 77 | ); 78 | 79 | await expect( 80 | ts.run('{obj.name.first.second}', { 81 | obj: new SafeObjectTransformer({ name: { first: { second: '5' } } }) 82 | }) 83 | ).resolves.toStrictEqual( 84 | new Response({ 85 | obj: new SafeObjectTransformer({ name: { first: { second: '5' } } }) 86 | }).setValues('5', '{obj.name.first.second}') 87 | ); 88 | 89 | await expect( 90 | ts.run('{obj.name.first.second.third}', { 91 | obj: new SafeObjectTransformer({ name: { first: { second: { third: '5' } } } }) 92 | }) 93 | ).resolves.toStrictEqual( 94 | new Response({ 95 | obj: new SafeObjectTransformer({ name: { first: { second: { third: '5' } } } }) 96 | }).setValues('5', '{obj.name.first.second.third}') 97 | ); 98 | }); 99 | 100 | test('GIVEN an object with an invalid nested key THEN return null', async () => { 101 | const ts = new Interpreter(new StrictVarsParser()); 102 | await expect( 103 | ts.run('{obj.name.first.second}', { 104 | obj: new SafeObjectTransformer({ name: { first: '5' } }) 105 | }) 106 | ).resolves.toStrictEqual( 107 | new Response({ 108 | obj: new SafeObjectTransformer({ name: { first: '5' } }) 109 | }).setValues('{obj.name.first.second}', '{obj.name.first.second}') 110 | ); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /packages/tagscript/tests/Transformer/String.test.ts: -------------------------------------------------------------------------------- 1 | import { Interpreter, Response, StrictVarsParser, StringTransformer } from '../../src'; 2 | 3 | describe('StringTransformer', () => { 4 | test('GIVEN a string in as a variable THEN returns the value instead of the variable', async () => { 5 | const ts = new Interpreter(new StrictVarsParser()); 6 | expect( 7 | await ts.run('{user}', { 8 | user: new StringTransformer('mahir') 9 | }) 10 | ).toStrictEqual( 11 | new Response({ 12 | user: new StringTransformer('mahir') 13 | }).setValues('mahir', '{user}') 14 | ); 15 | }); 16 | 17 | test('GIVEN a string in as a variable with parameter number THEN returns the value of the variable by splitting with payload and returns the parameter - 1 part', async () => { 18 | const ts = new Interpreter(new StrictVarsParser()); 19 | const text = '{user(2)}'; 20 | const variables = { 21 | user: new StringTransformer('Hello World') 22 | }; 23 | expect(await ts.run(text, variables)).toStrictEqual(new Response(variables).setValues('World', text)); 24 | 25 | const text2 = '{user(2):W}'; 26 | expect(await ts.run(text2, variables)).toStrictEqual(new Response(variables).setValues('orld', text2)); 27 | 28 | const text3 = '{user(10)}'; 29 | expect(await ts.run(text3, variables)).toStrictEqual(new Response(variables).setValues('Hello World', text3)); 30 | }); 31 | 32 | test('GIVEN a string in as a variable with parameter number+ or +number THEN returns the value of the variable by splitting with payload and returns the + part by skipping the number part', async () => { 33 | const ts = new Interpreter(new StrictVarsParser()); 34 | const text = '{user(+2)}'; 35 | const variables = { 36 | user: new StringTransformer('Hello World. Hello World.') 37 | }; 38 | expect(await ts.run(text, variables)).toStrictEqual(new Response(variables).setValues('Hello World.', text)); 39 | 40 | const text2 = '{user(2+)}'; 41 | expect(await ts.run(text2, variables)).toStrictEqual(new Response(variables).setValues('World. Hello World.', text2)); 42 | 43 | const text3 = '{user(2+3)}'; 44 | expect(await ts.run(text3, variables)).toStrictEqual(new Response(variables).setValues('Hello World. Hello World.', text3)); 45 | }); 46 | 47 | test('GIVEN a string in StringTransformer with escape true THEN escape the string', async () => { 48 | const ts = new Interpreter(new StrictVarsParser()); 49 | const text = '{user}'; 50 | const variables = { 51 | user: new StringTransformer('Parbez|Barbhuiya', true) 52 | }; 53 | expect(await ts.run(text, variables)).toStrictEqual(new Response(variables).setValues('Parbez\\|Barbhuiya', text)); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /packages/tagscript/tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "include": ["."], 4 | "compilerOptions": { 5 | "types": ["vitest/globals"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/tagscript/tsconfig.typecheck.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*.ts", "tests/**/*.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/tagscript/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { createTsupConfig } from '../../scripts/tsup.config'; 2 | 3 | export default createTsupConfig({ globalName: 'TagScript' }); 4 | -------------------------------------------------------------------------------- /packages/tagscript/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["./src/index.ts"], 3 | "readme": "./README.md", 4 | "tsconfig": "./src/tsconfig.json", 5 | "externalLinkPath": "../../scripts/externalConfig.mjs", 6 | "additionalModuleSources": [] 7 | } 8 | -------------------------------------------------------------------------------- /packages/tagscript/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { createVitestConfig } from '../../scripts/vitest.config'; 2 | 3 | export default createVitestConfig(); 4 | -------------------------------------------------------------------------------- /scripts/clean-full.mjs: -------------------------------------------------------------------------------- 1 | import { rm } from 'node:fs/promises'; 2 | import { URL } from 'node:url'; 3 | 4 | const rootDir = new URL('../', import.meta.url); 5 | const packagesDir = new URL('packages/', rootDir); 6 | const options = { recursive: true, force: true }; 7 | 8 | const paths = [ 9 | // Root node_modules 10 | new URL('node_modules/', rootDir), 11 | 12 | // Nested node_modules folders 13 | new URL('tagscript/node_modules/', packagesDir), 14 | new URL('tagscript-plugin-discord/node_modules/', packagesDir), 15 | 16 | // Dist folders 17 | new URL('tagscript/dist/', packagesDir), 18 | new URL('tagscript-plugin-discord/dist/', packagesDir) 19 | ]; 20 | 21 | await Promise.all(paths.map((path) => rm(path, options))); 22 | -------------------------------------------------------------------------------- /scripts/clean.mjs: -------------------------------------------------------------------------------- 1 | import { rm } from 'node:fs/promises'; 2 | import { URL } from 'node:url'; 3 | 4 | const rootDir = new URL('../', import.meta.url); 5 | const packagesDir = new URL('packages/', rootDir); 6 | const options = { recursive: true, force: true }; 7 | 8 | const paths = [ 9 | // Dist folders 10 | new URL('tagscript/dist/', packagesDir), 11 | new URL('tagscript-plugin-discord/dist/', packagesDir), 12 | 13 | // Turbo folders 14 | new URL('tagscript/.turbo/', packagesDir), 15 | new URL('tagscript-plugin-discord/.turbo/', packagesDir) 16 | ]; 17 | 18 | await Promise.all(paths.map((path) => rm(path, options))); 19 | -------------------------------------------------------------------------------- /scripts/externalConfig.mjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable tsdoc/syntax */ 2 | const packageNames = ['discord.js', 'discord-api-types']; 3 | 4 | /** 5 | * 6 | * @type {import('typedoc-plugin-external-link').getURL} 7 | */ 8 | function getURL(packageName, type) { 9 | if (!type && packageName === 'discord.js') return 'https://discordjs.dev/'; 10 | if (!type) return `https://www.npmjs.com/package/${packageName}`; 11 | switch (type) { 12 | case 'BaseChannel': 13 | return 'https://old.discordjs.dev/#/docs/discord.js/main/class/BaseChannel'; 14 | case 'ChatInputCommandInteraction': 15 | return 'https://old.discordjs.dev/#/docs/discord.js/main/class/ChatInputCommandInteraction'; 16 | case 'Client': 17 | return 'https://old.discordjs.dev/#/docs/discord.js/main/class/Client'; 18 | case 'CommandInteraction': 19 | return 'https://old.discordjs.dev/#/docs/discord.js/main/class/CommandInteraction'; 20 | case 'CommandInteractionOptionResolver': 21 | return 'https://old.discordjs.dev/#/docs/discord.js/main/class/CommandInteractionOptionResolver'; 22 | case 'Guild': 23 | return 'https://old.discordjs.dev/#/docs/discord.js/main/class/Guild'; 24 | case 'GuildMember': 25 | return 'https://old.discordjs.dev/#/docs/discord.js/main/class/GuildMember'; 26 | case 'Role': 27 | return 'https://old.discordjs.dev/#/docs/discord.js/main/class/Role'; 28 | case 'TextChannel': 29 | return 'https://old.discordjs.dev/#/docs/discord.js/main/class/TextChannel'; 30 | case 'User': 31 | return 'https://old.discordjs.dev/#/docs/discord.js/main/class/User'; 32 | 33 | case 'CommandInteractionOption': 34 | return 'https://old.discordjs.dev/#/docs/discord.js/main/typedef/CommandInteractionOption'; 35 | 36 | case 'CacheType': 37 | return 'https://github.com/discordjs/discord.js/blob/4d8361c711df423f154a7460939c60f6d9429105/packages/discord.js/typings/index.d.ts#L1474'; 38 | case 'Channel': 39 | return 'https://github.com/discordjs/discord.js/blob/4d8361c711df423f154a7460939c60f6d9429105/packages/discord.js/typings/index.d.ts#L5433'; 40 | case 'EmbedData': 41 | return 'https://github.com/discordjs/discord.js/blob/4d8361c711df423f154a7460939c60f6d9429105/packages/discord.js/typings/index.d.ts#L663'; 42 | 43 | case 'ApplicationCommandOptionType': 44 | return 'https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationCommandOptionType'; 45 | case 'ApplicationCommandType': 46 | return 'https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationCommandType'; 47 | case 'InteractionType': 48 | return 'https://discord-api-types.dev/api/discord-api-types-v10/enum/InteractionType'; 49 | 50 | case 'APIAttachment': 51 | return 'https://discord-api-types.dev/api/discord-api-types-v10/interface/APIAttachment'; 52 | case 'APIEmbed': 53 | return 'https://discord-api-types.dev/api/discord-api-types-v10/interface/APIEmbed'; 54 | case 'APIGuild': 55 | return 'https://discord-api-types.dev/api/discord-api-types-v10/interface/APIGuild'; 56 | case 'APIGuildMember': 57 | return 'https://discord-api-types.dev/api/discord-api-types-v10/interface/APIGuildMember'; 58 | case 'APIRole': 59 | return 'https://discord-api-types.dev/api/discord-api-types-v10/interface/APIRole'; 60 | 61 | case 'APIApplicationCommandInteraction': 62 | return 'https://discord-api-types.dev/api/discord-api-types-v10#APIApplicationCommandInteraction'; 63 | case 'APIChannel': 64 | return 'https://discord-api-types.dev/api/discord-api-types-v10#APIChannel'; 65 | } 66 | 67 | return undefined; 68 | } 69 | 70 | export { packageNames, getURL }; 71 | -------------------------------------------------------------------------------- /scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "module": "nodenext", 7 | "moduleResolution": "node16" 8 | }, 9 | "include": ["**/*.mjs", "**/*.js", "**/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /scripts/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { relative, resolve as resolveDir } from 'node:path'; 2 | import process from 'node:process'; 3 | 4 | import { defineConfig, type Options } from 'tsup'; 5 | 6 | export const createTsupConfig = (options: Options = {}) => 7 | defineConfig({ 8 | clean: true, 9 | dts: true, 10 | treeshake: true, 11 | entry: ['src/index.ts'], 12 | format: ['esm', 'cjs', 'iife'], 13 | minify: false, 14 | skipNodeModulesBundle: true, 15 | sourcemap: true, 16 | target: 'esnext', 17 | // eslint-disable-next-line unicorn/prefer-module 18 | tsconfig: relative(__dirname, resolveDir(process.cwd(), 'src', 'tsconfig.json')), 19 | keepNames: true, 20 | ...options 21 | }); 22 | -------------------------------------------------------------------------------- /scripts/uploadCoverage/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Upload Coverage' 2 | description: 'Uploads code coverage reports to codecov with separate flags for separate packages' 3 | runs: 4 | using: 'composite' 5 | steps: 6 | - name: Upload Tagscript Coverage 7 | uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5 8 | with: 9 | files: ./packages/tagscript/coverage/cobertura-coverage.xml 10 | flags: tagscript 11 | 12 | - name: Upload Tagscript Plugin Discord Coverage 13 | uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5 14 | with: 15 | files: ./packages/tagscript-plugin-discord/coverage/cobertura-coverage.xml 16 | flags: plugin-discord 17 | -------------------------------------------------------------------------------- /scripts/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export const createVitestConfig = () => 4 | defineConfig({ 5 | test: { 6 | globals: true, 7 | coverage: { 8 | reporter: ['text', 'lcov', 'cobertura'] 9 | } 10 | }, 11 | esbuild: { 12 | target: 'esnext' 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /scripts/yarnCache/action.yml: -------------------------------------------------------------------------------- 1 | name: 'yarn install' 2 | description: 'Run yarn install with node_modules linker and cache enabled' 3 | runs: 4 | using: 'composite' 5 | steps: 6 | - name: Expose yarn config as "$GITHUB_OUTPUT" 7 | id: yarn-config 8 | shell: bash 9 | run: | 10 | echo "CACHE_FOLDER=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT 11 | 12 | - name: Restore yarn cache 13 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 14 | id: yarn-download-cache 15 | with: 16 | path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }} 17 | key: yarn-download-cache-${{ hashFiles('yarn.lock') }} 18 | restore-keys: | 19 | yarn-download-cache- 20 | 21 | - name: Restore yarn install state 22 | id: yarn-install-state-cache 23 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 24 | with: 25 | path: .yarn/ci-cache/ 26 | key: ${{ runner.os }}-yarn-install-state-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }} 27 | 28 | - name: Install dependencies 29 | shell: bash 30 | run: | 31 | yarn install --immutable --inline-builds 32 | env: 33 | YARN_ENABLE_GLOBAL_CACHE: 'false' 34 | YARN_NM_MODE: 'hardlinks-local' 35 | YARN_INSTALL_STATE_PATH: .yarn/ci-cache/install-state.gz 36 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "commonjs", 5 | "strict": true, 6 | "skipLibCheck": true, 7 | "noImplicitAny": true, 8 | "strictNullChecks": true, 9 | "noImplicitThis": true, 10 | "alwaysStrict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "moduleResolution": "node", 16 | "declaration": true, 17 | "outDir": "dist" 18 | }, 19 | "includes": ["packages/**/*.ts"], 20 | "exclude": ["packages/**/dist/**"] 21 | } 22 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "tasks": { 4 | "build": { 5 | "dependsOn": ["^build"], 6 | "outputs": ["dist/**", ".next/**"], 7 | "env": ["VERCEL_URL"] 8 | }, 9 | "bump": { 10 | "dependsOn": [], 11 | "outputs": ["CHANGELOG.md"], 12 | "cache": false 13 | }, 14 | "check-update": { 15 | "dependsOn": [], 16 | "outputs": [] 17 | }, 18 | "format": { 19 | "outputs": [] 20 | }, 21 | "lint": { 22 | "dependsOn": ["^build"], 23 | "outputs": [".eslintcache"] 24 | }, 25 | "typecheck": { 26 | "dependsOn": ["^build"], 27 | "outputs": [] 28 | }, 29 | "test": { 30 | "dependsOn": ["^build"], 31 | "outputs": ["**/coverage/**"] 32 | }, 33 | "test:watch": { 34 | "dependsOn": ["^build"], 35 | "outputs": ["**/coverage/**"] 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://typedoc.org/schema.json", 3 | "cleanOutputDir": true, 4 | "entryPointStrategy": "packages", 5 | "entryPoints": ["packages/tagscript/", "packages/tagscript-plugin-discord/"], 6 | "name": "TagScript", 7 | "out": "./docs/", 8 | "tsconfig": "./tsconfig.json", 9 | "externalLinkPath": "./scripts/externalConfig.mjs", 10 | "hideGenerator": true, 11 | "hideBreadcrumbs": true, 12 | "publicPath": "./tagscript/", 13 | "plugin": ["typedoc-plugin-markdown", "typedoc-plugin-mdn-links", "typedoc-plugin-external-link"], 14 | "additionalModuleSources": [] 15 | } 16 | --------------------------------------------------------------------------------