├── .dockerignore ├── .github ├── dependabot.yml └── workflows │ ├── codeql.yml │ ├── dependency-review.yml │ ├── docker.yml │ └── mkdocs-build.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── README ├── 1.jpg ├── 11.png ├── 12.png ├── 13.png ├── 14.png ├── 15.png ├── 16.jpg ├── 2.jpg ├── 3.jpg ├── 4.jpg ├── 5.jpg ├── 6.jpg └── 7.jpg ├── config.json ├── discourse ├── CreatePosts.js ├── EventPosts.js ├── getCategories.js ├── getComment.js ├── get_latest_posts.js ├── index.js ├── sendComment.js └── sendMessagePrivate.js ├── docs ├── _headers ├── mkdocs.yml ├── overrides │ ├── .icons │ │ └── matrix.svg │ ├── extra.css │ └── main.html ├── requirements.txt └── source │ ├── images │ ├── 1.jpg │ ├── 11.png │ ├── 12.png │ ├── 13.png │ ├── 14.png │ ├── 15.png │ ├── 16.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ ├── 7.jpg │ ├── Discourse_Bridge.webp │ └── api-setup.png │ ├── index.md │ ├── installation.md │ ├── usage.md │ └── عربي │ ├── index.md │ ├── installation.md │ └── usage.md ├── index.js ├── matrix ├── EventPosts.js ├── EventReply.js ├── generate_matrix_token.js ├── index.js ├── menu │ ├── CreatePosts │ │ ├── CreatePosts_1.js │ │ ├── CreatePosts_2.js │ │ └── CreatePosts_3.js │ ├── activation.js │ ├── discourse │ │ ├── discourse_1.js │ │ └── discourse_2.js │ ├── main.js │ ├── sendComment │ │ ├── sendComment_1.js │ │ └── sendComment_2.js │ └── sendMessagePrivate │ │ ├── sendMessagePrivate_1.js │ │ ├── sendMessagePrivate_2.js │ │ └── sendMessagePrivate_3.js ├── sendFile.js └── start.js ├── module ├── Crate.js ├── database_matrix.js ├── database_telegram.js ├── getBuffer.js ├── getMenu.js ├── getUserTelegram.js ├── getrRoomMatrix.js ├── menu.js ├── random.js └── translation.js ├── package-lock.json ├── package.json ├── telegram ├── EventPosts_.js ├── EventText.js ├── WizardScene │ ├── CreatePosts.js │ ├── WizardScene.js │ ├── activation.js │ ├── discourse.js │ ├── sendComment.js │ └── sendMessagePrivate.js ├── command │ ├── CreatePosts.js │ ├── activation.js │ ├── discourse.js │ ├── getCategories.js │ ├── get_latest_posts.js │ ├── index.js │ ├── sendComment.js │ ├── sendMessagePrivate.js │ └── start.js ├── index.js └── join_left.js └── translation ├── ar.json └── en.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | Dockerfile 4 | .git 5 | .gitignore 6 | .dockerignore 7 | docs/ -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | paths-ignore: 18 | - 'docs/**' 19 | - '.github/**' 20 | - README.md 21 | - Dockerfile 22 | - docker-compose.yaml 23 | pull_request: 24 | # The branches below must be a subset of the branches above 25 | branches: [ "main" ] 26 | paths-ignore: 27 | - 'docs/**' 28 | - '.github/**' 29 | - README.md 30 | 31 | schedule: 32 | - cron: '22 0 * * 5' 33 | 34 | jobs: 35 | analyze: 36 | name: Analyze 37 | runs-on: ubuntu-latest 38 | permissions: 39 | actions: read 40 | contents: read 41 | security-events: write 42 | 43 | strategy: 44 | fail-fast: false 45 | matrix: 46 | language: [ 'javascript' ] 47 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 48 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 49 | 50 | steps: 51 | - name: Checkout repository 52 | uses: actions/checkout@v3 53 | 54 | # Initializes the CodeQL tools for scanning. 55 | - name: Initialize CodeQL 56 | uses: github/codeql-action/init@v2 57 | with: 58 | languages: ${{ matrix.language }} 59 | # If you wish to specify custom queries, you can do so here or in a config file. 60 | # By default, queries listed here will override any specified in a config file. 61 | # Prefix the list here with "+" to use these queries and those in the config file. 62 | 63 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 64 | # queries: security-extended,security-and-quality 65 | 66 | 67 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 68 | # If this step fails, then you should remove it and run the build manually (see below) 69 | - name: Autobuild 70 | uses: github/codeql-action/autobuild@v2 71 | 72 | # ℹ️ Command-line programs to run using the OS shell. 73 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 74 | 75 | # If the Autobuild fails above, remove it and uncomment the following three lines. 76 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 77 | 78 | # - run: | 79 | # echo "Run, Build Application using script" 80 | # ./location_of_script_within_repo/buildscript.sh 81 | 82 | - name: Perform CodeQL Analysis 83 | uses: github/codeql-action/analyze@v2 84 | with: 85 | category: "/language:${{matrix.language}}" 86 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 4 | # 5 | # Source repository: https://github.com/actions/dependency-review-action 6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 7 | name: 'Dependency Review' 8 | on: 9 | pull_request: 10 | paths-ignore: 11 | - 'docs/**' 12 | - '.github/**' 13 | - README.md 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | dependency-review: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: 'Checkout Repository' 23 | uses: actions/checkout@v3 24 | - name: 'Dependency Review' 25 | uses: actions/dependency-review-action@v2 26 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker build and publish 2 | 3 | # This workflow uses actions that are not certified by GitHub. 4 | # They are provided by a third-party and are governed by 5 | # separate terms of service, privacy policy, and support 6 | # documentation. 7 | 8 | on: 9 | workflow_dispatch: 10 | push: 11 | branches: [ main ] 12 | paths-ignore: 13 | - 'docs/**' 14 | - '.github/**' 15 | - README.md 16 | pull_request: 17 | branches: [ main ] 18 | paths-ignore: 19 | - 'docs/**' 20 | - '.github/**' 21 | - README.md 22 | 23 | jobs: 24 | build: 25 | runs-on: ubuntu-latest 26 | permissions: 27 | contents: read 28 | packages: write 29 | # This is used to complete the identity challenge 30 | # with sigstore/fulcio when running outside of PRs. 31 | id-token: write 32 | 33 | steps: 34 | - name: Checkout repository 35 | uses: actions/checkout@v3 36 | 37 | - name: Set up QEMU 38 | uses: docker/setup-qemu-action@v2 39 | 40 | # Workaround: https://github.com/docker/build-push-action/issues/461 41 | - name: Setup Docker buildx 42 | uses: docker/setup-buildx-action@v2 43 | 44 | # Login against a Docker registry except on PR 45 | # https://github.com/docker/login-action 46 | - name: Log into registry ghcr.io 47 | if: github.event_name != 'pull_request' 48 | uses: docker/login-action@v2 49 | with: 50 | registry: ghcr.io 51 | username: ${{ github.actor }} 52 | password: ${{ secrets.GITHUB_TOKEN }} 53 | 54 | # Extract metadata (tags, labels) for Docker 55 | # https://github.com/docker/metadata-action 56 | - name: Extract Docker metadata 57 | id: meta 58 | uses: docker/metadata-action@v3 59 | with: 60 | images: | 61 | ghcr.io/aosus/discourse-chat-bridge:latest 62 | 63 | # Build and push Docker image with Buildx (don't push on PR) 64 | # https://github.com/docker/build-push-action 65 | - name: Build and push Docker image 66 | id: build-and-push 67 | uses: docker/build-push-action@v3 68 | with: 69 | context: . 70 | platforms: linux/amd64, linux/arm64/v8 71 | push: ${{ github.event_name != 'pull_request' }} 72 | tags: | 73 | ghcr.io/aosus/discourse-chat-bridge:latest 74 | labels: ${{ steps.meta.outputs.labels }} 75 | cache-from: type=gha 76 | cache-to: type=gha,mode=max 77 | -------------------------------------------------------------------------------- /.github/workflows/mkdocs-build.yml: -------------------------------------------------------------------------------- 1 | name: Build Docs 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | paths: 7 | - 'docs/**' 8 | - '.github/workflows/mkdocs-build.yml' 9 | - 'docs/requirements.txt' 10 | branches: [ main ] 11 | pull_request: 12 | branches: [ main ] 13 | paths: 14 | - 'docs/**' 15 | - '.github/workflows/mkdocs-build.yml' 16 | - 'docs/requirements.txt' 17 | pull_request_review: 18 | types: [edited, dismissed] 19 | 20 | jobs: 21 | build-and-deploy: 22 | runs-on: ubuntu-latest 23 | permissions: 24 | contents: read 25 | deployments: write 26 | 27 | steps: 28 | - name: Checkout 🛎️ 29 | uses: actions/checkout@v3 30 | with: 31 | persist-credentials: false 32 | fetch-depth: '0' 33 | 34 | # setup python 35 | - name: Set up Python 3.11 36 | uses: actions/setup-python@v4 37 | with: 38 | python-version: 3.11.* 39 | 40 | - name: Install dependencies 41 | run: | 42 | python -m pip install --upgrade pip setuptools 43 | python -m pip install -U pip wheel 44 | python -m pip install -r docs/requirements.txt 45 | pip show mkdocs-material 46 | 47 | - name: mkdocs build 48 | run: mkdocs build -f docs/mkdocs.yml --clean --verbose -d generated 49 | 50 | - name: Include _headers file 51 | run: cp docs/_headers docs/generated/_headers 52 | 53 | - name: Publish to Cloudflare Pages 54 | if: github.event_name != 'pull_request' 55 | uses: cloudflare/pages-action@1 56 | with: 57 | apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} 58 | accountId: ${{ secrets.CLOUDFLARE_ID }} 59 | projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }} 60 | directory: docs/generated 61 | gitHubToken: ${{ secrets.GITHUB_TOKEN }} 62 | branch: main 63 | 64 | - name: Publish preview to Cloudflare Pages 65 | if: github.event_name == 'pull_request' 66 | uses: cloudflare/pages-action@1 67 | with: 68 | apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} 69 | accountId: ${{ secrets.CLOUDFLARE_ID }} 70 | projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }} 71 | directory: docs/generated 72 | gitHubToken: ${{ secrets.GITHUB_TOKEN }} 73 | branch: ${{ github.ref.name }} 74 | 75 | - name: Cache pip files 76 | uses: actions/cache@v3.0.11 77 | env: 78 | cache-name: pip-reg 79 | with: 80 | path: $HOME/.cache/pip 81 | key: pip-reg 82 | 83 | - name: Cache Mkdocs files 84 | uses: actions/cache@v3 85 | with: 86 | key: ${{ github.ref }} 87 | path: .cache -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | yarn.lock 2 | node_modules 3 | database 4 | storage 5 | preview.png 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | #https://snyk.io/blog/10-best-practices-to-containerize-nodejs-web-applications-with-docker/ 2 | # --------------> The build image 3 | FROM node:lts AS build 4 | RUN apt-get update && apt-get install -y --no-install-recommends dumb-init 5 | WORKDIR /app 6 | COPY package*.json /app 7 | RUN npm ci --only=production --omit=dev 8 | 9 | # --------------> The production image 10 | FROM node:lts-slim 11 | 12 | ENV NODE_ENV production 13 | COPY --from=build /usr/bin/dumb-init /usr/bin/dumb-init 14 | USER node 15 | WORKDIR /app 16 | COPY --chown=node:node --from=build /app/node_modules /app/node_modules 17 | COPY --chown=node:node . /app 18 | CMD ["dumb-init", "node", "index.js"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published by 637 | the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![discourse-chat-bridge](/README/Discourse_Bridge.png) 2 | 3 | 4 | # Discourse-chat-bridge 5 |
6 | جسر بين منصة Discourse وبرامج التواصل . 7 | 8 | - تيليجرام - مدعوم 9 | - matrix - مدعوم 10 | 11 |
12 | 13 | # مميزات الجسر 14 | 15 | - عرض آخر موضوع تم نشره 📄 16 | - عرض الفئات ⬇️ 17 | - كتابة موضوع جديد 📝 18 | - كتابة تعليق جديد 💬 19 | - إرسال رسالة خاصة 🔒 20 | - ربط حسابك على منصة Discourse 21 | - تفعيل البوت لتلقي آخر المواضيع المنشورة 22 | 23 |
24 | 25 | 26 | | الأمر | صورة| 27 | |:--------------|-----------------:| 28 | |start | ![discourse-chat-bridge](/README/16.jpg) | 29 | |get_latest_posts | ![discourse-chat-bridge](/README/6.jpg) | 30 | |getCategories | ![discourse-chat-bridge](/README/3.jpg) | 31 | |CreatePosts | ![discourse-chat-bridge](/README/2.jpg) | 32 | |sendComment | ![discourse-chat-bridge](/README/5.jpg) | 33 | |sendMessagePrivate | ![discourse-chat-bridge](/README/4.jpg) | 34 | |discourse | ![discourse-chat-bridge](/README/1.jpg) | 35 | |activation | ![discourse-chat-bridge](/README/7.jpg) | 36 | 37 |
38 | 39 | # تثبيت البوت 40 | 41 |
42 | 43 | **يجب عليك إنشاء مفتاح api عبر لوحة تحكم Discourse** 44 | 45 | ![11|328x402](/README/11.png) 46 | 47 |
48 | 49 | ![12|690x93](/README/12.png) 50 | 51 | ![13|690x193](/README/13.png) 52 | 53 | 54 | **`قم بإختيار جميع المستخدمين`** 55 | 56 | ![14|521x99](/README/14.png) 57 | 58 | **`قم بتحديدعلى صلاحية الكتابة`** 59 | 60 | ![15|690x123](/README/15.png) 61 | 62 | **`احفظ المفتاح في مكان آمن واضغط على متابعة`** 63 | 64 |
65 | 66 | بعد الإنتهاء من إنشاء مفتاح api قم بإستنساخ المستودع 67 | 68 | ```bash 69 | git clone https://github.com/aosus/discourse-chat-bridge 70 | 71 | ``` 72 | آلان قم بإضافة متغيرات البيئة 73 | - url 74 | - discourse_forum_name 75 | - discourse_token 76 | - discourse_username 77 | - telegram_token 78 | - matrix_username 79 | - matrix_password 80 | - matrix_homeserver_url 81 | - matrix_access_token 82 | - matrix_autojoin 83 | - dataPath 84 | - matrix_encryption 85 | - language 86 | 87 | مثال 88 | 89 | linux 90 | 91 | ```bash 92 | export url="https://discourse.aosus.org" 93 | ``` 94 | 95 | windows 96 | 97 | ```bash 98 | setx url="https://discourse.aosus.org" 99 | ``` 100 | 101 | أو قم بتعديل على ملف config.json 102 | ``` ملاحظة / الأولوية لمتغيرات البيئة اذا وجدت ``` 103 | 104 | 105 | ```bash 106 | cd discourse-chat-bridge 107 | nano config.json 108 | ``` 109 | 110 | ```json 111 | { 112 | "url": "https://$DISCOURSE_DOMAIN", 113 | "discourse_forum_name": "discourse forum name", 114 | "discourse_token": "discourse tokin", 115 | "discourse_username": "system", 116 | "telegram_token": "telegram token", 117 | "matrix_username": "Username to your Matrix account #aosus", 118 | "matrix_password": "Password to your Matrix account #*****", 119 | "matrix_homeserver_url": "https://matrix.org", 120 | "matrix_access_token": "Put your matrix_access_token here #npm run generate_matrix_token", 121 | "matrix_autojoin": true, 122 | "dataPath": "storage", 123 | "matrix_encryption": true, 124 | "language": "ar" 125 | } 126 | ``` 127 | 128 | بعد التعديل على ملف config.json قم بحفظه 129 | ثم قم بتثبيت التبعيات وتشغيل البوت 130 | 131 | ```bash 132 | npm i 133 | npm run generate_matrix_token 134 | npm start 135 | or 136 | node index.js 137 | ``` 138 | 139 | 140 | ``` ملاحظة / عند كتابة الأمر npm run generate_matrix_token سيتم توليد التوكن لـ (Matrix) بشكل تلقائي وحفظه في ملف config.json ``` -------------------------------------------------------------------------------- /README/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/README/1.jpg -------------------------------------------------------------------------------- /README/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/README/11.png -------------------------------------------------------------------------------- /README/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/README/12.png -------------------------------------------------------------------------------- /README/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/README/13.png -------------------------------------------------------------------------------- /README/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/README/14.png -------------------------------------------------------------------------------- /README/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/README/15.png -------------------------------------------------------------------------------- /README/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/README/16.jpg -------------------------------------------------------------------------------- /README/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/README/2.jpg -------------------------------------------------------------------------------- /README/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/README/3.jpg -------------------------------------------------------------------------------- /README/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/README/4.jpg -------------------------------------------------------------------------------- /README/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/README/5.jpg -------------------------------------------------------------------------------- /README/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/README/6.jpg -------------------------------------------------------------------------------- /README/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/README/7.jpg -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://$DISCOURSE_DOMAIN", 3 | "discourse_forum_name": "discourse forum name", 4 | "discourse_token": "discourse tokin", 5 | "discourse_username": "system", 6 | "telegram_token": "telegram token", 7 | "matrix_username": "Username to your Matrix account #aosus", 8 | "matrix_password": "Password to your Matrix account #*****", 9 | "matrix_homeserver_url": "https://matrix.org", 10 | "matrix_access_token": "Put your matrix_access_token here", 11 | "matrix_autojoin": true, 12 | "dataPath": "storage", 13 | "matrix_encryption": true, 14 | "language": "ar" 15 | } -------------------------------------------------------------------------------- /discourse/CreatePosts.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import fs from 'fs-extra'; 3 | import path from 'path'; 4 | 5 | export default async function CreatePosts(Api_Username, title, raw, category) { 6 | 7 | try { 8 | 9 | let __dirname = path.resolve(); 10 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 11 | let body = { 12 | 13 | "title": title, 14 | "raw": raw, 15 | "category": category 16 | } 17 | let init = { 18 | method: 'POST', 19 | headers: { 20 | 'Content-Type': 'application/json', 21 | 'Api-Key': process.env.DISCOURSE_TOKEN || config?.discourse_token, 22 | 'Api-Username': Api_Username 23 | }, 24 | body: JSON.stringify(body), 25 | } 26 | let response = await fetch(process.env.URL || config?.url + '/posts.json ', init); 27 | let data = await response.json(); 28 | 29 | if (data?.action && data?.errors) { 30 | 31 | return { 32 | action: data?.action, 33 | errors: data?.errors 34 | } 35 | 36 | } 37 | 38 | else { 39 | 40 | return { 41 | 42 | id: data?.id, 43 | username: data?.username, 44 | created_at: data?.created_at, 45 | cooked: data?.cooked, 46 | raw: data?.raw, 47 | post_number: data?.post_number, 48 | post_type: data?.post_type, 49 | reply_count: data?.reply_count, 50 | reply_to_post_number: data?.reply_to_post_number, 51 | topic_id: data?.topic_id, 52 | topic_slug: data?.topic_slug 53 | 54 | } 55 | 56 | } 57 | 58 | } catch (error) { 59 | 60 | console.log(error); 61 | 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /discourse/EventPosts.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import fs from 'fs-extra'; 3 | import path from 'path'; 4 | 5 | export default async function EventPosts(callback) { 6 | 7 | setInterval(async () => { 8 | 9 | try { 10 | 11 | let __dirname = path.resolve(); 12 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 13 | let EventPostsJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/EventPosts.json")); 14 | let response = await fetch(process.env.URL || config?.url + `/posts.json`, { method: 'GET' }); 15 | let data = await response.json(); 16 | let item = data?.latest_posts[0] 17 | let id = item.id; 18 | 19 | if (data?.latest_posts[0]?.post_number === 1 && EventPostsJson.includes(id) === false) { 20 | 21 | callback({ 22 | name: item?.name, 23 | username: item?.username, 24 | created_at: item?.created_at, 25 | post_number: item?.post_number, 26 | post_type: item?.post_type, 27 | topic_id: item?.topic_id, 28 | topic_slug: item?.topic_slug, 29 | topic_title: item?.topic_title, 30 | category_id: item?.category_id, 31 | cooked: item?.cooked, 32 | raw: item?.raw, 33 | }); 34 | EventPostsJson.push(id); 35 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/EventPosts.json"), EventPostsJson); 36 | } 37 | 38 | } catch (error) { 39 | console.log(error); 40 | } 41 | 42 | }, 60000); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /discourse/getCategories.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import fs from 'fs-extra'; 3 | import path from 'path'; 4 | 5 | export default async function getCategories() { 6 | 7 | try { 8 | 9 | let __dirname = path.resolve(); 10 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 11 | let response = await fetch(process.env.URL || config?.url + `/categories.json`, { method: 'GET' }); 12 | let data = await response.json(); 13 | 14 | if (data?.action && data?.errors) { 15 | 16 | return { 17 | action: data?.action, 18 | errors: data?.errors 19 | } 20 | 21 | } 22 | 23 | else { 24 | 25 | let array = [] 26 | 27 | for (const item of data?.category_list?.categories) { 28 | 29 | if (item?.read_restricted === false) { 30 | 31 | let opj = { 32 | 33 | id: item?.id, 34 | name: item?.name, 35 | slug: item?.slug, 36 | description: item?.description, 37 | topics_day: item?.topics_day, 38 | topics_week: item?.topics_week, 39 | topics_month: item?.topics_month, 40 | topics_all_time: item?.topics_all_time, 41 | 42 | } 43 | 44 | array.push(opj) 45 | 46 | } 47 | } 48 | 49 | return array 50 | 51 | } 52 | 53 | } catch (error) { 54 | 55 | console.log(error); 56 | 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /discourse/getComment.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import fs from 'fs-extra'; 3 | import path from 'path'; 4 | 5 | export default async function getComment(topic_id, comment_id) { 6 | 7 | try { 8 | 9 | let __dirname = path.resolve(); 10 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 11 | let response = await fetch(process.env.URL || config?.url + `/t/${topic_id}/posts.json`, { method: 'GET' }); 12 | let data = await response.json(); 13 | 14 | if (data?.action && data?.errors) { 15 | 16 | return { 17 | action: data?.action, 18 | errors: data?.errors 19 | } 20 | 21 | } 22 | 23 | else { 24 | 25 | if (comment_id) { 26 | 27 | return { 28 | 29 | id: data?.post_stream?.posts[comment_id]?.id, 30 | lengthComment: data?.post_stream?.posts?.length - 1, 31 | username: data?.post_stream?.posts[comment_id]?.username, 32 | cooked: data?.post_stream?.posts[comment_id]?.cooked, 33 | post_number: data?.post_stream?.posts[comment_id]?.post_number, 34 | post_type: data?.post_stream?.posts[comment_id]?.post_type, 35 | reply_count: data?.post_stream?.posts[comment_id]?.reply_count, 36 | reply_to_post_number: data?.post_stream?.posts[comment_id]?.reply_to_post_number, 37 | topic_id: data?.post_stream?.posts[comment_id]?.topic_id, 38 | topic_slug: data?.post_stream?.posts[comment_id]?.topic_slug, 39 | 40 | } 41 | 42 | } 43 | 44 | else { 45 | 46 | return { 47 | 48 | all_comments: data?.post_stream?.posts 49 | 50 | } 51 | } 52 | 53 | } 54 | 55 | } catch (error) { 56 | 57 | console.log(error); 58 | 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /discourse/get_latest_posts.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import fs from 'fs-extra'; 3 | import path from 'path'; 4 | 5 | export default async function get_latest_posts() { 6 | 7 | try { 8 | 9 | let __dirname = path.resolve(); 10 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 11 | let response = await fetch(process.env.URL || config?.url + `/posts.json`, { method: 'GET' }); 12 | let data = await response.json(); 13 | 14 | if (data?.action && data?.errors) { 15 | 16 | return { 17 | action: data?.action, 18 | errors: data?.errors 19 | } 20 | 21 | } 22 | 23 | else { 24 | 25 | var la_po = true; 26 | 27 | if (la_po) { 28 | for (let item of data?.latest_posts) { 29 | if (item?.post_number === 1) { 30 | return { 31 | name: item?.name, 32 | username: item?.username, 33 | created_at: item?.created_at, 34 | post_number: item?.post_number, 35 | post_type: item?.post_type, 36 | topic_id: item?.topic_id, 37 | topic_slug: item?.topic_slug, 38 | topic_title: item?.topic_title, 39 | category_id: item?.category_id, 40 | cooked: item?.cooked, 41 | raw: item?.raw, 42 | } 43 | } 44 | } 45 | 46 | var la_po = false 47 | } 48 | 49 | else if (la_po === false) { 50 | for (let item of data?.latest_posts) { 51 | return { 52 | name: item?.name, 53 | username: item?.username, 54 | created_at: item?.created_at, 55 | post_number: item?.post_number, 56 | post_type: item?.post_type, 57 | topic_id: item?.topic_id, 58 | topic_slug: item?.topic_slug, 59 | topic_title: item?.topic_title, 60 | category_id: item?.category_id, 61 | cooked: item?.cooked, 62 | raw: item?.raw, 63 | } 64 | } 65 | } 66 | 67 | } 68 | 69 | } catch (error) { 70 | 71 | console.log(error); 72 | 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /discourse/index.js: -------------------------------------------------------------------------------- 1 | import CreatePosts from './CreatePosts.js'; 2 | import sendComment from './sendComment.js'; 3 | import getComment from './getComment.js'; 4 | import get_latest_posts from './get_latest_posts.js'; 5 | import sendMessagePrivate from './sendMessagePrivate.js'; 6 | import getCategories from './getCategories.js'; 7 | import EventPosts from './EventPosts.js'; 8 | 9 | export { 10 | CreatePosts, 11 | sendComment, 12 | getComment, 13 | get_latest_posts, 14 | sendMessagePrivate, 15 | getCategories, 16 | EventPosts 17 | } -------------------------------------------------------------------------------- /discourse/sendComment.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import fs from 'fs-extra'; 3 | import path from 'path'; 4 | 5 | export default async function sendComment(Api_Username, topic_id, raw) { 6 | 7 | try { 8 | 9 | let __dirname = path.resolve(); 10 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 11 | let body = { 12 | 13 | "raw": raw, 14 | "topic_id": topic_id, 15 | } 16 | 17 | let init = { 18 | method: 'POST', 19 | headers: { 20 | 'Content-Type': 'application/json', 21 | 'Api-Key': process.env.DISCOURSE_TOKEN || config?.discourse_token, 22 | 'Api-Username': Api_Username 23 | }, 24 | body: JSON.stringify(body), 25 | } 26 | let response = await fetch(process.env.URL || config?.url + '/posts.json ', init); 27 | let data = await response.json(); 28 | 29 | if (data?.action && data?.errors) { 30 | 31 | return { 32 | action: data?.action, 33 | errors: data?.errors 34 | } 35 | 36 | } 37 | 38 | else { 39 | 40 | return { 41 | 42 | id: data?.id, 43 | username: data?.username, 44 | created_at: data?.created_at, 45 | cooked: data?.cooked, 46 | raw: data?.raw, 47 | post_number: data?.post_number, 48 | post_type: data?.post_type, 49 | reply_count: data?.reply_count, 50 | reply_to_post_number: data?.reply_to_post_number, 51 | topic_id: data?.topic_id, 52 | topic_slug: data?.topic_slug, 53 | 54 | } 55 | 56 | } 57 | 58 | } catch (error) { 59 | 60 | console.log(error); 61 | 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /discourse/sendMessagePrivate.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import fs from 'fs-extra'; 3 | import path from 'path'; 4 | 5 | export default async function sendMessagePrivate(Api_Username, title, raw, sendTo) { 6 | 7 | try { 8 | 9 | let __dirname = path.resolve(); 10 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 11 | let body = { 12 | title: title, 13 | raw: raw, 14 | archetype: "private_message", 15 | target_recipients: sendTo 16 | } 17 | 18 | let init = { 19 | method: 'POST', 20 | headers: { 21 | 'Content-Type': 'application/json', 22 | 'Api-Key': process.env.DISCOURSE_TOKEN || config?.discourse_token, 23 | 'Api-Username': Api_Username 24 | }, 25 | body: JSON.stringify(body), 26 | } 27 | let response = await fetch(process.env.URL || config?.url + '/posts.json ', init); 28 | let data = await response.json(); 29 | 30 | if (data?.action && data?.errors) { 31 | 32 | return { 33 | action: data?.action, 34 | errors: data?.errors 35 | } 36 | 37 | } 38 | 39 | else { 40 | 41 | return { 42 | 43 | id: data?.id, 44 | username: data?.username, 45 | created_at: data?.created_at, 46 | cooked: data?.cooked, 47 | raw: data?.raw, 48 | topic_id: data?.topic_id, 49 | topic_slug: data?.topic_slug, 50 | 51 | } 52 | 53 | } 54 | 55 | } catch (error) { 56 | 57 | console.log(error); 58 | 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /docs/_headers: -------------------------------------------------------------------------------- 1 | https://:project.pages.dev/* 2 | X-Robots-Tag: noindex -------------------------------------------------------------------------------- /docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Discourse chat bridge 2 | site_url: https://discourse-chat-bridge.aosus.dev 3 | repo_name: aosus/discourse-chat-bridge 4 | repo_url: https://github.com/aosus/discourse-chat-bridge 5 | edit_uri: https://github.com/aosus/discourse-chat-bridge/docs 6 | site_description: A chat bridge allowing users to post, reply and interact with discourse posts from their own favorite chat platforms. 7 | docs_dir: source 8 | 9 | plugins: 10 | - autolinks 11 | - search 12 | - git-revision-date-localized: 13 | type: timeago 14 | - minify: 15 | minify_html: true 16 | 17 | theme: 18 | name: material 19 | custom_dir: overrides 20 | logo: images/Discourse_Bridge.webp 21 | icon: 22 | repo: fontawesome/brands/github 23 | font: false 24 | features: 25 | - search.suggest 26 | - search.highlight 27 | - content.tabs.link 28 | - navigation.instant 29 | - navigation.tracking 30 | - toc.integrate 31 | palette: 32 | # Palette toggle for light mode 33 | - media: "(prefers-color-scheme: light)" 34 | scheme: default 35 | toggle: 36 | icon: material/brightness-7 37 | name: Switch to dark mode 38 | primary: green 39 | accent: blue 40 | 41 | # Palette toggle for dark mode 42 | - media: "(prefers-color-scheme: dark)" 43 | scheme: slate 44 | toggle: 45 | icon: material/brightness-4 46 | name: Switch to light mode 47 | primary: green 48 | accent: blue 49 | 50 | 51 | extra: 52 | alternate: 53 | - name: English 54 | link: / 55 | lang: en 56 | 57 | - name: العربية 58 | link: /ar/ 59 | lang: ar 60 | 61 | social: 62 | - icon: fontawesome/brands/github 63 | link: https://github.com/aosus 64 | - icon: fontawesome/brands/twitter 65 | link: https://twitter.com/aosusorg 66 | - icon: fontawesome/brands/mastodon 67 | link: https://mastodon.online/@aosus 68 | - icon: fontawesome/brands/telegram 69 | link: https://t.me/aosus 70 | - icon: matrix 71 | link: https://matrix.to/#/#aosus:aosus.org 72 | - icon: fontawesome/brands/linkedin 73 | link: https://www.linkedin.com/company/aosus/ 74 | 75 | markdown_extensions: 76 | - admonition 77 | - pymdownx.highlight: 78 | anchor_linenums: true 79 | line_spans: __span 80 | pygments_lang_class: true 81 | - pymdownx.inlinehilite 82 | - pymdownx.snippets 83 | - pymdownx.superfences 84 | 85 | extra_css: 86 | - extra.css 87 | -------------------------------------------------------------------------------- /docs/overrides/.icons/matrix.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 14 | 32 | Matrix (protocol) logo 34 | 39 | 43 | 47 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /docs/overrides/extra.css: -------------------------------------------------------------------------------- 1 | @import url("https://aosus.org/fonts/fonts.css"); 2 | 3 | :root { 4 | --md-text-font: "Almarai"; 5 | } -------------------------------------------------------------------------------- /docs/overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block analytics %} 4 | 5 | {% endblock %} -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs 2 | mkdocs-material 3 | git+https://github.com/midnightprioriem/mkdocs-autolinks-plugin 4 | mkdocs-git-revision-date-localized-plugin 5 | mkdocs-minify-plugin 6 | Pygments -------------------------------------------------------------------------------- /docs/source/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/docs/source/images/1.jpg -------------------------------------------------------------------------------- /docs/source/images/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/docs/source/images/11.png -------------------------------------------------------------------------------- /docs/source/images/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/docs/source/images/12.png -------------------------------------------------------------------------------- /docs/source/images/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/docs/source/images/13.png -------------------------------------------------------------------------------- /docs/source/images/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/docs/source/images/14.png -------------------------------------------------------------------------------- /docs/source/images/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/docs/source/images/15.png -------------------------------------------------------------------------------- /docs/source/images/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/docs/source/images/16.jpg -------------------------------------------------------------------------------- /docs/source/images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/docs/source/images/2.jpg -------------------------------------------------------------------------------- /docs/source/images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/docs/source/images/3.jpg -------------------------------------------------------------------------------- /docs/source/images/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/docs/source/images/4.jpg -------------------------------------------------------------------------------- /docs/source/images/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/docs/source/images/5.jpg -------------------------------------------------------------------------------- /docs/source/images/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/docs/source/images/6.jpg -------------------------------------------------------------------------------- /docs/source/images/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/docs/source/images/7.jpg -------------------------------------------------------------------------------- /docs/source/images/Discourse_Bridge.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/docs/source/images/Discourse_Bridge.webp -------------------------------------------------------------------------------- /docs/source/images/api-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aosus/discourse-chat-bridge/544cdd41d45b7debe6a9ac293e19ea5601e843e2/docs/source/images/api-setup.png -------------------------------------------------------------------------------- /docs/source/index.md: -------------------------------------------------------------------------------- 1 | # Discourse-chat-bridge 2 |
3 | discourse-chat-bridge 4 |

A chat bridge allowing users to post, reply and interact with discourse posts from their own favorite chat platforms.

5 | Installation 6 |
7 | 8 | ## Bridge Features 9 | 10 | - fetch the latest published topic 📄 11 | - fetch categories ⬇️ 12 | - Post a new topic 📝 13 | - Post a new comment 💬 14 | - Send a private message 🔒 15 | - Link your chat account with your discussion account 16 | - Receive the latest topics posted from the bot 17 | 18 | ## Supported platforms 19 | - Matrix 20 | - Telegram 21 | 22 | ## License 23 | Discourse-chat-bridge is licensed under the AGPLv3. 24 | 25 | ![license](https://www.gnu.org/graphics/agplv3-with-text-162x68.png) -------------------------------------------------------------------------------- /docs/source/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## Discourse setup 4 | Go to Admin Settings > API and create a new API key. 5 | It should have a granular scope and be able to access all users. 6 | Make sure you also give the token topics write access. 7 | 8 | ![Api settings](images/api-setup.png) 9 | 10 | ### installation with docker-compose.yml 11 | ```yaml 12 | services: 13 | discourse-chat-bridge: 14 | image: oci.aosus.org/aosus/discourse-chat-bridge:latest 15 | restart: always 16 | environment: 17 | URL: "https://discourse.example.com" 18 | DISCOURSE_FORUM_NAME: "Discourse community" 19 | DISCOURSE_TOKEN: "API key with write access for all users" 20 | DISCOURSE_USERNAME: "username to be used for Direct messages from the bridge" 21 | TELEGRAM_TOKEN: "" 22 | MATRIX_USERNAME: "" 23 | #MATRIX_PASSWORD: "" matrix password shouldn't beused in env variables, rather you should directly set the access token. 24 | MATRIX_HOMESERVER_URL: "https://matrix.example.com" 25 | MATRIX_ACCESS_TOKEN: "" 26 | MATRIX_AUTOJOIN: TRUE 27 | DATAPATH: /data 28 | MATRIX_ENCRYPTION: TRUE 29 | language: "en" 30 | volumes: 31 | - ./data:/data:z 32 | ``` 33 | 34 | Then you can start the container 35 | ```bash 36 | docker compose up -d 37 | ``` 38 | 39 | ## Native 40 | ```bash 41 | git clone https://github.com/aosus/discourse-chat-bridge 42 | ``` 43 | 44 | edit `config.json` and add required inputs 45 | ```json 46 | { 47 | "url": "https://$DISCOURSE_DOMAIN", 48 | "discourse_forum_name": "discourse forum name", 49 | "discourse_token": "API key with write access for all users", 50 | "discourse_username": "username to be used for Direct messages from the bridge", 51 | "telegram_token": "", 52 | "matrix_username": "Username to your Matrix account #aosus", 53 | "matrix_password": "Password to your Matrix account to generate the access token, you can skip this by inputing it directly", 54 | "matrix_homeserver_url": "https://matrix.org", 55 | "matrix_access_token": "Put your matrix_access_token here #npm run generate_matrix_token", 56 | "matrix_autojoin": true, 57 | "dataPath": "./storage", 58 | "matrix_encryption": true, 59 | "language": "en" 60 | } 61 | ``` 62 | 63 | Then start it up! 64 | 65 | ```bash 66 | npm i 67 | npm run generate_matrix_token 68 | npm start 69 | or 70 | node index.js 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/source/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ## Bot commands 4 | 5 | | Command | description| 6 | |:--------------|-----------------:| 7 | | start | Start the bot | 8 | | discourse | Link your Discourse account | 9 | | get_latest_posts | Fetch and display the latest posts | 10 | | getCategories | List available categories | 11 | | CreatePosts | Create a new post | 12 | | sendComment | Send a comment on an existing post | 13 | | sendMessagePrivate | Send a private message | 14 | | activation | Activate notifications for new posts | 15 | -------------------------------------------------------------------------------- /docs/source/عربي/index.md: -------------------------------------------------------------------------------- 1 | # الرئيسية 2 |
3 | discourse-chat-bridge 4 |

جسر بين منصة Discourse وبرامج التواصل . 5 |

6 | تثبيت 7 |
8 | 9 | - تيليجرام - مدعوم 10 | - matrix - مدعوم 11 | 12 | # مميزات الجسر 13 | 14 | - عرض آخر موضوع تم نشره 📄 15 | - عرض الفئات ⬇️ 16 | - كتابة موضوع جديد 📝 17 | - كتابة تعليق جديد 💬 18 | - إرسال رسالة خاصة 🔒 19 | - ربط حسابك على منصة Discourse 20 | - تفعيل البوت لتلقي آخر المواضيع المنشورة 21 | 22 | 23 | 24 | ## الترخيص 25 | مرخص تحت AGPLv3. 26 | 27 | ![license](https://www.gnu.org/graphics/agplv3-with-text-162x68.png) -------------------------------------------------------------------------------- /docs/source/عربي/installation.md: -------------------------------------------------------------------------------- 1 | # التثبيت 2 | 3 | ## discourse من API الحصول على مفتاح 4 | ![11|328x402](11.png) 5 | 6 |
7 | 8 | ![12|690x93](12.png) 9 | 10 | ![13|690x193](13.png) 11 | 12 | 13 | قم بإختيار جميع المستخدمين 14 | 15 | ![14|521x99](14.png) 16 | 17 | قم بتحديد على صلاحية الكتابة 18 | 19 | ![15|690x123](15.png) 20 | 21 | احفظ المفتاح في مكان آمن واضغط على متابعة 22 | 23 | ### docker-compose.yml التثبيت باستخدام 24 | 25 | ```yaml 26 | services: 27 | discourse-chat-bridge: 28 | image: oci.aosus.org/aosus/discourse-chat-bridge:latest 29 | restart: always 30 | environment: 31 | URL: "https://discourse.example.com" 32 | DISCOURSE_FORUM_NAME: "مجتمع Discourse" 33 | DISCOURSE_TOKEN: "مفتاح API مع صلاحيات الكتابة لجميع المستخدمين" 34 | DISCOURSE_USERNAME: "اسم المستخدم الذي سيتم استخدامه للرسائل المباشرة من الجسر" 35 | TELEGRAM_TOKEN: "" 36 | MATRIX_USERNAME: "" 37 | #MATRIX_PASSWORD: "" لا يجب استخدام كلمة مرور Matrix في متغيرات البيئة، بل يجب تعيين رمز الوصول مباشرة. 38 | MATRIX_HOMESERVER_URL: "https://matrix.example.com" 39 | MATRIX_ACCESS_TOKEN: "" 40 | MATRIX_AUTOJOIN: TRUE 41 | DATAPATH: /data 42 | MATRIX_ENCRYPTION: TRUE 43 | language: "en" 44 | volumes: 45 | - ./data:/data:z 46 | ``` 47 | 48 | ثم يمكنك بدء الحاوية باستخدام: 49 | 50 | ```bash 51 | docker compose up -d 52 | ``` 53 | 54 | ## التثبيت المباشر 55 | 56 | ```bash 57 | git clone https://github.com/aosus/discourse-chat-bridge 58 | ``` 59 | 60 | وأضف المدخلات المطلوبة `config.json` قم بتحرير: 61 | 62 | ```json 63 | { 64 | "url": "https://$DISCOURSE_DOMAIN", 65 | "discourse_forum_name": "اسم منتدى Discourse", 66 | "discourse_token": "مفتاح API مع صلاحيات الكتابة لجميع المستخدمين", 67 | "discourse_username": "اسم المستخدم الذي سيتم استخدامه للرسائل المباشرة من الجسر", 68 | "telegram_token": "", 69 | "matrix_username": "اسم المستخدم لحسابك على Matrix #aosus", 70 | "matrix_password": "كلمة مرور حساب Matrix لتوليد رمز الوصول، يمكنك تخطي هذا بإدخال الرمز مباشرة", 71 | "matrix_homeserver_url": "https://matrix.org", 72 | "matrix_access_token": "أدخل رمز الوصول الخاص بـ Matrix هنا #npm run generate_matrix_token", 73 | "matrix_autojoin": true, 74 | "dataPath": "./storage", 75 | "matrix_encryption": true, 76 | "language": "en" 77 | } 78 | ``` 79 | 80 | ثم قم بتشغيله! 81 | 82 | ```bash 83 | npm i 84 | npm run generate_matrix_token 85 | npm start 86 | or 87 | node index.js 88 | ``` -------------------------------------------------------------------------------- /docs/source/عربي/usage.md: -------------------------------------------------------------------------------- 1 | # أستخدام البوت 2 | 3 | | الأمر | صورة| 4 | |:--------------|-----------------:| 5 | |start | ![discourse-chat-bridge](16.jpg) | 6 | |get_latest_posts | ![discourse-chat-bridge](6.jpg) | 7 | |getCategories | ![discourse-chat-bridge](3.jpg) | 8 | |CreatePosts | ![discourse-chat-bridge](2.jpg) | 9 | |sendComment | ![discourse-chat-bridge](5.jpg) | 10 | |sendMessagePrivate | ![discourse-chat-bridge](4.jpg) | 11 | |discourse | ![discourse-chat-bridge](1.jpg) | 12 | |activation | ![discourse-chat-bridge](7.jpg) | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment-hijri'; 2 | import Crate from './module/Crate.js'; 3 | import telegram from './telegram/index.js'; 4 | import MatrixBot from './matrix/index.js'; 5 | import * as dotenv from 'dotenv' 6 | dotenv.config({ override: true }); 7 | moment.locale('en-EN'); 8 | 9 | console.log('starting discourse bridge: ', moment().format('LT')); 10 | 11 | await Crate() // إنشاء مجلدات قاعدة البيانات 12 | await telegram() // بوت تيليجرام 13 | await MatrixBot() // بوت ماتركس -------------------------------------------------------------------------------- /matrix/EventPosts.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import moment from 'moment-hijri'; 3 | import EventPosts from '../discourse/EventPosts.js'; 4 | import getrRoomMatrix from '../module/getrRoomMatrix.js'; 5 | import sendFile from './sendFile.js'; 6 | import fs from 'fs-extra'; 7 | import Translation from '../module/translation.js'; 8 | import path from 'path'; 9 | 10 | 11 | export default async function EventPosts_(client) { 12 | 13 | await EventPosts(async e => { 14 | 15 | try { 16 | 17 | let __dirname = path.resolve(); 18 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 19 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 20 | let name = e?.name; 21 | let username = e?.username; 22 | let created_at = e?.created_at; 23 | let post_number = e?.post_number; 24 | let post_type = e?.post_type; 25 | let topic_id = e?.topic_id; 26 | let topic_slug = e?.topic_slug; 27 | let topic_title = e?.topic_title; 28 | let category_id = e?.category_id; 29 | let cooked = e?.cooked; 30 | let raw = e?.raw; 31 | let response = await fetch(process.env.URL || config?.url + `/t/${topic_slug}/${topic_id}`, { method: 'GET' }); 32 | let data = await response?.text(); 33 | 34 | if (data.includes('itemprop="image"')) { 35 | 36 | let getRoom = await getrRoomMatrix('all'); 37 | 38 | for (let item of getRoom?.array) { 39 | 40 | if (item?.categories === category_id) { 41 | let preview = data.split('itemprop="image" href="')[1]?.split('">')[0]; 42 | let caption = `${topic_title}

`; 43 | caption += `${translation.writer}: ${name}
`; 44 | caption += `${translation.date}: ${moment(created_at).format('iYYYY/iM/iD')}
`; 45 | caption += `${translation.number_topic}: ${topic_id}`; 46 | 47 | await sendFile(item?.roomId, preview, 'm.image', client).catch(error => console.log(error)); 48 | await client.sendMessage(item?.roomId, { 49 | "msgtype": "m.notice", 50 | "format": "org.matrix.custom.html", 51 | "formatted_body": caption, 52 | "body": caption, 53 | }).catch(error => console.log(error)); 54 | } 55 | 56 | else if (item?.categories === 0) { 57 | let preview = data.split('itemprop="image" href="')[1]?.split('">')[0]; 58 | let caption = `${topic_title}

`; 59 | caption += `${translation.writer}: ${name}
`; 60 | caption += `${translation.date}: ${moment(created_at).format('iYYYY/iM/iD')}
`; 61 | caption += `${translation.number_topic}: ${topic_id}`; 62 | 63 | await sendFile(item?.roomId, preview, 'm.image', client).catch(error => console.log(error)); 64 | await client.sendMessage(item?.roomId, { 65 | "msgtype": "m.notice", 66 | "format": "org.matrix.custom.html", 67 | "formatted_body": caption, 68 | "body": caption, 69 | }).catch(error => console.log(error)); 70 | } 71 | } 72 | 73 | } 74 | 75 | else { 76 | 77 | let getRoom = await getrRoomMatrix('all'); 78 | 79 | for (let item of getRoom?.array) { 80 | 81 | if (item?.categories === category_id) { 82 | let caption = `${topic_title}

`; 83 | caption += `${translation.writer}: ${name}
`; 84 | caption += `${translation.date}: ${moment(created_at).format('iYYYY/iM/iD')}
`; 85 | caption += `${translation.number_topic}: ${topic_id}`; 86 | await client.sendMessage(item?.roomId, { 87 | "msgtype": "m.notice", 88 | "format": "org.matrix.custom.html", 89 | "formatted_body": caption, 90 | "body": caption, 91 | }).catch(error => console.log(error)); 92 | } 93 | 94 | else if (item?.categories === 0) { 95 | let caption = `${topic_title}

`; 96 | caption += `${translation.writer}: ${name}
`; 97 | caption += `${translation.date}: ${moment(created_at).format('iYYYY/iM/iD')}
`; 98 | caption += `${translation.number_topic}: ${topic_id}`; 99 | await client.sendMessage(item?.roomId, { 100 | "msgtype": "m.notice", 101 | "format": "org.matrix.custom.html", 102 | "formatted_body": caption, 103 | "body": caption, 104 | }).catch(error => console.log(error)); 105 | 106 | } 107 | } 108 | } 109 | 110 | } catch (error) { 111 | 112 | console.log(error); 113 | 114 | } 115 | }); 116 | 117 | } -------------------------------------------------------------------------------- /matrix/EventReply.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import sendComment from '../discourse/sendComment.js'; 3 | import Translation from '../module/translation.js'; 4 | import path from 'path'; 5 | 6 | let __dirname = path.resolve(); 7 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 8 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 9 | 10 | export default async function EventReply(roomId, sender, meId, body, replySender, replyBody, event, RichReply, client) { 11 | 12 | if (replySender?.includes(meId)) { 13 | 14 | let topic_id = replyBody?.split(`${translation.number_topic}: `)[1]; 15 | let memberJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`)); 16 | 17 | if (topic_id) { 18 | 19 | let seCo = await sendComment(memberJson?.discourse_username, topic_id, body).catch(error => console.log(error)); 20 | 21 | if (seCo?.errors) { 22 | 23 | for (let item of seCo?.errors) { 24 | let reply = RichReply.createFor(roomId, event, item, item); 25 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 26 | } 27 | 28 | } 29 | else { 30 | 31 | let topic_slug = seCo?.topic_slug 32 | let post_number = seCo?.post_number 33 | let message = `${translation.comment_posted} ✅ ${post_number}` 34 | let reply = RichReply.createFor(roomId, event, message, message); 35 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 36 | 37 | } 38 | 39 | } 40 | 41 | 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /matrix/generate_matrix_token.js: -------------------------------------------------------------------------------- 1 | import { MatrixAuth } from "matrix-bot-sdk"; 2 | import fs from 'fs-extra'; 3 | import * as dotenv from 'dotenv' 4 | import path from 'path'; 5 | dotenv.config({ override: true }) 6 | 7 | let __dirname = path.resolve(); 8 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 9 | let matrix_homeserver_url = process.env.MATRIX_HOMESERVER_URL || config?.matrix_homeserver_url; 10 | let username = process.env.MATRIX_USERNAME || config?.matrix_username; 11 | let password = process.env.MATRIX_PASSWORD || config?.matrix_password; 12 | let auth = new MatrixAuth(matrix_homeserver_url); 13 | let login = await auth.passwordLogin(username, password); 14 | 15 | config.matrix_access_token = login.matrix_access_token; 16 | fs.writeJSONSync("./config.json", config, { spaces: '\t' }); 17 | console.log('matrix_access_token: ', login.matrix_access_token); -------------------------------------------------------------------------------- /matrix/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | AutojoinRoomsMixin, 3 | LogLevel, 4 | LogService, 5 | MatrixClient, 6 | RustSdkCryptoStorageProvider, 7 | SimpleFsStorageProvider, 8 | RichReply, 9 | RichRepliesPreprocessor 10 | } from "matrix-bot-sdk"; 11 | import getMenu from '../module/getMenu.js'; 12 | import start from './start.js'; 13 | import menu from '../module/menu.js'; 14 | import { database_matrix, database_matrix_member } from '../module/database_matrix.js'; 15 | import EventPosts_ from './EventPosts.js'; 16 | import EventReply from './EventReply.js'; 17 | import path from 'path'; 18 | import fs from 'fs-extra'; 19 | 20 | export default async function MatrixBot() { 21 | 22 | try { 23 | 24 | let __dirname = path.resolve(); 25 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 26 | LogService.setLevel(LogLevel.name); 27 | 28 | let storage = new SimpleFsStorageProvider(path.join(process.env.DATAPATH || config?.dataPath, "matrix.json")); 29 | // Prepare a crypto store if we need that 30 | let cryptoStore; 31 | if (process.env.MATRIX_ENCRYPTION === "true" || config?.matrix_encryption) { 32 | cryptoStore = new RustSdkCryptoStorageProvider(path.join(process.env.DATAPATH || config?.dataPath, "encrypted")); 33 | } 34 | // Now create the client 35 | let client = new MatrixClient(process.env.MATRIX_HOMESERVER_URL || config?.matrix_homeserver_url, process.env.MATRIX_ACCESS_TOKEN || config?.matrix_access_token, storage, cryptoStore); 36 | // Setup the matrix_autojoin mixin (if enabled) 37 | if (process.env.MATRIX_ACCESS_TOKEN === "true" || config?.matrix_autojoin) { 38 | AutojoinRoomsMixin.setupOnClient(client); 39 | } 40 | 41 | client.addPreprocessor(new RichRepliesPreprocessor(false)); 42 | 43 | client.on("room.message", async (roomId, event) => { 44 | 45 | if (!event?.content) return; // Ignore redacted events that come through 46 | if (event?.sender === await client.getUserId()) return; // Ignore ourselves 47 | if (event?.sender.includes('telegram')) return; // Ignore user telegram 48 | if (event?.content?.msgtype !== "m.text") return; // Ignore non-text messages 49 | if (event.unsigned.age > 1000 * 60) return; // older than a minute 50 | 51 | let meId = await client.getUserId(); 52 | let sender = event?.sender; 53 | let roomIdOrAlias = await client?.getPublishedAlias(roomId) 54 | let Profile = await client.getUserProfile(sender); 55 | let body = event?.content?.body; 56 | let name = Profile?.displayname; 57 | let external_url = event?.content?.external_url; 58 | let msgtype = event?.content?.msgtype; 59 | let replyBody = event?.mx_richreply?.fallbackHtmlBody; 60 | let replySender = event?.mx_richreply?.fallbackSender; 61 | let DirectoryVisibility = await client.getDirectoryVisibility(roomId); 62 | let type = event?.type; 63 | let event_id = event?.event_id; 64 | let roomState = await client.getRoomState(roomId); 65 | let roomfindName = roomState.find(e => e?.type === 'm.room.name'); 66 | let roomfindAdmin = roomState.find(e => e?.type === 'm.room.power_levels'); 67 | let roomName = roomfindName?.content?.name; 68 | let checkRoom = roomName ? 'room' : 'direct'; 69 | let usersAdmin = Object.keys(roomfindAdmin?.content?.users); 70 | 71 | await database_matrix({ roomId: roomId, sender: sender, name: roomName ? roomName : name, checkRoom: checkRoom, roomIdOrAlias: roomIdOrAlias }); 72 | await database_matrix_member({ sender: sender, name: name }); 73 | await start(roomId, sender, name, body, event, RichReply, client); 74 | await EventReply(roomId, sender, meId, body, replySender, replyBody, event, RichReply, client); 75 | await menu[await getMenu(sender)]?.module?.exec({ 76 | meId: meId, 77 | roomId: roomId, 78 | sender: sender, 79 | name: name, 80 | checkRoom: checkRoom, 81 | roomIdOrAlias: roomIdOrAlias, 82 | body: body, 83 | replyBody: replyBody, 84 | replySender: replySender, 85 | roomName: roomName ? roomName : name, 86 | event_id: event_id, 87 | usersAdmin: usersAdmin, 88 | RichReply: RichReply, 89 | event: event, 90 | client: client 91 | }); 92 | 93 | console.log(`#Matrix sender: ${sender} ${checkRoom}: ${roomIdOrAlias ? roomIdOrAlias : roomName ? roomName : name}`); 94 | 95 | 96 | }); 97 | 98 | await EventPosts_(client).catch(error => console.log(error)); 99 | 100 | await client.start().then(() => console.log('Matrix is ready!')); 101 | 102 | } catch (error) { 103 | 104 | console.log(error); 105 | 106 | if (error?.body?.[0]?.errcode === 'M_UNKNOWN') { 107 | 108 | console.log('Run the command "npm run generate_matrix_token" and restart the bridge'); 109 | } 110 | 111 | } 112 | 113 | } -------------------------------------------------------------------------------- /matrix/menu/CreatePosts/CreatePosts_1.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import { database_matrix_member } from '../../../module/database_matrix.js'; 3 | import Translation from '../../../module/translation.js'; 4 | import path from 'path'; 5 | 6 | export default { 7 | async exec({ meId, roomId, sender, name, checkRoom, roomIdOrAlias, body, replyBody, replySender, roomName, event_id, usersAdmin, RichReply, event, client }) { 8 | 9 | let __dirname = path.resolve(); 10 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 11 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 12 | 13 | if (!isNaN(body)) { 14 | 15 | let memberJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`)); 16 | let message = `${translation.topic_title} 📝` 17 | let reply = RichReply.createFor(roomId, event, message, message); 18 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 19 | memberJson.CreatePosts_1 = Number(body); 20 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`), memberJson, { spaces: '\t' }); 21 | await database_matrix_member({ sender: sender, menu: 'CreatePosts_2' }).catch(error => console.log(error)); 22 | } 23 | 24 | else { 25 | let message = `${translation.err_wrong_entry} ❌

` 26 | message += `${translation.back_main_menu}` 27 | let reply = RichReply.createFor(roomId, event, message, message); 28 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 29 | } 30 | 31 | } 32 | } -------------------------------------------------------------------------------- /matrix/menu/CreatePosts/CreatePosts_2.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import { database_matrix_member } from '../../../module/database_matrix.js'; 3 | import Translation from '../../../module/translation.js'; 4 | import path from 'path'; 5 | 6 | export default { 7 | async exec({ meId, roomId, sender, name, checkRoom, roomIdOrAlias, body, replyBody, replySender, roomName, event_id, usersAdmin, RichReply, event, client }) { 8 | 9 | let __dirname = path.resolve(); 10 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 11 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 12 | 13 | if (body) { 14 | 15 | let memberJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`)); 16 | let message = `${translation.topic_content} 📝` 17 | let reply = RichReply.createFor(roomId, event, message, message); 18 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 19 | memberJson.CreatePosts_2 = body; 20 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`), memberJson, { spaces: '\t' }); 21 | await database_matrix_member({ sender: sender, menu: 'CreatePosts_3' }).catch(error => console.log(error)); 22 | } 23 | 24 | else { 25 | let message = `${translation.err_wrong_entry} ❌

` 26 | message += `${translation.back_main_menu}` 27 | let reply = RichReply.createFor(roomId, event, message, message); 28 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 29 | } 30 | 31 | } 32 | } -------------------------------------------------------------------------------- /matrix/menu/CreatePosts/CreatePosts_3.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import CreatePosts from '../../../discourse/CreatePosts.js'; 3 | import { database_matrix_member } from '../../../module/database_matrix.js'; 4 | import Translation from '../../../module/translation.js'; 5 | import path from 'path'; 6 | 7 | export default { 8 | async exec({ meId, roomId, sender, name, checkRoom, roomIdOrAlias, body, replyBody, replySender, roomName, event_id, usersAdmin, RichReply, event, client }) { 9 | 10 | let __dirname = path.resolve(); 11 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 12 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 13 | 14 | if (body) { 15 | 16 | let memberJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`)); 17 | let url = process.env.URL || config?.url 18 | let category = memberJson?.CreatePosts_1; 19 | let title = memberJson?.CreatePosts_2; 20 | let raw = body; 21 | let crPo = await CreatePosts(memberJson?.discourse_username, title, raw, category).catch(error => console.log(error)); 22 | 23 | if (crPo?.errors) { 24 | for (let item of crPo?.errors) { 25 | let reply = RichReply.createFor(roomId, event, item, item); 26 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 27 | } 28 | } 29 | 30 | else if (crPo?.topic_id && crPo?.topic_slug) { 31 | let topic_id = crPo?.topic_id; 32 | let topic_slug = crPo?.topic_slug; 33 | let message = `${title}
` 34 | let reply = RichReply.createFor(roomId, event, message, message); 35 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 36 | } 37 | 38 | else { 39 | let message = `${translation.wrong_data_entered} ❌

` 40 | message += `${translation.back_main_menu_2}` 41 | let reply = RichReply.createFor(roomId, event, message, message); 42 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 43 | } 44 | 45 | await database_matrix_member({ sender: sender, menu: 'main' }).catch(error => console.log(error)); 46 | } 47 | 48 | else { 49 | let message = `${translation.err_wrong_entry} ❌

` 50 | message += `${translation.back_main_menu}` 51 | let reply = RichReply.createFor(roomId, event, message, message); 52 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 53 | } 54 | 55 | } 56 | } -------------------------------------------------------------------------------- /matrix/menu/activation.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import { database_matrix_member } from '../../module/database_matrix.js'; 3 | import Translation from '../../module/translation.js'; 4 | import path from 'path'; 5 | 6 | export default { 7 | async exec({ meId, roomId, sender, name, checkRoom, roomIdOrAlias, body, replyBody, replySender, roomName, event_id, usersAdmin, RichReply, event, client }) { 8 | 9 | let __dirname = path.resolve(); 10 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 11 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 12 | 13 | if (!isNaN(body)) { 14 | 15 | let roomJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/${checkRoom}/${roomId}.json`)); 16 | let message = `${translation.active_bot} ✅`; 17 | let reply = RichReply.createFor(roomId, event, message, message); 18 | 19 | roomJson.evenPost = true; 20 | roomJson.categories = Number(body) 21 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/${checkRoom}/${roomId}.json`), roomJson, { spaces: '\t' }); 22 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 23 | await database_matrix_member({ sender: sender, menu: 'main' }).catch(error => console.log(error)); 24 | } 25 | 26 | else { 27 | let message = `${translation.err_wrong_entry} ❌

` 28 | message += `${translation.back_main_menu}` 29 | let reply = RichReply.createFor(roomId, event, message, message); 30 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 31 | } 32 | 33 | } 34 | } -------------------------------------------------------------------------------- /matrix/menu/discourse/discourse_1.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import { database_matrix_member } from '../../../module/database_matrix.js'; 3 | import sendMessagePrivate from '../../../discourse/sendMessagePrivate.js'; 4 | import Translation from '../../../module/translation.js'; 5 | import path from 'path'; 6 | 7 | export default { 8 | async exec({ meId, roomId, sender, name, checkRoom, roomIdOrAlias, body, replyBody, replySender, roomName, event_id, usersAdmin, RichReply, event, client }) { 9 | 10 | let __dirname = path.resolve(); 11 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 12 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 13 | 14 | if (body) { 15 | 16 | let memberJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`)); 17 | let title = `${translation.verification_code}` 18 | let raw = `${translation.verification_code_for} ${memberJson?.sender ? sender : memberJson?.name}

`; 19 | raw += memberJson?.verification_code; 20 | memberJson.discourse_username = body; 21 | let Private = await sendMessagePrivate(process.env.DISCOURSE_USERNAME || config?.discourse_username, title, raw, body).catch(error => console.log(error)); 22 | 23 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`), memberJson, { spaces: '\t' }); 24 | 25 | if (Private?.errors) { 26 | for (let item of Private?.errors) { 27 | let reply = RichReply.createFor(roomId, event, item, item); 28 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 29 | } 30 | } 31 | else { 32 | let message_1 = `${translation.send_verification_code} ✅` 33 | let message_2 = `${translation.write_verification_code} 📝` 34 | let reply_1 = RichReply.createFor(roomId, event, message_1, message_1); 35 | let reply_2 = RichReply.createFor(roomId, event, message_2, message_2); 36 | await client.sendMessage(roomId, reply_1).catch(error => console.log(error)); 37 | await client.sendMessage(roomId, reply_2).catch(error => console.log(error)); 38 | await database_matrix_member({ sender: sender, menu: 'discourse_2' }).catch(error => console.log(error)); 39 | } 40 | } 41 | 42 | else { 43 | let message = `${translation.err_wrong_entry} ❌

` 44 | message += `${translation.back_main_menu}` 45 | let reply = RichReply.createFor(roomId, event, message, message); 46 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 47 | } 48 | 49 | } 50 | } -------------------------------------------------------------------------------- /matrix/menu/discourse/discourse_2.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import { database_matrix_member } from '../../../module/database_matrix.js'; 3 | import Translation from '../../../module/translation.js'; 4 | import path from 'path'; 5 | 6 | export default { 7 | async exec({ meId, roomId, sender, name, checkRoom, roomIdOrAlias, body, replyBody, replySender, roomName, event_id, usersAdmin, RichReply, event, client }) { 8 | 9 | let __dirname = path.resolve(); 10 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 11 | let memberJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`)); 12 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 13 | 14 | if (memberJson?.verification_code === body) { 15 | 16 | let message = `${translation.active_bridge} ✅` 17 | let reply = RichReply.createFor(roomId, event, message, message); 18 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 19 | memberJson.access = true; 20 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`), memberJson, { spaces: '\t' }); 21 | await database_matrix_member({ sender: sender, menu: 'main' }).catch(error => console.log(error)); 22 | } 23 | 24 | else { 25 | let message = `${translation.err_verification_code}❌

` 26 | message += `${translation.back_main_menu}` 27 | let reply = RichReply.createFor(roomId, event, message, message); 28 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 29 | } 30 | 31 | } 32 | } -------------------------------------------------------------------------------- /matrix/menu/main.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import fs from 'fs-extra'; 3 | import moment from 'moment-hijri'; 4 | import sendFile from '../sendFile.js'; 5 | import get_latest_posts from '../../discourse/get_latest_posts.js'; 6 | import getCategories from '../../discourse/getCategories.js'; 7 | import { database_matrix_member } from '../../module/database_matrix.js'; 8 | import Translation from '../../module/translation.js'; 9 | import path from 'path'; 10 | moment.locale('en-EN'); 11 | 12 | export default { 13 | async exec({ meId, roomId, sender, name, checkRoom, roomIdOrAlias, body, replyBody, replySender, roomName, event_id, usersAdmin, RichReply, event, client }) { 14 | 15 | let __dirname = path.resolve(); 16 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 17 | let memberJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`)); 18 | let roomJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/${checkRoom}/${roomId}.json`)); 19 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 20 | 21 | if (body === '1' || body === '١' || body === 'get_latest_posts') { 22 | 23 | let get = await get_latest_posts().catch(error => console.log(error)); 24 | let response = await fetch(process.env.URL || config?.url + `/t/${get?.topic_slug}/${get?.topic_id}`, { method: 'GET' }); 25 | let data = await response?.text(); 26 | 27 | if (data.includes('itemprop="image"')) { 28 | 29 | let preview = data.split('itemprop="image" href="')[1]?.split('">')[0]; 30 | let caption = `${get?.topic_title}

`; 31 | caption += `${translation.writer}: ${get?.name}
`; 32 | caption += `${translation.date}: ${moment(get?.created_at).format('iYYYY/iM/iD')}
`; 33 | caption += `${translation.number_topic}: ${get?.topic_id}`; 34 | let reply = RichReply.createFor(roomId, event, caption, caption); 35 | 36 | await sendFile(roomId, preview, 'm.image', client).catch(error => console.log(error)); 37 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 38 | 39 | } 40 | 41 | else { 42 | let caption = `${get?.topic_title}

`; 43 | caption += `${translation.writer}: ${get?.name}
`; 44 | caption += `${translation.date}: ${moment(get?.created_at).format('iYYYY/iM/iD')}
`; 45 | caption += `${translation.number_topic}: ${get?.topic_id}`; 46 | let reply = RichReply.createFor(roomId, event, caption, caption); 47 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 48 | } 49 | 50 | } 51 | 52 | else if (body === '2' || body === '٢' || body === 'getCategories') { 53 | 54 | let Categories = await getCategories().catch(error => console.log(error)); 55 | let url = process.env.URL || config?.url; 56 | let title = process.env.DISCOURSE_FORUM_NAME || config?.discourse_forum_name; 57 | let message = `${translation.categories} ${title} ⬇️

` 58 | 59 | for (let item of Categories) { 60 | let id = item?.id; 61 | let name = item?.name; 62 | let topics_all_time = item?.topics_all_time; 63 | let slug = item?.slug 64 | message += `${name}
` 65 | message += `${translation.number_of_topics_posted}: ${topics_all_time}
` 66 | } 67 | 68 | let reply = RichReply.createFor(roomId, event, message, message); 69 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 70 | 71 | } 72 | 73 | else if (body === '3' || body === '٣' || body === 'CreatePosts') { 74 | 75 | if (memberJson?.access) { 76 | 77 | await database_matrix_member({ sender: sender, menu: 'CreatePosts_1' }).catch(error => console.log(error)); 78 | let Categories = await getCategories().catch(error => console.log(error)); 79 | let message = `${translation.send_category_id} ⬇️

` 80 | 81 | for (let item of Categories) { 82 | message += `▪ ${item?.name}
` 83 | message += `▪ ${translation.id}: ${item?.id}

` 84 | } 85 | let reply = RichReply.createFor(roomId, event, message, message); 86 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 87 | } 88 | 89 | else { 90 | let message = `${translation.first_link_your_account_matrix} ❌` 91 | let reply = RichReply.createFor(roomId, event, message, message); 92 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 93 | } 94 | 95 | } 96 | 97 | else if (body === '4' || body === '٤' || body === 'sendComment') { 98 | 99 | if (memberJson?.access) { 100 | 101 | await database_matrix_member({ sender: sender, menu: 'sendComment_1' }).catch(error => console.log(error)); 102 | let message = `${translation.send_id_or_url_topic} 🌐` 103 | let reply = RichReply.createFor(roomId, event, message, message); 104 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 105 | } 106 | 107 | else { 108 | let message = `${translation.first_link_your_account_matrix} ❌` 109 | let reply = RichReply.createFor(roomId, event, message, message); 110 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 111 | } 112 | 113 | } 114 | 115 | else if (body === '5' || body === '٥' || body === 'sendMessagePrivate') { 116 | 117 | if (memberJson?.access) { 118 | 119 | await database_matrix_member({ sender: sender, menu: 'sendMessagePrivate_1' }).catch(error => console.log(error)); 120 | let message = `${translation.username_send_to} 📝` 121 | let reply = RichReply.createFor(roomId, event, message, message); 122 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 123 | } 124 | 125 | else { 126 | let message = `${translation.first_link_your_account_matrix} ❌` 127 | let reply = RichReply.createFor(roomId, event, message, message); 128 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 129 | } 130 | 131 | } 132 | 133 | else if (body === '6' || body === '٦' || body === 'discourse') { 134 | 135 | if (checkRoom === 'direct') { 136 | 137 | if (memberJson?.access) { 138 | 139 | let message = `${translation.err_linked_to_discourse} ⁉️` 140 | let reply = RichReply.createFor(roomId, event, message, message); 141 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 142 | } 143 | 144 | else { 145 | 146 | await database_matrix_member({ sender: sender, menu: 'discourse_1' }).catch(error => console.log(error)); 147 | let message = `${translation.enter_your_username_discourse} 📝

${translation.sign_}` 148 | let reply = RichReply.createFor(roomId, event, message, message); 149 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 150 | 151 | } 152 | 153 | } 154 | 155 | else { 156 | let message = `${translation.send_me_private_message_to_link_your_account} ⚠️` 157 | let reply = RichReply.createFor(roomId, event, message, message); 158 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 159 | } 160 | 161 | } 162 | 163 | else if (body === '7' || body === '٧' || body === 'activation') { 164 | 165 | if (checkRoom === 'room') { 166 | if (roomJson?.evenPost) { 167 | 168 | let message = `${translation.err_active_in_the_chat} ⁉️`; 169 | let reply = RichReply.createFor(roomId, event, message, message); 170 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 171 | 172 | } 173 | 174 | else { 175 | 176 | if (usersAdmin?.includes(sender)) { 177 | 178 | await getCategories().then(async e => { 179 | 180 | let message = `${translation.category_id}

` 181 | for (let item of e) { 182 | 183 | message += `▪ ${item?.name}
` 184 | message += `▪ ${translation.id}: ${item?.id}

` 185 | 186 | } 187 | message += `${translation.category_id_all}` 188 | let reply = RichReply.createFor(roomId, event, message, message); 189 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 190 | await database_matrix_member({ sender: sender, menu: 'activation' }).catch(error => console.log(error)); 191 | }); 192 | 193 | } 194 | 195 | else { 196 | 197 | let message = `${translation.admin_activate} ❌`; 198 | let reply = RichReply.createFor(roomId, event, message, message); 199 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 200 | } 201 | 202 | } 203 | } 204 | 205 | else if (checkRoom === 'direct') { 206 | 207 | if (roomJson?.evenPost) { 208 | 209 | let message = `${translation.err_active_in_the_chat} ⁉️`; 210 | let reply = RichReply.createFor(roomId, event, message, message); 211 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 212 | 213 | } 214 | 215 | else { 216 | 217 | let message = `${translation.active_bot} ✅`; 218 | let reply = RichReply.createFor(roomId, event, message, message); 219 | roomJson.evenPost = true; 220 | roomJson.categories = 0; 221 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/${checkRoom}/${roomId}.json`), roomJson, { spaces: '\t' }); 222 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 223 | } 224 | 225 | } 226 | 227 | } 228 | 229 | } 230 | } -------------------------------------------------------------------------------- /matrix/menu/sendComment/sendComment_1.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import { database_matrix_member } from '../../../module/database_matrix.js'; 3 | import Translation from '../../../module/translation.js'; 4 | import path from 'path'; 5 | 6 | export default { 7 | async exec({ meId, roomId, sender, name, checkRoom, roomIdOrAlias, body, replyBody, replySender, roomName, event_id, usersAdmin, RichReply, event, client }) { 8 | 9 | let __dirname = path.resolve(); 10 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 11 | let memberJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`)); 12 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 13 | 14 | if (body.includes(process.env.URL || config?.url)) { 15 | 16 | let message = `${translation.write_comment} 📝` 17 | 18 | let sp = body?.split(''); 19 | 20 | if (sp[sp?.length - 1] === '/') { 21 | 22 | let reply = RichReply.createFor(roomId, event, message, message); 23 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 24 | memberJson.sendComment_1 = Number(body?.split('/')?.slice(-2)[0]); 25 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`), memberJson, { spaces: '\t' }); 26 | await database_matrix_member({ sender: sender, menu: 'sendComment_2' }).catch(error => console.log(error)); 27 | 28 | } 29 | 30 | else { 31 | 32 | let reply = RichReply.createFor(roomId, event, message, message); 33 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 34 | memberJson.sendComment_1 = Number(body?.split('/')?.slice(-1)[0]); 35 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`), memberJson, { spaces: '\t' }); 36 | await database_matrix_member({ sender: sender, menu: 'sendComment_2' }).catch(error => console.log(error)); 37 | 38 | } 39 | } 40 | 41 | else if (!isNaN(body)) { 42 | 43 | let message = `${translation.write_comment} 📝` 44 | let reply = RichReply.createFor(roomId, event, message, message); 45 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 46 | memberJson.sendComment_1 = Number(body); 47 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`), memberJson, { spaces: '\t' }); 48 | await database_matrix_member({ sender: sender, menu: 'sendComment_2' }).catch(error => console.log(error)); 49 | } 50 | 51 | else { 52 | let message = `${translation.err_wrong_entry} ❌

` 53 | message += `${translation.back_main_menu}` 54 | let reply = RichReply.createFor(roomId, event, message, message); 55 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 56 | } 57 | 58 | } 59 | } -------------------------------------------------------------------------------- /matrix/menu/sendComment/sendComment_2.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import { database_matrix_member } from '../../../module/database_matrix.js'; 3 | import sendComment from '../../../discourse/sendComment.js'; 4 | import Translation from '../../../module/translation.js'; 5 | import path from 'path'; 6 | 7 | export default { 8 | async exec({ meId, roomId, sender, name, checkRoom, roomIdOrAlias, body, replyBody, replySender, roomName, event_id, usersAdmin, RichReply, event, client }) { 9 | 10 | let __dirname = path.resolve(); 11 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 12 | let memberJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`)); 13 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 14 | 15 | if (body) { 16 | 17 | let raw = body; 18 | let topic_id = memberJson?.sendComment_1 19 | let seCo = await sendComment(memberJson?.discourse_username, topic_id, raw); 20 | 21 | if (seCo?.errors) { 22 | for (let item of seCo?.errors) { 23 | let reply = RichReply.createFor(roomId, event, item, item); 24 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 25 | } 26 | } 27 | 28 | else { 29 | let topic_slug = seCo?.topic_slug 30 | let topic_id = seCo?.topic_id 31 | let post_number = seCo?.post_number 32 | let message = `${translation.comment_posted} ✅ ${post_number}` 33 | let reply = RichReply.createFor(roomId, event, message, message); 34 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 35 | } 36 | 37 | await database_matrix_member({ sender: sender, menu: 'main' }).catch(error => console.log(error)); 38 | } 39 | 40 | else { 41 | let message = `${translation.err_wrong_entry} ❌

` 42 | message += `${translation.back_main_menu}` 43 | let reply = RichReply.createFor(roomId, event, message, message); 44 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 45 | } 46 | 47 | } 48 | } -------------------------------------------------------------------------------- /matrix/menu/sendMessagePrivate/sendMessagePrivate_1.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import { database_matrix_member } from '../../../module/database_matrix.js'; 3 | import Translation from '../../../module/translation.js'; 4 | import path from 'path'; 5 | 6 | export default { 7 | async exec({ meId, roomId, sender, name, checkRoom, roomIdOrAlias, body, replyBody, replySender, roomName, event_id, usersAdmin, RichReply, event, client }) { 8 | 9 | let __dirname = path.resolve(); 10 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 11 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 12 | 13 | if (body) { 14 | 15 | let memberJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`)); 16 | let message = `${translation.title_message_private} 📝` 17 | let reply = RichReply.createFor(roomId, event, message, message); 18 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 19 | memberJson.sendMessagePrivate_1 = body; 20 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`), memberJson, { spaces: '\t' }); 21 | await database_matrix_member({ sender: sender, menu: 'sendMessagePrivate_2' }).catch(error => console.log(error)); 22 | } 23 | 24 | else { 25 | let message = `${translation.err_wrong_entry} ❌

` 26 | message += `${translation.back_main_menu}` 27 | let reply = RichReply.createFor(roomId, event, message, message); 28 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 29 | } 30 | 31 | } 32 | } -------------------------------------------------------------------------------- /matrix/menu/sendMessagePrivate/sendMessagePrivate_2.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import { database_matrix_member } from '../../../module/database_matrix.js'; 3 | import Translation from '../../../module/translation.js'; 4 | import path from 'path'; 5 | 6 | export default { 7 | async exec({ meId, roomId, sender, name, checkRoom, roomIdOrAlias, body, replyBody, replySender, roomName, event_id, usersAdmin, RichReply, event, client }) { 8 | 9 | let __dirname = path.resolve(); 10 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 11 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 12 | 13 | if (body) { 14 | 15 | let memberJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`)); 16 | let message = `${translation.content_message_private} 📝` 17 | let reply = RichReply.createFor(roomId, event, message, message); 18 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 19 | memberJson.sendMessagePrivate_2 = body; 20 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`), memberJson, { spaces: '\t' }); 21 | await database_matrix_member({ sender: sender, menu: 'sendMessagePrivate_3' }).catch(error => console.log(error)); 22 | } 23 | 24 | else { 25 | let message = `${translation.err_wrong_entry} ❌

` 26 | message += `${translation.back_main_menu}` 27 | let reply = RichReply.createFor(roomId, event, message, message); 28 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 29 | } 30 | 31 | } 32 | } -------------------------------------------------------------------------------- /matrix/menu/sendMessagePrivate/sendMessagePrivate_3.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import { database_matrix_member } from '../../../module/database_matrix.js'; 3 | import sendMessagePrivate from '../../../discourse/sendMessagePrivate.js'; 4 | import Translation from '../../../module/translation.js'; 5 | import path from 'path'; 6 | 7 | export default { 8 | async exec({ meId, roomId, sender, name, checkRoom, roomIdOrAlias, body, replyBody, replySender, roomName, event_id, usersAdmin, RichReply, event, client }) { 9 | 10 | let __dirname = path.resolve(); 11 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 12 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 13 | 14 | if (body) { 15 | 16 | let memberJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`)); 17 | let title = memberJson?.sendMessagePrivate_2 18 | let raw = body 19 | let sendTo = memberJson?.sendMessagePrivate_1 20 | let sePr = await sendMessagePrivate(memberJson?.discourse_username, title, raw, sendTo).catch(error => console.log(error)); 21 | 22 | if (sePr?.errors) { 23 | 24 | for (let item of sePr?.errors) { 25 | let reply = RichReply.createFor(roomId, event, item, item); 26 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 27 | } 28 | } 29 | 30 | else { 31 | let message = `${translation.message_sent} ✅` 32 | let reply = RichReply.createFor(roomId, event, message, message); 33 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 34 | await database_matrix_member({ sender: sender, menu: 'main' }).catch(error => console.log(error)); 35 | } 36 | 37 | } 38 | 39 | else { 40 | let message = `${translation.err_wrong_entry} ❌

` 41 | message += `${translation.back_main_menu}` 42 | let reply = RichReply.createFor(roomId, event, message, message); 43 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 44 | } 45 | 46 | } 47 | } -------------------------------------------------------------------------------- /matrix/sendFile.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import getBuffer from '../module/getBuffer.js'; 3 | import path from 'path'; 4 | 5 | export default async function sendFile(roomId, filePath, type, client) { 6 | 7 | let worksBuffer 8 | let checkfilePath = filePath.includes('https') || filePath.includes('http'); 9 | let __dirname = path.resolve(); 10 | if (checkfilePath) { 11 | 12 | let buffer = await getBuffer(filePath).catch(error => console.log(error)); 13 | fs.writeFileSync(path.join(__dirname, '/preview.png', Buffer.from(buffer))); 14 | worksBuffer = fs.readFileSync(path.join(__dirname, '/preview.png')); 15 | } 16 | 17 | else { 18 | worksBuffer = fs.readFileSync(filePath); 19 | } 20 | 21 | let encrypted = await client.crypto.encryptMedia(Buffer.from(worksBuffer)); 22 | let mxc = await client.uploadContent(encrypted.buffer); 23 | 24 | await client.sendMessage(roomId, { 25 | msgtype: type, 26 | body: "preview", 27 | file: { 28 | url: mxc, 29 | ...encrypted.file, 30 | }, 31 | }) 32 | } -------------------------------------------------------------------------------- /matrix/start.js: -------------------------------------------------------------------------------- 1 | import { database_matrix_member } from '../module/database_matrix.js'; 2 | import fs from 'fs-extra'; 3 | import Translation from '../module/translation.js'; 4 | import path from 'path'; 5 | 6 | export default async function start(roomId, sender, name, body, event, RichReply, client) { 7 | 8 | if (body === 'start' || body === '#') { 9 | 10 | await database_matrix_member({ sender: sender, menu: 'main' }).catch(error => console.log(error)); 11 | 12 | let __dirname = path.resolve(); 13 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 14 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 15 | let message = `${translation.welcome} ${name} ${translation.in_the_bridge} ${process.env.DISCOURSE_FORUM_NAME || config?.discourse_forum_name} 👋
` 16 | message += `${translation.send_number_or_name_service}

` 17 | message += `▪ ${translation.view_last_topic} 📄
1- get_latest_posts

` 18 | message += `▪ ${translation.view_categories} ⬇️
2- getCategories

` 19 | message += `▪ ${translation.write_new_topic} 📝
3- CreatePosts

` 20 | message += `▪ ${translation.write_new_comment} 💬
4- sendComment

` 21 | message += `▪ ${translation.send_message_private} 🔒
5- sendMessagePrivate

` 22 | message += `▪ ${translation.link_your_account_to} ${process.env.DISCOURSE_FORUM_NAME || config?.discourse_forum_name}
6- discourse

` 23 | message += `▪ ${translation.activate_the_bot}
7- activation` 24 | let reply = RichReply.createFor(roomId, event, message, message); 25 | await client.sendMessage(roomId, reply).catch(error => console.log(error)); 26 | 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /module/Crate.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | 4 | export default async function Crate() { 5 | 6 | let __dirname = path.resolve(); 7 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 8 | let datapath = fs.existsSync(process.env.DATAPATH || config?.dataPath); 9 | let database = fs.existsSync(path.join(process.env.DATAPATH || config?.dataPath, "/database")); 10 | let telegram = fs.existsSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/telegram")); 11 | let chat = fs.existsSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/telegram/chat")); 12 | let from = fs.existsSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/telegram/from")); 13 | let matrix = fs.existsSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/matrix")); 14 | let room = fs.existsSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/matrix/room")); 15 | let direct = fs.existsSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/matrix/direct")); 16 | let member = fs.existsSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/matrix/member")); 17 | let EventPosts = fs.existsSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/EventPosts.json")); 18 | 19 | if (datapath === false) { 20 | fs.mkdirSync(process.env.DATAPATH || config?.dataPath, { recursive: true }); 21 | } 22 | if (database === false) { 23 | fs.mkdirSync(path.join(process.env.DATAPATH || config?.dataPath, "/database"), { recursive: true }); 24 | } 25 | if (telegram === false) { 26 | fs.mkdirSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/telegram"), { recursive: true }); 27 | } 28 | if (chat === false) { 29 | fs.mkdirSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/telegram/chat"), { recursive: true }); 30 | } 31 | if (from === false) { 32 | fs.mkdirSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/telegram/from"), { recursive: true }); 33 | } 34 | if (matrix === false) { 35 | fs.mkdirSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/matrix"), { recursive: true }); 36 | } 37 | if (room === false) { 38 | fs.mkdirSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/matrix/room"), { recursive: true }); 39 | } 40 | if (direct === false) { 41 | fs.mkdirSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/matrix/direct"), { recursive: true }); 42 | } 43 | if (member === false) { 44 | fs.mkdirSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/matrix/member"), { recursive: true }); 45 | } 46 | if (EventPosts === false) { 47 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/EventPosts.json"), []); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /module/database_matrix.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import random from './random.js'; 3 | import path from 'path'; 4 | 5 | export async function database_matrix({ roomId: roomId, sender: sender, name: name, checkRoom: checkRoom, roomIdOrAlias: roomIdOrAlias }) { 6 | 7 | let __dirname = path.resolve(); 8 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 9 | let create_db_user = fs.existsSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/${checkRoom}/${roomId}.json`)); 10 | 11 | if (create_db_user === false) { 12 | 13 | if (checkRoom === 'room') { 14 | 15 | let opj = { 16 | roomId: roomId, 17 | name: name, 18 | checkRoom: checkRoom, 19 | roomIdOrAlias: roomIdOrAlias, 20 | evenPost: false, 21 | categories: null 22 | } 23 | 24 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/${checkRoom}/${roomId}.json`), opj, { spaces: '\t' }); 25 | } 26 | 27 | else if (checkRoom === 'direct') { 28 | 29 | let opj = { 30 | roomId: roomId, 31 | sender: sender, 32 | name: name, 33 | checkRoom: checkRoom, 34 | evenPost: false, 35 | categories: null, 36 | } 37 | 38 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/${checkRoom}/${roomId}.json`), opj, { spaces: '\t' }); 39 | 40 | } 41 | 42 | } 43 | 44 | else { 45 | 46 | let db_user = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/${checkRoom}/${roomId}.json`)); 47 | db_user.name = name; 48 | 49 | if (checkRoom === 'direct') { 50 | db_user.sender = sender; 51 | } 52 | 53 | else if (checkRoom === 'room') { 54 | db_user.roomIdOrAlias = roomIdOrAlias; 55 | } 56 | 57 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/${checkRoom}/${roomId}.json`), db_user, { spaces: '\t' }); 58 | } 59 | 60 | } 61 | 62 | 63 | export async function database_matrix_member({ sender: sender, name: name, menu: menu }) { 64 | let __dirname = path.resolve(); 65 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 66 | 67 | if (sender && name) { 68 | 69 | let create_db_user = fs.existsSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`)); 70 | 71 | if (create_db_user === false) { 72 | 73 | let opj = { 74 | sender: sender, 75 | name: name, 76 | verification_code: random(10), 77 | access: false, 78 | menu: 'main' 79 | } 80 | 81 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`), opj, { spaces: '\t' }); 82 | } 83 | 84 | else { 85 | 86 | let db_user = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`)); 87 | db_user.name = name; 88 | db_user.sender = sender; 89 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`), db_user, { spaces: '\t' }); 90 | } 91 | } 92 | 93 | else { 94 | let db_user = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`)); 95 | db_user.menu = menu; 96 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`), db_user, { spaces: '\t' }); 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /module/database_telegram.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import random from './random.js'; 3 | import path from 'path'; 4 | 5 | export default async function database_telegram(id, username, name, type, message_id) { 6 | 7 | let __dirname = path.resolve(); 8 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 9 | let create_db_user = fs.existsSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/${type}/${id}.json`)); 10 | 11 | if (create_db_user === false) { 12 | 13 | if (type === 'chat') { 14 | 15 | let opj = { 16 | id: id, 17 | username: username, 18 | name: name, 19 | type: type, 20 | evenPost: false, 21 | categories: null, 22 | message_id: message_id 23 | } 24 | 25 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/${type}/${id}.json`), opj, { spaces: '\t' }); 26 | } 27 | 28 | else if (type === 'from') { 29 | 30 | let opj = { 31 | id: id, 32 | username: username, 33 | name: name, 34 | type: type, 35 | evenPost: false, 36 | categories: null, 37 | message_id: message_id, 38 | verification_code: random(10), 39 | access: false 40 | } 41 | 42 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/${type}/${id}.json`), opj, { spaces: '\t' }); 43 | 44 | } 45 | 46 | 47 | } 48 | 49 | else { 50 | 51 | let db_user = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/${type}/${id}.json`)); 52 | db_user.username = username; 53 | db_user.name = name; 54 | if (message_id) { 55 | db_user.message_id = message_id; 56 | } 57 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/${type}/${id}.json`), db_user, { spaces: '\t' }); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /module/getBuffer.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | export default async function getBuffer(url) { 4 | 5 | let response = await fetch(url, { method: 'GET' }); 6 | let data = await response?.arrayBuffer(); 7 | 8 | return data 9 | } 10 | -------------------------------------------------------------------------------- /module/getMenu.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | 4 | export default async function getMenu(sender) { 5 | 6 | let __dirname = path.resolve(); 7 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 8 | let user = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/member/${sender}.json`)); 9 | 10 | return user?.menu 11 | 12 | } -------------------------------------------------------------------------------- /module/getUserTelegram.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | 4 | export default async function getUserTelegram() { 5 | 6 | let __dirname = path.resolve(); 7 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 8 | let chat = fs.readdirSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/telegram/chat")); 9 | let from = fs.readdirSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/telegram/from")); 10 | let array = [] 11 | 12 | for (let item of chat) { 13 | 14 | let id = item.split('.json')[0] 15 | let chatJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/chat/${item}`)); 16 | if (chatJson?.evenPost) { 17 | 18 | array.push(id); 19 | } 20 | } 21 | 22 | for (let item of from) { 23 | 24 | let id = item.split('.json')[0] 25 | let fromJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${item}`)); 26 | if (fromJson?.evenPost) { 27 | 28 | array.push(id); 29 | } 30 | } 31 | 32 | return array 33 | } -------------------------------------------------------------------------------- /module/getrRoomMatrix.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | 4 | /** 5 | @param {string} type - room or direct 6 | */ 7 | 8 | export default async function getrRoomMatrix(type) { 9 | 10 | let __dirname = path.resolve(); 11 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 12 | let room = fs.readdirSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/matrix/room")); 13 | let direct = fs.readdirSync(path.join(process.env.DATAPATH || config?.dataPath, "/database/matrix/direct")); 14 | let arrayRoomId = [] 15 | let arrayDirectId = [] 16 | let arrayAllId = [] 17 | let arrayRoomJson = [] 18 | let arrayDirectJson = [] 19 | let arrayAllJson = [] 20 | 21 | for (let item of room) { 22 | 23 | let id = item.split('.json')[0] 24 | let roomJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/room/${item}`)); 25 | if (roomJson?.evenPost) { 26 | 27 | arrayRoomId.push(id); 28 | arrayRoomJson.push(roomJson); 29 | arrayAllId.push(id); 30 | arrayAllJson.push(roomJson); 31 | } 32 | } 33 | 34 | for (let item of direct) { 35 | 36 | let id = item.split('.json')[0] 37 | let directJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/matrix/direct/${item}`)); 38 | if (directJson?.evenPost) { 39 | 40 | arrayDirectId.push(id); 41 | arrayDirectJson.push(directJson); 42 | arrayAllId.push(id); 43 | arrayAllJson.push(directJson); 44 | } 45 | } 46 | 47 | if (type === 'room') { 48 | return { 49 | id: arrayRoomId, 50 | array: arrayRoomJson 51 | } 52 | } 53 | 54 | else if (type === 'direct') { 55 | return { 56 | id: arrayDirectId, 57 | array: arrayDirectJson 58 | } 59 | } 60 | 61 | else if (type === 'all') { 62 | return { 63 | id: arrayAllId, 64 | array: arrayAllJson 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /module/menu.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import main from '../matrix/menu/main.js'; 3 | import CreatePosts_1 from '../matrix/menu/CreatePosts/CreatePosts_1.js'; 4 | import CreatePosts_2 from '../matrix/menu/CreatePosts/CreatePosts_2.js'; 5 | import CreatePosts_3 from '../matrix/menu/CreatePosts/CreatePosts_3.js'; 6 | import activation from '../matrix/menu/activation.js'; 7 | import sendComment_1 from '../matrix/menu/sendComment/sendComment_1.js'; 8 | import sendComment_2 from '../matrix/menu/sendComment/sendComment_2.js'; 9 | import sendMessagePrivate_1 from '../matrix/menu/sendMessagePrivate/sendMessagePrivate_1.js'; 10 | import sendMessagePrivate_2 from '../matrix/menu/sendMessagePrivate/sendMessagePrivate_2.js'; 11 | import sendMessagePrivate_3 from '../matrix/menu/sendMessagePrivate/sendMessagePrivate_3.js'; 12 | import discourse_1 from '../matrix/menu/discourse/discourse_1.js'; 13 | import discourse_2 from '../matrix/menu/discourse/discourse_2.js'; 14 | import Translation from './translation.js'; 15 | import path from 'path'; 16 | 17 | let __dirname = path.resolve(); 18 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 19 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 20 | 21 | export default { 22 | main: { 23 | name: `${translation.main_menu}`, 24 | module: main 25 | }, 26 | CreatePosts_1: { 27 | name: `${translation.write_new_topic} 📝 #1`, 28 | module: CreatePosts_1 29 | }, 30 | CreatePosts_2: { 31 | name: `${translation.write_new_topic} 📝 #2`, 32 | module: CreatePosts_2 33 | }, 34 | CreatePosts_3: { 35 | name: `${translation.write_new_topic} 📝 #3`, 36 | module: CreatePosts_3 37 | }, 38 | activation: { 39 | name: `${translation.activate_the_bot}✅`, 40 | module: activation 41 | }, 42 | sendComment_1: { 43 | name: `${translation.write_new_comment} 💬 #1`, 44 | module: sendComment_1 45 | }, 46 | sendComment_2: { 47 | name: `${translation.write_new_comment} 💬 #2`, 48 | module: sendComment_2 49 | }, 50 | sendMessagePrivate_1: { 51 | name: `${translation.send_message_private} 🔒 #1`, 52 | module: sendMessagePrivate_1 53 | }, 54 | sendMessagePrivate_2: { 55 | name: `${translation.send_message_private} 🔒 #2`, 56 | module: sendMessagePrivate_2 57 | }, 58 | sendMessagePrivate_3: { 59 | name: `${translation.send_message_private} 🔒 #3`, 60 | module: sendMessagePrivate_3 61 | }, 62 | discourse_1: { 63 | name: `${translation.first_link_your_account} discourse✅ #1`, 64 | module: discourse_1 65 | }, 66 | discourse_2: { 67 | name: `${translation.first_link_your_account} discourse ✅ #2`, 68 | module: discourse_2 69 | } 70 | } -------------------------------------------------------------------------------- /module/random.js: -------------------------------------------------------------------------------- 1 | export default function random(number) { 2 | 3 | let chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; 4 | let res = ""; 5 | for (let i = 0; i < number; i++) { 6 | 7 | let id = Math.ceil(Math.random() * 35); 8 | res += chars[id]; 9 | 10 | } 11 | 12 | return res; 13 | } -------------------------------------------------------------------------------- /module/translation.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | 4 | export default async function Translation(language) { 5 | 6 | let __dirname = path.resolve(); 7 | let ExistsLanguage = fs.existsSync(path.join(__dirname, `/translation/${language?.toLowerCase()}.json`)); 8 | 9 | if (ExistsLanguage) { 10 | 11 | let ReadLanguage = fs.readJsonSync(path.join(__dirname, `/translation/${language?.toLowerCase()}.json`)); 12 | 13 | return { 14 | number_topic: ReadLanguage?.number_topic, 15 | comment_posted: ReadLanguage?.comment_posted, 16 | first_link_your_account: ReadLanguage?.first_link_your_account, 17 | first_link_your_account_matrix: ReadLanguage?.first_link_your_account_matrix, 18 | writer: ReadLanguage?.writer, 19 | date: ReadLanguage?.date, 20 | publication_time: ReadLanguage?.publication_time, 21 | err_active_in_the_chat: ReadLanguage?.err_active_in_the_chat, 22 | active_bot: ReadLanguage?.active_bot, 23 | admin_activate: ReadLanguage?.admin_activate, 24 | category_id: ReadLanguage?.category_id, 25 | category_id_all: ReadLanguage?.category_id_all, 26 | id: ReadLanguage?.id, 27 | err_wrong_entry: ReadLanguage?.err_wrong_entry, 28 | send_category_id: ReadLanguage?.send_category_id, 29 | topic_title: ReadLanguage?.topic_title, 30 | topic_content: ReadLanguage?.topic_content, 31 | send_me_private_message_to_link_your_account: ReadLanguage?.send_me_private_message_to_link_your_account, 32 | err_linked_to_discourse: ReadLanguage?.err_linked_to_discourse, 33 | enter_your_username_discourse: ReadLanguage?.enter_your_username_discourse, 34 | sign_: ReadLanguage?.sign_, 35 | verification_code: ReadLanguage?.verification_code, 36 | verification_code_for: ReadLanguage?.verification_code_for, 37 | send_verification_code: ReadLanguage?.send_verification_code, 38 | write_verification_code: ReadLanguage?.write_verification_code, 39 | active_bridge: ReadLanguage?.active_bridge, 40 | err_verification_code: ReadLanguage?.err_verification_code, 41 | send_id_or_url_topic: ReadLanguage?.send_id_or_url_topic, 42 | write_comment: ReadLanguage?.write_comment, 43 | username_send_to: ReadLanguage?.username_send_to, 44 | title_message_private: ReadLanguage?.title_message_private, 45 | content_message_private: ReadLanguage?.content_message_private, 46 | message_sent: ReadLanguage?.message_sent, 47 | categories: ReadLanguage?.categories, 48 | number_of_topics_posted: ReadLanguage?.number_of_topics_posted, 49 | welcome: ReadLanguage?.welcome, 50 | in_the_bridge: ReadLanguage?.in_the_bridge, 51 | view_last_topic: ReadLanguage?.view_last_topic, 52 | view_categories: ReadLanguage?.view_categories, 53 | write_new_topic: ReadLanguage?.write_new_topic, 54 | write_new_comment: ReadLanguage?.write_new_comment, 55 | send_message_private: ReadLanguage?.send_message_private, 56 | link_your_account_to: ReadLanguage?.link_your_account_to, 57 | activate_the_bot: ReadLanguage?.activate_the_bot, 58 | send_number_or_name_service: ReadLanguage?.send_number_or_name_service, 59 | back_main_menu: ReadLanguage?.back_main_menu, 60 | wrong_data_entered: ReadLanguage?.wrong_data_entered, 61 | main_menu: ReadLanguage?.main_menu, 62 | back_main_menu_2: ReadLanguage?.back_main_menu_2 63 | } 64 | 65 | } 66 | 67 | else { 68 | let ReadLanguage = fs.readJsonSync(path.join(__dirname, `/translation/ar.json`)); 69 | 70 | return { 71 | number_topic: ReadLanguage?.number_topic, 72 | comment_posted: ReadLanguage?.comment_posted, 73 | first_link_your_account: ReadLanguage?.first_link_your_account, 74 | first_link_your_account_matrix: ReadLanguage?.first_link_your_account_matrix, 75 | writer: ReadLanguage?.writer, 76 | date: ReadLanguage?.date, 77 | publication_time: ReadLanguage?.publication_time, 78 | err_active_in_the_chat: ReadLanguage?.err_active_in_the_chat, 79 | active_bot: ReadLanguage?.active_bot, 80 | admin_activate: ReadLanguage?.admin_activate, 81 | category_id: ReadLanguage?.category_id, 82 | category_id_all: ReadLanguage?.category_id_all, 83 | id: ReadLanguage?.id, 84 | err_wrong_entry: ReadLanguage?.err_wrong_entry, 85 | send_category_id: ReadLanguage?.send_category_id, 86 | topic_title: ReadLanguage?.topic_title, 87 | topic_content: ReadLanguage?.topic_content, 88 | send_me_private_message_to_link_your_account: ReadLanguage?.send_me_private_message_to_link_your_account, 89 | err_linked_to_discourse: ReadLanguage?.err_linked_to_discourse, 90 | enter_your_username_discourse: ReadLanguage?.enter_your_username_discourse, 91 | sign_: ReadLanguage?.sign_, 92 | verification_code: ReadLanguage?.verification_code, 93 | verification_code_for: ReadLanguage?.verification_code_for, 94 | send_verification_code: ReadLanguage?.send_verification_code, 95 | write_verification_code: ReadLanguage?.write_verification_code, 96 | active_bridge: ReadLanguage?.active_bridge, 97 | err_verification_code: ReadLanguage?.err_verification_code, 98 | send_id_or_url_topic: ReadLanguage?.send_id_or_url_topic, 99 | write_comment: ReadLanguage?.write_comment, 100 | username_send_to: ReadLanguage?.username_send_to, 101 | title_message_private: ReadLanguage?.title_message_private, 102 | content_message_private: ReadLanguage?.content_message_private, 103 | message_sent: ReadLanguage?.message_sent, 104 | categories: ReadLanguage?.categories, 105 | number_of_topics_posted: ReadLanguage?.number_of_topics_posted, 106 | welcome: ReadLanguage?.welcome, 107 | in_the_bridge: ReadLanguage?.in_the_bridge, 108 | view_last_topic: ReadLanguage?.view_last_topic, 109 | view_categories: ReadLanguage?.view_categories, 110 | write_new_topic: ReadLanguage?.write_new_topic, 111 | write_new_comment: ReadLanguage?.write_new_comment, 112 | send_message_private: ReadLanguage?.send_message_private, 113 | link_your_account_to: ReadLanguage?.link_your_account_to, 114 | activate_the_bot: ReadLanguage?.activate_the_bot, 115 | send_number_or_name_service: ReadLanguage?.send_number_or_name_service, 116 | back_main_menu: ReadLanguage?.back_main_menu, 117 | wrong_data_entered: ReadLanguage?.wrong_data_entered, 118 | main_menu: ReadLanguage?.main_menu, 119 | back_main_menu_2: ReadLanguage?.back_main_menu_2 120 | } 121 | } 122 | 123 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discourse-chat-bridge", 3 | "description": "bridge between discourse and social media programs", 4 | "version": "1.0.0", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "node index.js", 9 | "generate_matrix_token": "node ./matrix/generate_matrix_token.js" 10 | }, 11 | "author": "aosus", 12 | "license": "agpl-3.0", 13 | "dependencies": { 14 | "dotenv": "^16.3.1", 15 | "fs-extra": "^11.1.1", 16 | "matrix-bot-sdk": "^0.7.1", 17 | "moment-hijri": "^2.1.2", 18 | "node-fetch": "^3.3.1", 19 | "telegraf": "^4.12.2" 20 | }, 21 | "devDependencies": { 22 | "@types/fs-extra": "^11.0.1", 23 | "@types/moment-hijri": "^2.1.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /telegram/EventPosts_.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import fs from 'fs-extra'; 3 | import moment from 'moment-hijri'; 4 | import EventPosts from '../discourse/EventPosts.js'; 5 | import getUserTelegram from '../module/getUserTelegram.js'; 6 | import Translation from '../module/translation.js'; 7 | import path from 'path'; 8 | moment.locale('en-EN'); 9 | 10 | export default async function EventPosts_(client) { 11 | 12 | await EventPosts(async e => { 13 | 14 | try { 15 | 16 | let __dirname = path.resolve(); 17 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 18 | let name = e?.name; 19 | let username = e?.username; 20 | let created_at = e?.created_at; 21 | let post_number = e?.post_number; 22 | let post_type = e?.post_type; 23 | let topic_id = e?.topic_id; 24 | let topic_slug = e?.topic_slug; 25 | let topic_title = e?.topic_title; 26 | let category_id = e?.category_id; 27 | let cooked = e?.cooked; 28 | let raw = e?.raw; 29 | let response = await fetch(process.env.URL || config?.url + `/t/${topic_slug}/${topic_id}`, { method: 'GET' }); 30 | let data = await response.text(); 31 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 32 | 33 | if (data.includes('itemprop="image"')) { 34 | 35 | let getUser = await getUserTelegram(); 36 | 37 | for (let item of getUser) { 38 | 39 | let fromJson = fs.existsSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${item}.json`)); 40 | let chat = fromJson ? fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${item}.json`)) : fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/chat/${item}.json`)); 41 | 42 | if (chat?.categories === category_id) { 43 | let preview = data.split('itemprop="image" href="')[1]?.split('">')[0]; 44 | let caption = `${topic_title} \n\n`; 45 | caption += `${translation.writer}: ${name} \n`; 46 | caption += `${translation.publication_time}: ${moment(created_at).format('LT')}\n`; 47 | caption += `${translation.number_topic}: ${topic_id}`; 48 | await client.telegram.sendPhoto(item, { url: preview }, { caption: caption, parse_mode: 'HTML', disable_web_page_preview: true }); 49 | } 50 | 51 | else if (chat?.categories === 0) { 52 | let preview = data.split('itemprop="image" href="')[1]?.split('">')[0]; 53 | let caption = `${topic_title} \n\n`; 54 | caption += `${translation.writer}: ${name} \n`; 55 | caption += `${translation.publication_time}: ${moment(created_at).format('LT')}\n`; 56 | caption += `${translation.number_topic}: ${topic_id}`; 57 | await client.telegram.sendPhoto(item, { url: preview }, { caption: caption, parse_mode: 'HTML', disable_web_page_preview: true }); 58 | } 59 | } 60 | 61 | } 62 | 63 | else { 64 | 65 | let getUser = await getUserTelegram(); 66 | 67 | for (let item of getUser) { 68 | 69 | let fromJson = fs.existsSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${item}.json`)); 70 | let chat = fromJson ? fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${item}.json`)) : fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/chat/${item}.json`)); 71 | 72 | if (chat?.categories === category_id) { 73 | let caption = `${topic_title} \n\n`; 74 | caption += `${translation.writer}: ${name} \n`; 75 | caption += `${translation.publication_time}: ${moment(created_at).format('LT')}\n`; 76 | caption += `${translation.number_topic}: ${topic_id}`; 77 | await client.telegram.sendMessage(item, caption, { parse_mode: 'HTML', disable_web_page_preview: true }); 78 | } 79 | 80 | else if (chat?.categories === 0) { 81 | let caption = `${topic_title} \n\n`; 82 | caption += `${translation.writer}: ${name} \n`; 83 | caption += `${translation.publication_time}: ${moment(created_at).format('LT')}\n`; 84 | caption += `${translation.number_topic}: ${topic_id}`; 85 | await client.telegram.sendMessage(item, caption, { parse_mode: 'HTML', disable_web_page_preview: true }); 86 | 87 | } 88 | } 89 | } 90 | 91 | } catch (error) { 92 | 93 | console.log(error); 94 | 95 | } 96 | }); 97 | 98 | } -------------------------------------------------------------------------------- /telegram/EventText.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import database_telegram from '../module/database_telegram.js'; 3 | import sendComment from '../discourse/sendComment.js'; 4 | import Translation from '../module/translation.js'; 5 | import path from 'path'; 6 | 7 | export default async function EventText(client) { 8 | 9 | client.on('text', async (ctx) => { 10 | 11 | let id_from = ctx?.from?.id; 12 | let id_chat = ctx?.chat?.id; 13 | let username_from = ctx?.from?.username; 14 | let username_chat = ctx?.chat?.username; 15 | let name_from = ctx?.from?.first_name ? ctx?.from?.first_name : ctx?.from?.last_name ? ctx?.from?.last_name : undefined; 16 | let name_chat = ctx?.chat?.first_name ? ctx?.chat?.first_name : ctx?.chat?.last_name ? ctx?.chat?.last_name : ctx?.chat?.title; 17 | let type = ctx?.chat?.type; 18 | let message_id = ctx?.message?.message_id; 19 | let me = ctx?.botInfo 20 | let body = ctx?.message.text; 21 | let __dirname = path.resolve(); 22 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 23 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 24 | 25 | await database_telegram(id_from, username_from, name_from, 'from'); // from || user 26 | await database_telegram(id_chat, username_chat, name_chat, 'chat', message_id); // chat || supergroup or group 27 | 28 | if (ctx?.message?.reply_to_message?.from?.id === me?.id) { 29 | 30 | let caption = ctx?.message?.reply_to_message?.caption 31 | let text = ctx?.message?.reply_to_message?.text 32 | let fromJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${id_from}.json`)); 33 | 34 | if (fromJson?.access) { 35 | 36 | if (caption?.split(`${translation.number_topic}: `)[1]) { 37 | 38 | let topic_id = caption?.split(`${translation.number_topic}: `)[1]; 39 | let seCo = await sendComment(fromJson?.discourse_username, topic_id, body); 40 | if (seCo?.errors) { 41 | for (let item of seCo?.errors) { 42 | ctx?.reply(item); 43 | } 44 | } 45 | else { 46 | 47 | let topic_slug = seCo?.topic_slug 48 | let post_number = seCo?.post_number 49 | let message = `${translation.comment_posted} ✅ ${post_number}` 50 | await ctx?.reply(message, { parse_mode: 'HTML', disable_web_page_preview: true }); 51 | 52 | } 53 | } 54 | 55 | else if (text?.split(`${translation.number_topic}: `)[1]) { 56 | 57 | let topic_id = text?.split(`${translation.number_topic}: `)[1]; 58 | let seCo = await sendComment(fromJson?.discourse_username, topic_id, body); 59 | if (seCo?.errors) { 60 | for (let item of seCo?.errors) { 61 | ctx?.reply(item); 62 | } 63 | } 64 | else { 65 | 66 | let topic_slug = seCo?.topic_slug 67 | let post_number = seCo?.post_number 68 | let message = `${translation.comment_posted} ✅ ${post_number}` 69 | await ctx?.reply(message, { parse_mode: 'HTML', disable_web_page_preview: true }); 70 | 71 | } 72 | 73 | } 74 | } 75 | 76 | else { 77 | 78 | let message = `${translation.first_link_your_account} /discourse ❌` 79 | ctx?.reply(message); 80 | } 81 | } 82 | 83 | console.log(`#Telegram sender: ${username_from ? username_from : id_from} ${type}: ${username_chat ? username_chat : name_chat}`); 84 | }); 85 | } -------------------------------------------------------------------------------- /telegram/WizardScene/CreatePosts.js: -------------------------------------------------------------------------------- 1 | import { Scenes } from 'telegraf'; 2 | import fs from 'fs-extra'; 3 | import CreatePosts from '../../discourse/CreatePosts.js'; 4 | import getCategories from '../../discourse/getCategories.js'; 5 | import Translation from '../../module/translation.js'; 6 | import path from 'path'; 7 | 8 | let __dirname = path.resolve(); 9 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 10 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 11 | 12 | export default new Scenes.WizardScene( 13 | 'CreatePosts', 14 | async (ctx) => { 15 | 16 | let id_from = ctx?.from?.id; 17 | let fromJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${id_from}.json`)); 18 | if (fromJson?.access === false) { 19 | let message = `${translation.first_link_your_account} /discourse ❌` 20 | ctx?.reply(message); 21 | return ctx.scene.leave(); 22 | } 23 | 24 | else { 25 | 26 | let Categories = await getCategories(); 27 | let message = `${translation.send_category_id} ⬇️ \n\n` 28 | 29 | for (let item of Categories) { 30 | message += `▪ ${item?.name}\n` 31 | message += `▪ ${translation.id}: ${item?.id}\n\n` 32 | } 33 | 34 | await ctx.reply(message, { parse_mode: 'HTML' }); 35 | ctx.wizard.state.data = {}; 36 | return ctx.wizard.next(); 37 | } 38 | 39 | }, 40 | async (ctx) => { 41 | 42 | if (ctx.message?.text !== undefined && !isNaN(ctx?.message?.text)) { 43 | 44 | ctx.wizard.state.data.category = Number(ctx.message?.text); 45 | ctx?.reply(`${translation.topic_title} 📝`) 46 | return ctx.wizard.next(); 47 | } 48 | 49 | else { 50 | ctx?.reply(`${translation.err_wrong_entry} ❌`); 51 | return ctx.scene.leave(); 52 | } 53 | }, 54 | async (ctx) => { 55 | 56 | if (ctx.message?.text !== undefined) { 57 | 58 | ctx.wizard.state.data.title = ctx.message?.text; 59 | ctx?.reply(`${translation.topic_content} 📝`) 60 | return ctx.wizard.next(); 61 | 62 | } 63 | 64 | else { 65 | ctx?.reply(`${translation.err_wrong_entry} ❌`); 66 | return ctx.scene.leave(); 67 | } 68 | 69 | }, 70 | async (ctx) => { 71 | 72 | if (ctx.message?.text !== undefined) { 73 | 74 | let url = process.env.URL || config?.url 75 | let id_from = ctx?.from?.id; 76 | let fromJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${id_from}.json`)); 77 | let category = ctx.wizard.state.data.category; 78 | let title = ctx.wizard.state.data.title; 79 | let raw = ctx.message?.text; 80 | let crPo = await CreatePosts(fromJson?.discourse_username, title, raw, category); 81 | 82 | if (crPo?.errors) { 83 | for (let item of crPo?.errors) { 84 | ctx?.reply(item); 85 | } 86 | } 87 | 88 | else { 89 | let topic_id = crPo?.topic_id; 90 | let topic_title = ctx.wizard.state.data.title; 91 | let topic_slug = crPo?.topic_slug; 92 | let message = `${topic_title} \n` 93 | await ctx?.reply(message, { parse_mode: 'HTML', disable_web_page_preview: true }); 94 | } 95 | 96 | return ctx.scene.leave() 97 | } 98 | 99 | else { 100 | ctx?.reply(`${translation.err_wrong_entry} ❌`); 101 | return ctx.scene.leave(); 102 | } 103 | 104 | }, 105 | ) -------------------------------------------------------------------------------- /telegram/WizardScene/WizardScene.js: -------------------------------------------------------------------------------- 1 | import activation from './activation.js'; 2 | import discourse from './discourse.js'; 3 | import CreatePosts from './CreatePosts.js'; 4 | import sendComment from './sendComment.js'; 5 | import sendMessagePrivate from './sendMessagePrivate.js'; 6 | 7 | export default [activation, discourse, CreatePosts, sendComment, sendMessagePrivate] -------------------------------------------------------------------------------- /telegram/WizardScene/activation.js: -------------------------------------------------------------------------------- 1 | import { Scenes } from 'telegraf'; 2 | import fs from 'fs-extra'; 3 | import getCategories from '../../discourse/getCategories.js'; 4 | import Translation from '../../module/translation.js'; 5 | import path from 'path'; 6 | 7 | let __dirname = path.resolve(); 8 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 9 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 10 | 11 | export default new Scenes.WizardScene( 12 | 'activation', 13 | async (ctx) => { 14 | let type = ctx?.chat?.type; 15 | let id_from = ctx?.from?.id; 16 | let id_chat = ctx?.chat?.id; 17 | 18 | if (type === 'private') { 19 | let fromJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${id_from}.json`)); 20 | if (fromJson?.evenPost) { 21 | await ctx?.reply(`${translation.err_active_in_the_chat} ⁉️`); 22 | return ctx.scene.leave(); 23 | } 24 | else { 25 | fromJson.evenPost = true 26 | fromJson.categories = 0 27 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${id_from}.json`), fromJson, { spaces: '\t' }); 28 | await ctx?.reply(`${translation.active_bot} ✅`); 29 | return ctx.scene.leave(); 30 | } 31 | } 32 | 33 | else { 34 | 35 | let chatJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/chat/${id_chat}.json`)); 36 | 37 | if (chatJson?.evenPost) { 38 | 39 | let Administrators = await ctx?.getChatAdministrators(); 40 | let checkAdmin = Administrators?.some(e => e?.user?.id === ctx?.from?.id); 41 | if (checkAdmin === false) { 42 | await ctx?.reply(`${translation.admin_activate} ❌`); 43 | } 44 | 45 | else { 46 | await ctx?.reply(`${translation.err_active_in_the_chat} ⁉️`); 47 | } 48 | 49 | return ctx.scene.leave(); 50 | } 51 | 52 | else { 53 | let Administrators = await ctx?.getChatAdministrators(); 54 | let checkAdmin = Administrators?.some(e => e?.user?.id === ctx?.from?.id); 55 | if (checkAdmin) { 56 | await getCategories().then(async e => { 57 | 58 | let message = `${translation.category_id} \n\n` 59 | for (let item of e) { 60 | 61 | message += `▪ ${item?.name}\n` 62 | message += `▪ ${translation.id}: ${item?.id}\n\n` 63 | 64 | } 65 | message += `${translation.category_id_all}` 66 | await ctx?.reply(message, { parse_mode: 'HTML' }); 67 | }); 68 | return ctx.wizard.next(); 69 | } 70 | 71 | else { 72 | await ctx?.reply(`${translation.admin_activate} ❌`); 73 | return ctx.scene.leave(); 74 | } 75 | } 76 | 77 | } 78 | 79 | }, 80 | async (ctx) => { 81 | 82 | let id_chat = ctx?.chat?.id; 83 | 84 | if (ctx?.message?.text !== undefined && !isNaN(ctx?.message?.text)) { 85 | 86 | let chatJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/chat/${id_chat}.json`)); 87 | chatJson.categories = Number(ctx?.message?.text); 88 | chatJson.evenPost = true 89 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/chat/${id_chat}.json`), chatJson, { spaces: '\t' }); 90 | ctx?.reply(`${translation.active_bot} ✅`); 91 | return ctx.scene.leave(); 92 | } 93 | 94 | else { 95 | ctx?.reply(`${translation.err_wrong_entry} ❌`); 96 | return ctx.scene.leave(); 97 | } 98 | } 99 | ) -------------------------------------------------------------------------------- /telegram/WizardScene/discourse.js: -------------------------------------------------------------------------------- 1 | import { Scenes } from 'telegraf'; 2 | import fs from 'fs-extra'; 3 | import sendMessagePrivate from '../../discourse/sendMessagePrivate.js'; 4 | import Translation from '../../module/translation.js'; 5 | import path from 'path'; 6 | 7 | 8 | let __dirname = path.resolve(); 9 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 10 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 11 | 12 | export default new Scenes.WizardScene( 13 | 'discourse', 14 | async (ctx) => { 15 | let id_from = ctx?.from?.id; 16 | let fromJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${id_from}.json`)); 17 | 18 | if (ctx?.chat?.type === 'supergroup' || ctx?.chat?.type === 'group') { 19 | 20 | await ctx?.reply(`${translation.send_me_private_message_to_link_your_account} ⚠️`); 21 | return ctx.scene.leave(); 22 | 23 | } 24 | 25 | else { 26 | if (fromJson?.access) { 27 | await ctx?.reply(`${translation.err_linked_to_discourse} ⁉️`); 28 | return ctx.scene.leave(); 29 | } 30 | 31 | else { 32 | await ctx?.reply(`${translation.enter_your_username_discourse} 📝\n\n${translation.sign_}`); 33 | return ctx.wizard.next(); 34 | } 35 | } 36 | }, 37 | async (ctx) => { 38 | 39 | if (ctx.message?.text !== undefined) { 40 | let id_from = ctx?.from?.id; 41 | let fromJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${id_from}.json`)); 42 | fromJson.discourse_username = ctx.message?.text; 43 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${id_from}.json`), fromJson, { spaces: '\t' }); 44 | let title = `${translation.verification_code}` 45 | let raw = `${translation.verification_code_for} ${fromJson?.username ? '@' + fromJson?.username : fromJson?.name} \n\n`; 46 | raw += fromJson?.verification_code; 47 | let Private = await sendMessagePrivate(process.env.DISCOURSE_USERNAME || config?.discourse_username, title, raw, ctx.message?.text); 48 | if (Private?.errors) { 49 | for (let item of Private?.errors) { 50 | ctx?.reply(item); 51 | } 52 | return ctx.scene.leave(); 53 | } 54 | else { 55 | await ctx?.reply(`${translation.send_verification_code} ✅`); 56 | await ctx?.reply(`${translation.write_verification_code} 📝`); 57 | return ctx.wizard.next(); 58 | } 59 | } 60 | 61 | else { 62 | ctx?.reply(`${translation.err_wrong_entry} ❌`); 63 | return ctx.scene.leave(); 64 | } 65 | }, 66 | async (ctx) => { 67 | 68 | if (ctx.message?.text !== undefined) { 69 | 70 | let id_from = ctx?.from?.id; 71 | let fromJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${id_from}.json`)); 72 | if (fromJson?.verification_code === ctx.message?.text) { 73 | 74 | fromJson.access = true 75 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${id_from}.json`), fromJson, { spaces: '\t' }); 76 | ctx?.reply(`${translation.active_bridge} ✅`); 77 | } 78 | else { 79 | ctx?.reply(`${translation.err_verification_code}❌`); 80 | } 81 | return ctx.scene.leave(); 82 | } 83 | else { 84 | ctx?.reply(`${translation.err_wrong_entry} ❌`); 85 | return ctx.scene.leave(); 86 | } 87 | }, 88 | ) -------------------------------------------------------------------------------- /telegram/WizardScene/sendComment.js: -------------------------------------------------------------------------------- 1 | import { Scenes } from 'telegraf'; 2 | import fs from 'fs-extra'; 3 | import sendComment from '../../discourse/sendComment.js'; 4 | import Translation from '../../module/translation.js'; 5 | import path from 'path'; 6 | 7 | let __dirname = path.resolve(); 8 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 9 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 10 | 11 | export default new Scenes.WizardScene( 12 | 'sendComment', 13 | async (ctx) => { 14 | let id_from = ctx?.from?.id; 15 | let fromJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${id_from}.json`)); 16 | if (fromJson?.access === false) { 17 | let message = `${translation.first_link_your_account} /discourse ❌` 18 | ctx?.reply(message); 19 | return ctx.scene.leave(); 20 | } 21 | 22 | else { 23 | 24 | ctx?.reply(`${translation.send_id_or_url_topic} 🌐`); 25 | ctx.wizard.state.data = {}; 26 | return ctx.wizard.next(); 27 | } 28 | }, 29 | async (ctx) => { 30 | 31 | if (ctx.message?.text !== undefined) { 32 | 33 | let text = ctx.message?.text; 34 | 35 | if (text.includes(process.env.URL || config?.url)) { 36 | 37 | let sp = text.split(''); 38 | 39 | if (sp[sp.length - 1] === '/') { 40 | 41 | ctx.wizard.state.data.topic_id = Number(text.split('/').slice(-2)[0]); 42 | ctx?.reply(`${translation.write_comment} 📝`); 43 | 44 | } 45 | 46 | else { 47 | 48 | ctx.wizard.state.data.topic_id = Number(text.split('/').slice(-1)[0]); 49 | ctx?.reply(`${translation.write_comment} 📝`); 50 | 51 | } 52 | 53 | } 54 | 55 | else if (!isNaN(text)) { 56 | 57 | ctx.wizard.state.data.topic_id = Number(text); 58 | ctx?.reply(`${translation.write_comment} 📝`); 59 | } 60 | 61 | return ctx.wizard.next(); 62 | } 63 | 64 | else { 65 | ctx?.reply(`${translation.err_wrong_entry} ❌`); 66 | return ctx.scene.leave(); 67 | } 68 | }, 69 | async (ctx) => { 70 | 71 | if (ctx.message?.text !== undefined) { 72 | 73 | let url = process.env.URL || config?.url 74 | let id_from = ctx?.from?.id; 75 | let fromJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${id_from}.json`)); 76 | let raw = ctx.message?.text; 77 | let topic_id = ctx.wizard.state.data.topic_id 78 | let seCo = await sendComment(fromJson?.discourse_username, topic_id, raw); 79 | 80 | if (seCo?.errors) { 81 | for (let item of seCo?.errors) { 82 | ctx?.reply(item); 83 | } 84 | } 85 | 86 | else { 87 | let topic_slug = seCo?.topic_slug 88 | let topic_id = seCo?.topic_id 89 | let post_number = seCo?.post_number 90 | let message = `${translation.comment_posted} ✅ ${post_number}` 91 | await ctx?.reply(message, { parse_mode: 'HTML', disable_web_page_preview: true }); 92 | } 93 | return ctx.scene.leave(); 94 | } 95 | 96 | else { 97 | ctx?.reply(`${translation.err_wrong_entry} ❌`); 98 | return ctx.scene.leave(); 99 | } 100 | 101 | } 102 | ) -------------------------------------------------------------------------------- /telegram/WizardScene/sendMessagePrivate.js: -------------------------------------------------------------------------------- 1 | import { Scenes, Markup } from 'telegraf'; 2 | import fs from 'fs-extra'; 3 | import sendMessagePrivate from '../../discourse/sendMessagePrivate.js'; 4 | import Translation from '../../module/translation.js'; 5 | import path from 'path'; 6 | 7 | let __dirname = path.resolve(); 8 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 9 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 10 | 11 | export default new Scenes.WizardScene( 12 | 'sendMessagePrivate', 13 | async (ctx) => { 14 | 15 | let id_from = ctx?.from?.id; 16 | let fromJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${id_from}.json`)); 17 | if (fromJson?.access === false) { 18 | let message = `${translation.first_link_your_account} /discourse ❌` 19 | ctx?.reply(message); 20 | return ctx.scene.leave(); 21 | } 22 | 23 | else { 24 | 25 | ctx?.reply(`${translation.username_send_to} 📝`); 26 | 27 | ctx.wizard.state.data = {}; 28 | return ctx.wizard.next(); 29 | } 30 | }, 31 | async (ctx) => { 32 | 33 | if (ctx.message?.text !== undefined) { 34 | 35 | ctx.wizard.state.data.username = ctx.message?.text 36 | ctx?.reply(`${translation.title_message_private} 📝`); 37 | return ctx.wizard.next(); 38 | } 39 | 40 | else { 41 | ctx?.reply(`${translation.err_wrong_entry} ❌`); 42 | return ctx.scene.leave(); 43 | } 44 | }, 45 | async (ctx) => { 46 | 47 | if (ctx.message?.text !== undefined) { 48 | 49 | ctx.wizard.state.data.title = ctx.message?.text 50 | ctx?.reply(`${translation.content_message_private} 📝`); 51 | return ctx.wizard.next(); 52 | } 53 | 54 | else { 55 | ctx?.reply(`${translation.err_wrong_entry} ❌`); 56 | return ctx.scene.leave(); 57 | } 58 | }, 59 | async (ctx) => { 60 | 61 | if (ctx.message?.text !== undefined) { 62 | 63 | let id_from = ctx?.from?.id; 64 | let fromJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${id_from}.json`)); 65 | let title = ctx.wizard.state.data.title; 66 | let raw = ctx.message?.text; 67 | let sendTo = ctx.wizard.state.data.username; 68 | let sePr = await sendMessagePrivate(fromJson?.discourse_username, title, raw, sendTo); 69 | if (sePr?.errors) { 70 | 71 | for (let item of sePr?.errors) { 72 | ctx?.reply(item); 73 | } 74 | } 75 | 76 | else { 77 | ctx?.reply(`${translation.message_sent} ✅`); 78 | } 79 | return ctx.scene.leave(); 80 | 81 | } 82 | 83 | else { 84 | ctx?.reply(`${translation.err_wrong_entry} ❌`); 85 | return ctx.scene.leave(); 86 | } 87 | 88 | }, 89 | ) -------------------------------------------------------------------------------- /telegram/command/CreatePosts.js: -------------------------------------------------------------------------------- 1 | export default async function CreatePosts(client) { 2 | 3 | client.command('CreatePosts', async (ctx) => { 4 | 5 | await ctx.scene.enter('CreatePosts'); 6 | 7 | }); 8 | } -------------------------------------------------------------------------------- /telegram/command/activation.js: -------------------------------------------------------------------------------- 1 | export default async function activation(client) { 2 | 3 | client.command('activation', async (ctx) => { 4 | 5 | await ctx.scene.enter('activation'); 6 | 7 | }); 8 | } -------------------------------------------------------------------------------- /telegram/command/discourse.js: -------------------------------------------------------------------------------- 1 | export default async function discourse(client) { 2 | 3 | client.command('discourse', async (ctx) => { 4 | 5 | await ctx.scene.enter('discourse'); 6 | 7 | }); 8 | } -------------------------------------------------------------------------------- /telegram/command/getCategories.js: -------------------------------------------------------------------------------- 1 | import getCategories from '../../discourse/getCategories.js'; 2 | import fs from 'fs-extra'; 3 | import Translation from '../../module/translation.js'; 4 | import path from 'path'; 5 | 6 | export default async function getCategories_(client) { 7 | 8 | client.command('getCategories', async (ctx) => { 9 | 10 | let __dirname = path.resolve(); 11 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 12 | let Categories = await getCategories(); 13 | let url = process.env.URL || config?.url; 14 | let title = process.env.DISCOURSE_FORUM_NAME || config?.discourse_forum_name; 15 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 16 | let message = `${translation.categories} ${title} ⬇️\n\n` 17 | 18 | for (let item of Categories) { 19 | let id = item?.id; 20 | let name = item?.name; 21 | let topics_all_time = item?.topics_all_time; 22 | let slug = item?.slug 23 | message += `${name} \n` 24 | message += `${translation.number_of_topics_posted}: ${topics_all_time}\n` 25 | } 26 | 27 | await ctx.reply(message, { parse_mode: 'HTML', disable_web_page_preview: true }); 28 | }); 29 | } -------------------------------------------------------------------------------- /telegram/command/get_latest_posts.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import moment from 'moment-hijri'; 3 | import fs from 'fs-extra'; 4 | import get_latest_posts from '../../discourse/get_latest_posts.js'; 5 | import Translation from '../../module/translation.js'; 6 | import path from 'path'; 7 | 8 | moment.locale('en-EN'); 9 | 10 | export default async function get_latest_posts_(client) { 11 | 12 | client.command('get_latest_posts', async (ctx) => { 13 | 14 | let __dirname = path.resolve(); 15 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 16 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 17 | let get = await get_latest_posts(); 18 | let response = await fetch(process.env.URL || config?.url + `/t/${get?.topic_slug}/${get?.topic_id}`, { method: 'GET' }); 19 | let data = await response.text(); 20 | if (data.includes('itemprop="image"')) { 21 | let preview = data.split('itemprop="image" href="')[1]?.split('">')[0]; 22 | let caption = `${get?.topic_title} \n\n`; 23 | caption += `${translation.writer}: ${get?.name} \n`; 24 | caption += `${translation.date}: ${moment(get?.created_at).format('iYYYY/iM/iD')}\n`; 25 | caption += `${translation.number_topic}: ${get?.topic_id}`; 26 | await ctx.replyWithPhoto({ url: preview }, { caption: caption, parse_mode: 'HTML', disable_web_page_preview: true }); 27 | 28 | } 29 | 30 | else { 31 | let caption = `${get?.topic_title} \n\n`; 32 | caption += `${translation.writer}: ${get?.name} \n`; 33 | caption += `${translation.date}: ${moment(get?.created_at).format('iYYYY/iM/iD')}\n`; 34 | caption += `${translation.number_topic}: ${get?.topic_id}`; 35 | await ctx.reply(caption, { parse_mode: 'HTML' }); 36 | } 37 | 38 | }); 39 | } -------------------------------------------------------------------------------- /telegram/command/index.js: -------------------------------------------------------------------------------- 1 | import activation from './activation.js'; 2 | import start from './start.js'; 3 | import get_latest_posts_ from './get_latest_posts.js'; 4 | import discourse from './discourse.js'; 5 | import getCategories_ from './getCategories.js'; 6 | import CreatePosts from './CreatePosts.js'; 7 | import sendComment from './sendComment.js'; 8 | import sendMessagePrivate from './sendMessagePrivate.js'; 9 | 10 | export default async function command(client, Markup) { 11 | 12 | try { 13 | 14 | await start(client, Markup); 15 | await activation(client); 16 | await get_latest_posts_(client); 17 | await discourse(client); 18 | await getCategories_(client); 19 | await CreatePosts(client); 20 | await sendComment(client); 21 | await sendMessagePrivate(client); 22 | 23 | } catch (error) { 24 | 25 | console.log(error); 26 | 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /telegram/command/sendComment.js: -------------------------------------------------------------------------------- 1 | export default async function sendComment(client) { 2 | 3 | client.command('sendComment', async (ctx) => { 4 | 5 | await ctx.scene.enter('sendComment'); 6 | 7 | }); 8 | } -------------------------------------------------------------------------------- /telegram/command/sendMessagePrivate.js: -------------------------------------------------------------------------------- 1 | export default async function sendMessagePrivate(client) { 2 | 3 | client.command('sendMessagePrivate', async (ctx) => { 4 | 5 | await ctx.scene.enter('sendMessagePrivate'); 6 | 7 | }); 8 | } -------------------------------------------------------------------------------- /telegram/command/start.js: -------------------------------------------------------------------------------- 1 | import database_telegram from '../../module/database_telegram.js'; 2 | import fs from 'fs-extra'; 3 | import Translation from '../../module/translation.js'; 4 | import path from 'path'; 5 | 6 | export default async function start(client, Markup) { 7 | 8 | client.start(async (ctx) => { 9 | let __dirname = path.resolve(); 10 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 11 | let translation = await Translation(`${process.env.LANGUAGE || config?.language}`); 12 | let id_from = ctx?.from?.id; 13 | let id_chat = ctx?.chat?.id; 14 | let username_from = ctx?.from?.username; 15 | let username_chat = ctx?.chat?.username; 16 | let name_from = ctx?.from?.first_name ? ctx?.from?.first_name : ctx?.from?.last_name ? ctx?.from?.last_name : undefined; 17 | let name_chat = ctx?.chat?.first_name ? ctx?.chat?.first_name : ctx?.chat?.last_name ? ctx?.chat?.last_name : ctx?.chat?.title; 18 | let type = ctx?.chat?.type; 19 | let message_id = ctx?.message?.message_id; 20 | let but_1 = [ 21 | Markup.button.url(process.env.DISCOURSE_FORUM_NAME || config?.discourse_forum_name, process.env.URL || config?.url) 22 | ]; 23 | let button = Markup.inlineKeyboard([but_1]); 24 | let message = `${translation.welcome} ${name_from} ${translation.in_the_bridge} ${process.env.DISCOURSE_FORUM_NAME || config?.discourse_forum_name} 👋 \n\n` 25 | message += `▪ ${translation.view_last_topic} 📄 \n/get_latest_posts \n` 26 | message += `▪ ${translation.view_categories} ⬇️ \n/getCategories \n` 27 | message += `▪ ${translation.write_new_topic} 📝 \n/CreatePosts \n` 28 | message += `▪ ${translation.write_new_comment} 💬 \n/sendComment \n` 29 | message += `▪ ${translation.send_message_private} 🔒 \n/sendMessagePrivate \n` 30 | message += `▪ ${translation.link_your_account_to} ${process.env.DISCOURSE_FORUM_NAME || config?.discourse_forum_name} \n/discourse \n` 31 | message += `▪ ${translation.activate_the_bot} \n/activation` 32 | 33 | if (type === 'group' || type === 'supergroup') { 34 | 35 | await database_telegram(id_from, username_from, name_from, 'from'); // from || user 36 | await database_telegram(id_chat, username_chat, name_chat, 'chat', message_id); // chat || supergroup or group 37 | await ctx.reply(message, { parse_mode: 'HTML', reply_markup: button.reply_markup }); 38 | 39 | } 40 | 41 | else if (type === 'private') { 42 | 43 | await database_telegram(id_chat, username_chat, name_chat, 'from', message_id); // from || user 44 | await ctx.reply(message, { parse_mode: 'HTML', reply_markup: button.reply_markup }); 45 | 46 | } 47 | 48 | }); 49 | 50 | } -------------------------------------------------------------------------------- /telegram/index.js: -------------------------------------------------------------------------------- 1 | import { Telegraf, Markup, Scenes, session } from 'telegraf'; 2 | import fs from 'fs-extra'; 3 | import command from './command/index.js'; 4 | import WizardScene from './WizardScene/WizardScene.js'; 5 | import join_left from './join_left.js'; 6 | import EventText from './EventText.js'; 7 | import EventPosts_ from './EventPosts_.js'; 8 | import path from 'path'; 9 | 10 | export default async function telegram() { 11 | 12 | try { 13 | 14 | let __dirname = path.resolve(); 15 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 16 | let options = { channelMode: true, polling: true }; 17 | let client = new Telegraf(process.env.TELEGRAM_TOKEN || config?.telegram_token, options); 18 | let stage = new Scenes.Stage(WizardScene); 19 | client.use(session()) 20 | client.use(stage.middleware()); 21 | await join_left(client); // إنظمام ومغادرة الأعضاء 22 | await command(client, Markup); // أوامر البوت 23 | await EventText(client); // حدث تلقي رسالة جديده 24 | await EventPosts_(client); // حدث عند إنشاء موصوع جديد على discourse 25 | client.launch().then(() => console.log('Telegram is ready!')); 26 | client.catch((error) => { 27 | console.log(error); 28 | }); 29 | 30 | } catch (error) { 31 | 32 | console.log(error); 33 | 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /telegram/join_left.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import database_telegram from '../module/database_telegram.js'; 3 | import path from 'path'; 4 | 5 | export default async function join_left(client) { 6 | 7 | client.on("my_chat_member", async (ctx) => { 8 | 9 | let __dirname = path.resolve(); 10 | let config = fs.readJsonSync(path.join(__dirname, '/config.json')); 11 | let id_from = ctx?.from?.id; 12 | let id_chat = ctx?.chat?.id; 13 | let username_chat = ctx?.chat?.username; 14 | let name_from = ctx?.from?.first_name ? ctx?.from?.first_name : ctx?.from?.last_name ? ctx?.from?.last_name : undefined; 15 | let name_chat = ctx?.chat?.first_name ? ctx?.chat?.first_name : ctx?.chat?.last_name ? ctx?.chat?.last_name : ctx?.chat?.title; 16 | let type = ctx?.chat?.type; 17 | 18 | if (ctx?.update?.my_chat_member?.new_chat_member?.status === 'member' || ctx?.update?.my_chat_member?.new_chat_member?.status === 'administrator') { 19 | 20 | if (type === 'private') { 21 | await database_telegram(id_chat, username_chat, name_chat, 'from'); 22 | } 23 | 24 | else { 25 | await database_telegram(id_chat, username_chat, name_chat, 'chat'); 26 | } 27 | } 28 | 29 | else if (ctx?.update?.my_chat_member?.new_chat_member?.status === 'left' || ctx?.update?.my_chat_member?.new_chat_member?.status === 'kicked') { 30 | 31 | if (type === 'private') { 32 | 33 | let fromJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${id_from}.json`)); 34 | fromJson.evenPost = false 35 | fromJson.categories = null 36 | fromJson.access = false 37 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/from/${id_from}.json`), fromJson, { spaces: '\t' }); 38 | 39 | } 40 | 41 | else { 42 | 43 | let chatJson = fs.readJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/chat/${id_chat}.json`)); 44 | chatJson.evenPost = false 45 | chatJson.categories = null 46 | chatJson.access = false 47 | fs.writeJsonSync(path.join(process.env.DATAPATH || config?.dataPath, `/database/telegram/chat/${id_chat}.json`), chatJson, { spaces: '\t' }); 48 | } 49 | 50 | } 51 | }); 52 | 53 | } -------------------------------------------------------------------------------- /translation/ar.json: -------------------------------------------------------------------------------- 1 | { 2 | "number_topic":"رقم الموضوع", 3 | "comment_posted":"نُشر التعليق ✅", 4 | "first_link_your_account":"⛔ عليك ربط حسابك أولًا", 5 | "first_link_your_account_matrix":"عليك أولًا أن تربط حسابك، وسبيل ذلك إما أن ترسل كلمة ‹discourse› أو الرقم ‹6›", 6 | "writer":"🚻 الكاتب", 7 | "date":"التاريخ", 8 | "publication_time":"وقت النشر", 9 | "err_active_in_the_chat":"🔴 البوت مفعَّل في هذه المحادثة بالفعل", 10 | "active_bot":"✅ فُعِّل البوت", 11 | "admin_activate":"⛔ يجب أن تكون مشرفًا لتفعِّل البوت", 12 | "category_id":"أرسل معرِّف الفئة لتتلقَّى آخر المواضيع منها", 13 | "category_id_all":"إن شئت أن تتلقَّى المواضيع من كلِّ الفئات فأرسل الرقم ‹0›", 14 | "id":"المعرِّف", 15 | "err_wrong_entry":"إدخال خاطئ", 16 | "send_category_id":"أرسل معرِّف الفئة", 17 | "topic_title":"اكتب عنوان الموضوع", 18 | "topic_content":"اكتب محتوى الموضوع", 19 | "send_me_private_message_to_link_your_account":"🔶 راسلني في الخاصِّ لتربط حسابك", 20 | "err_linked_to_discourse":"⛔ الحساب مربوط بدسكورس بالفعل", 21 | "enter_your_username_discourse":"اكتب اسمك في دسكورس", 22 | "sign_":"اسم المستخدم دون @", 23 | "verification_code":"🔵 رمز تحقُّقك", 24 | "verification_code_for":"رمز تحقُّقِ ", 25 | "send_verification_code":"✅ أُرسل لك رمز تحقُّق في دسكورس", 26 | "write_verification_code":"🔵 اكتب الرمز الذي استلمته لتفعِّل الجسر", 27 | "active_bridge":"✅ فُعِّل الجسر", 28 | "err_verification_code":"⛔ رمز التحقق المدخل خاطئ", 29 | "send_id_or_url_topic":"أرسل رقم أو رابط الموضوع", 30 | "write_comment":"اكتب تعليقًا", 31 | "username_send_to":"اكتب اسم مستخدم المرسَل إليه", 32 | "title_message_private":"اكتب عنوان الرسالة", 33 | "content_message_private":"اكتب محتوى الرسالة", 34 | "message_sent":"✅ أُرسلت الرسالة", 35 | "categories":"فئات", 36 | "number_of_topics_posted":"عدد المواضيع المنشورة", 37 | "welcome":"أهلًا وسهلًا", 38 | "in_the_bridge":"في الجسر", 39 | "view_last_topic":"اعرض آخر موضوع نُشر", 40 | "view_categories":"اعرض الفئات", 41 | "write_new_topic":"اكتب موضوعًا جديدًا", 42 | "write_new_comment":"اكتب تعليقًا جديدًا", 43 | "send_message_private":"أرسل رسالةً خاصَّةً", 44 | "link_your_account_to":"اربط حسابك في", 45 | "activate_the_bot":"فعِّل البوت", 46 | "send_number_or_name_service":"أرسل اسم الخدمة أو رقمها", 47 | "back_main_menu":"أرسل # لتعود للقائمة الرئيسة", 48 | "wrong_data_entered":"⛔ تأكَّد من إدخالك البيانات صحيحةً" 49 | } 50 | -------------------------------------------------------------------------------- /translation/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "number_topic":"Topic number", 3 | "comment_posted":"Comment posted ✅", 4 | "first_link_your_account":"⛔ You need to link your account first", 5 | "first_link_your_account_matrix":"⛔ You need to link your account either by sending 'discourse' or '6'", 6 | "writer":"🚻 Author", 7 | "date":"Date", 8 | "publication_time":"Publish date", 9 | "err_active_in_the_chat":"🔴 the bot is already active in this chat", 10 | "active_bot":"Bot activated ✅", 11 | "admin_activate":"⛔ You need to be an admin to activate the bot", 12 | "category_id":"Send the category's 🆔 you want to subscribe to", 13 | "category_id_all":"Type '0' if you want to receive posts from all categories", 14 | "id":"Identifier", 15 | "err_wrong_entry":"Wrong entry", 16 | "send_category_id":"Send the category's 🆔", 17 | "topic_title":"🔤 Write the topic's title", 18 | "topic_content":"🔤 Write the topic's content", 19 | "send_me_private_message_to_link_your_account":"🔶 To link your account, send me a private message", 20 | "err_linked_to_discourse":"⛔ This account is already linked to a Discourse account", 21 | "enter_your_username_discourse":"Write your Discourse username", 22 | "sign_":"Username without @", 23 | "verification_code":"🔵 Verification code", 24 | "verification_code_for":"Verification code", 25 | "send_verification_code":"Your verification code has been sent ✅. You can find it in your Discourse DMs", 26 | "write_verification_code":"🔵 Write the verification code to enable bridging", 27 | "active_bridge":"Bridge enabled ✅", 28 | "err_verification_code":"⛔ Can't verify, is the code correct?", 29 | "send_id_or_url_topic":"Send the topic's ID or a direct link to it", 30 | "write_comment":"Write a comment", 31 | "username_send_to":"Username you want to send the message to", 32 | "title_message_private":"Write the message title", 33 | "content_message_private":"Write the message content:", 34 | "message_sent":"Message sent ✅", 35 | "categories":"Categories", 36 | "number_of_topics_posted":"Number of topics posted", 37 | "welcome":"Welcome", 38 | "in_the_bridge":"In the bridge", 39 | "view_last_topic":"View the latest topic", 40 | "view_categories":"View categories", 41 | "write_new_topic":"Write a new topic", 42 | "write_new_comment":"Write a new comment", 43 | "send_message_private":"Send a private message", 44 | "link_your_account_to":"Link your account to", 45 | "activate_the_bot":"Activate the bot", 46 | "send_number_or_name_service":"Send the service's number or name", 47 | "back_main_menu":"To go back to the main menu, type #", 48 | "wrong_data_entered":"⛔ Make sure info is written correctly" 49 | } 50 | --------------------------------------------------------------------------------