Duongkum999 commited on
Commit
91d9d20
1 Parent(s): a47764e

Upload 56 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +35 -35
  2. Dockerfile +49 -49
  3. LICENSE +21 -0
  4. README.md +12 -12
  5. changelog.json +33 -0
  6. config.json +62 -0
  7. configCommands.json +4 -0
  8. package-lock.json +0 -0
  9. package.json +44 -0
  10. plugins/fb.js +63 -0
  11. source/apis/index.js +272 -0
  12. source/apis/lib/Client.js +1132 -0
  13. source/apis/lib/Messenger.js +464 -0
  14. source/apis/lib/changeAvatar.js +103 -0
  15. source/apis/lib/changeBio.js +61 -0
  16. source/apis/lib/changeCover.js +84 -0
  17. source/apis/lib/getAccessToken.js +81 -0
  18. source/apis/lib/getCurrentUserID.js +5 -0
  19. source/apis/lib/getThreadInfo.js +163 -0
  20. source/apis/lib/getUserID.js +91 -0
  21. source/apis/lib/getUserInfo.js +144 -0
  22. source/apis/lib/logout.js +53 -0
  23. source/apis/lib/markAsDelivered.js +35 -0
  24. source/apis/lib/markAsRead.js +41 -0
  25. source/apis/lib/muteThread.js +49 -0
  26. source/apis/lib/post.js +12 -0
  27. source/apis/lib/resolvePhotoUrl.js +63 -0
  28. source/apis/lib/screenshot.js +77 -0
  29. source/apis/utils.js +372 -0
  30. source/control/events/cache.json +1 -0
  31. source/control/index.js +204 -0
  32. source/control/model.js +267 -0
  33. source/dashboard/assets/css/404.css +113 -0
  34. source/dashboard/assets/css/login.css +176 -0
  35. source/dashboard/assets/image/403.png +0 -0
  36. source/dashboard/assets/image/404.png +0 -0
  37. source/dashboard/assets/image/login.png +0 -0
  38. source/dashboard/assets/js/lockkey.js +89 -0
  39. source/dashboard/assets/js/login.js +46 -0
  40. source/dashboard/index.js +123 -0
  41. source/dashboard/views/403.ejs +42 -0
  42. source/dashboard/views/404.ejs +34 -0
  43. source/dashboard/views/login.ejs +52 -0
  44. source/database/database.sqlite +0 -0
  45. source/database/index.js +93 -0
  46. source/database/model/thread.mongodb.js +159 -0
  47. source/database/model/thread.sqlite3.js +132 -0
  48. source/database/model/user.mongodb.js +167 -0
  49. source/database/model/user.sqlite3.js +142 -0
  50. source/index.js +27 -0
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
Dockerfile CHANGED
@@ -1,49 +1,49 @@
1
- FROM nvidia/cuda:11.3.1-base-ubuntu20.04
2
-
3
- ENV DEBIAN_FRONTEND=noninteractive \
4
- TZ=Europe/Paris
5
-
6
- # Remove any third-party apt sources to avoid issues with expiring keys.
7
- # Install some basic utilities
8
- RUN rm -f /etc/apt/sources.list.d/*.list && \
9
- apt-get update && apt-get install -y --no-install-recommends \
10
- curl \
11
- ca-certificates \
12
- sudo \
13
- git \
14
- wget \
15
- procps \
16
- git-lfs \
17
- zip \
18
- unzip \
19
- htop \
20
- vim \
21
- nano \
22
- bzip2 \
23
- libx11-6 \
24
- build-essential \
25
- libsndfile-dev \
26
- software-properties-common \
27
- && rm -rf /var/lib/apt/lists/*
28
-
29
- RUN curl -sL https://deb.nodesource.com/setup_18.x | bash - && \
30
- apt-get install -y nodejs && \
31
- npm install -g configurable-http-proxy
32
-
33
- # Tạo thư mục làm việc trong container
34
- WORKDIR /app
35
-
36
- # Clone repository từ GitHub
37
- RUN git clone https://github.com/pedguedes090/mira-bot-v1.git .
38
-
39
- # Cài đặt các phụ thuộc
40
- RUN npm install
41
-
42
- # Cấp quyền đọc/ghi cho thư mục
43
- RUN chmod -R 755 /app
44
-
45
- # Expose port mà ứng dụng sẽ chạy
46
- EXPOSE 25645
47
-
48
- # Lệnh để chạy ứng dụng
49
- CMD ["npm", "start"]
 
1
+ FROM nvidia/cuda:11.3.1-base-ubuntu20.04
2
+
3
+ ENV DEBIAN_FRONTEND=noninteractive \
4
+ TZ=Europe/Paris
5
+
6
+ # Remove any third-party apt sources to avoid issues with expiring keys.
7
+ # Install some basic utilities
8
+ RUN rm -f /etc/apt/sources.list.d/*.list && \
9
+ apt-get update && apt-get install -y --no-install-recommends \
10
+ curl \
11
+ ca-certificates \
12
+ sudo \
13
+ git \
14
+ wget \
15
+ procps \
16
+ git-lfs \
17
+ zip \
18
+ unzip \
19
+ htop \
20
+ vim \
21
+ nano \
22
+ bzip2 \
23
+ libx11-6 \
24
+ build-essential \
25
+ libsndfile-dev \
26
+ software-properties-common \
27
+ && rm -rf /var/lib/apt/lists/*
28
+
29
+ RUN curl -sL https://deb.nodesource.com/setup_18.x | bash - && \
30
+ apt-get install -y nodejs && \
31
+ npm install -g configurable-http-proxy
32
+
33
+ # Tạo thư mục làm việc trong container
34
+ WORKDIR /app
35
+ COPY package*.json ./
36
+ COPY package-lock*.json ./
37
+ COPY . .
38
+
39
+ # Cài đặt các phụ thuộc
40
+ RUN npm install
41
+
42
+ # Cấp quyền đọc/ghi cho thư mục
43
+ RUN chmod -R 755 /app
44
+
45
+ # Expose port mà ứng dụng sẽ chạy
46
+ EXPOSE 25645
47
+
48
+ # Lệnh để chạy ứng dụng
49
+ CMD ["npm", "start"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024, GiaKhang1810
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,12 +1,12 @@
1
- ---
2
- title: Dunbot
3
- emoji: 🐢
4
- colorFrom: indigo
5
- colorTo: gray
6
- sdk: docker
7
- app_port: 25645
8
- pinned: false
9
- license: mit
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ ---
2
+ title: Dunbot
3
+ emoji: 🐢
4
+ colorFrom: indigo
5
+ colorTo: gray
6
+ sdk: docker
7
+ app_port: 25645
8
+ pinned: false
9
+ license: mit
10
+ ---
11
+
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
changelog.json ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "beta-1.0.1": {
3
+ "source/database/index.js": [],
4
+ "source/database/model/thread.mongodb.js": [],
5
+ "source/database/model/thread.sqlite3.js": [],
6
+ "source/database/model/user.mongodb.js": [],
7
+ "source/database/model/user.sqlite3.js": [],
8
+ "source/languages/en-US.json": [],
9
+ "source/languages/vi-VN.json": [],
10
+ "source/lib/log.js": [],
11
+ "source/lib/utils.js": [],
12
+ "source/control/index.js": [],
13
+ "source/control/model.js": [],
14
+ "source/mira.js": [],
15
+ "source/apis/utils.js": [],
16
+ "source/apis/lib/changeAvatar.js": [],
17
+ "source/apis/lib/changeBio.js": [],
18
+ "source/apis/lib/changeCover.js": [],
19
+ "source/apis/lib/Client.js": [],
20
+ "source/apis/lib/getAccessToken.js": [],
21
+ "source/apis/lib/getCurrentUserID.js": [],
22
+ "source/apis/lib/getThreadInfo.js": [],
23
+ "source/apis/lib/getUserID.js": [],
24
+ "source/apis/lib/logout.js": [],
25
+ "source/apis/lib/markAsDelivered.js": [],
26
+ "source/apis/lib/markAsRead.js": [],
27
+ "source/apis/lib/Messenger.js": [],
28
+ "source/apis/lib/muteThread.js": [],
29
+ "source/apis/lib/post.js": [],
30
+ "source/apis/lib/resolvePhotoUrl.js": [],
31
+ "source/apis/lib/screenshot.js": []
32
+ }
33
+ }
config.json ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "facebookAccountOptions": {
3
+ "email": "",
4
+ "password": "",
5
+ "whiteID": "",
6
+ "pageID": "",
7
+ "proxy": null,
8
+ "cookies": "",
9
+ "facebookState": "fbstate.json"
10
+ },
11
+ "dashboardOptions": {
12
+ "port": 25645,
13
+ "user": "[email protected]",
14
+ "password": "ZU2pXI93sDCT5mIy",
15
+ "resetAccount": false
16
+ },
17
+ "botOptions": {
18
+ "adminOnly": false,
19
+ "name": "Mira",
20
+ "prefix": ",",
21
+ "adminIDs": [
22
+ 100036947774673,
23
+ 100035731083424
24
+ ]
25
+ },
26
+ "facebookAPIsOptions": {
27
+ "autoReconnect": true,
28
+ "listenSelf": true,
29
+ "listenNotif": true,
30
+ "listenEventsSelf": true,
31
+ "listenEvents": true,
32
+ "forceLogin": true,
33
+ "autoRefreshState": true,
34
+ "online": true,
35
+ "updatePresence": true,
36
+ "autoMarkDelivery": true
37
+ },
38
+ "systemOptions": {
39
+ "time_zone": "Asia/Ho_Chi_Minh",
40
+ "language": "en-US",
41
+ "autoUpdate": {
42
+ "enable": true,
43
+ "releaseOnly": true
44
+ },
45
+ "autoRestart": {
46
+ "enable": true,
47
+ "timeMS": 3600000
48
+ },
49
+ "autoLoadPlugins": {
50
+ "enable": true,
51
+ "ignore": []
52
+ },
53
+ "autoReloadPlugins": {
54
+ "enable": true,
55
+ "ignore": []
56
+ },
57
+ "DataBase": {
58
+ "type": "sqlite",
59
+ "mongoURI": "mongodb+srv://GiaKhang1810:[email protected]/?retryWrites=true&w=majority&appName=Mira"
60
+ }
61
+ }
62
+ }
configCommands.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "fb": {},
3
+ "facebook": {}
4
+ }
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "mira-bot-v1",
3
+ "version": "beta-1.0.1",
4
+ "description": "Simple Chatbot Messenger",
5
+ "main": "source/mira.js",
6
+ "scripts": {
7
+ "start": "node source/index.js"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/GiaKhang1810/mira-bot-v1.git"
12
+ },
13
+ "keywords": [],
14
+ "author": "Khang",
15
+ "license": "MIT",
16
+ "bugs": {
17
+ "url": "https://github.com/GiaKhang1810/mira-bot-v1/issues"
18
+ },
19
+ "homepage": "https://github.com/GiaKhang1810/mira-bot-v1#readme",
20
+ "os": [
21
+ "win32",
22
+ "darwin",
23
+ "linux"
24
+ ],
25
+ "dependencies": {
26
+ "axios": "^1.7.2",
27
+ "cheerio": "^1.0.0",
28
+ "ejs": "^3.1.10",
29
+ "express": "^4.19.2",
30
+ "express-session": "^1.18.0",
31
+ "express-ws": "^5.0.2",
32
+ "https-proxy-agent": "^7.0.5",
33
+ "moment-timezone": "^0.5.45",
34
+ "mongodb": "^6.8.0",
35
+ "mongoose": "^8.5.2",
36
+ "mqtt": "^5.9.1",
37
+ "mqtt-packet": "^9.0.0",
38
+ "puppeteer": "^22.14.0",
39
+ "request": "^2.88.2",
40
+ "sequelize": "^6.37.3",
41
+ "sqlite3": "^5.1.7",
42
+ "websocket-stream": "^5.5.2"
43
+ }
44
+ }
plugins/fb.js ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class facebook {
2
+ get Options() {
3
+ return {
4
+ name: "fb",
5
+ version: "1.0.0",
6
+ role: 0,
7
+ author: [
8
+ {
9
+ info: "Khang",
10
+ contact: "m.me/100036947774673"
11
+ }
12
+ ],
13
+ category: "Tools",
14
+ description: {
15
+ "vi-VN": "Công cụ giúp bạn thực hiện các thao tác không cho phép trên facebook.",
16
+ "en-US": "Tool to help you perform actions that are not allowed on Facebook."
17
+ },
18
+ delay: 2000,
19
+ guides: {
20
+ "vi-VN":
21
+ "{p}{n}dl reel {url} - Tải video từ reel.\n" +
22
+ "{p}{n}dl watch {url} - Tải video từ watch.\n" +
23
+ "{p}{n}token {cookie} - Lấy token từ cookie.\n" +
24
+ "{p}{n}info user {uid} - Lấy thông tin người dùng.\n" +
25
+ "{p}{n}info thread {tid} - Lấy thông tin nhóm.\n" +
26
+ "{p}{n}screen {uid} {userAgent} - Chụp màn hình người dùng.",
27
+ "en-US":
28
+ "{p}{n}help - Sends a list of instructions for use.\n" +
29
+ "{p}{n}dl reel {url} - Download video from reel.\n" +
30
+ "{p}{n}dl watch {url} - Download videos from watch.\n" +
31
+ "{p}{n}token {cookie} - Get token from cookie.\n" +
32
+ "{p}{n}info user {uid} - Get user information.\n" +
33
+ "{p}{n}info thread {tid} - Get group information.\n" +
34
+ "{p}{n}screen {uid} {userAgent} - Capture user screen."
35
+ },
36
+ dependencies: ["fs"],
37
+ envConfig: {}
38
+ }
39
+ }
40
+
41
+ get Langs() {
42
+ return {
43
+ "vi-VN": {
44
+
45
+ },
46
+ "en-US": {
47
+
48
+ }
49
+ }
50
+ }
51
+
52
+ async Reply({ Messenger, User, Thread, events, apis }) {}
53
+
54
+ async React({ Messenger, User, Thread, events, apis }) {}
55
+
56
+ async Schedule({ Messenger, User, Thread, events, apis }) {}
57
+
58
+ async Main({ Messenger, User, Thread, events, apis , args}) {
59
+
60
+ }
61
+ }
62
+
63
+ module.exports = facebook;
source/apis/index.js ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var log = require("../lib/log");
2
+ var utils = require("./utils");
3
+ var fs = require("fs");
4
+ var globalOptions = global.mira.config.facebookAPIsOptions;
5
+
6
+ function buildAPIs(body, jar, whiteID, proxy, loginCount) {
7
+ var infoCookies = jar
8
+ .getCookies("https://www.facebook.com")
9
+ .reduce(function (form, cookies) {
10
+ var [name, value] = cookies.cookieString().split("=");
11
+ form[name] = value;
12
+ return form;
13
+ }, {});
14
+
15
+ if (!infoCookies.i_user && !infoCookies.c_user) {
16
+ log.warn("facebook.login.notUser");
17
+ if (globalOptions.forceLogin) {
18
+ log.warn("facebook.relogin.force");
19
+ return Login(false, loginCount += 1);
20
+ } else
21
+ process.exit(1);
22
+ }
23
+
24
+ var userID = whiteID === infoCookies.i_user ? infoCookies.i_user : infoCookies.c_user;
25
+ log.info("facebook.login.user", userID);
26
+ var clientID = (Math.random() * 2147483648 | 0).toString(16);
27
+ var apis = {
28
+ getAppState: function getAppState() {
29
+ return jar
30
+ .getCookies("https://www.facebook.com")
31
+ .concat(jar.getCookies("https://www.messenger.com"));
32
+ }
33
+ }
34
+
35
+ var endpoint, region, syncToken;
36
+ var endpointExec = /"appID":219994525426954,"endpoint":"(.+?)"/g.exec(body);
37
+ if (endpointExec) {
38
+ endpoint = endpointExec[1].replace(/\\\//g, "/");
39
+ region = new URL(endpoint).searchParams.get("region").toUpperCase();
40
+ log.info("facebook.login.region", region);
41
+ } else
42
+ log.warn("facebook.login.notRegion");
43
+
44
+ var ctx = {
45
+ userID,
46
+ region,
47
+ endpoint,
48
+ clientID,
49
+ jar,
50
+ get globalOptions() {
51
+ return global.mira.config.facebookAPIsOptions;
52
+ },
53
+ isLogin: true,
54
+ lastSeqID: null,
55
+ clientMutationID: 0,
56
+ proxy
57
+ }
58
+
59
+ var http = utils.makeDefaults(body, userID, ctx);
60
+ fs.readdirSync(__dirname + "/lib")
61
+ .filter(item => item.endsWith(".js"))
62
+ .map(function (item) {
63
+ apis[item.replace(".js", "")] = require("./lib/" + item)(http, apis, ctx);
64
+ });
65
+ log.info("facebook.apis", Object.keys(apis).length);
66
+ global.mira.apis = apis;
67
+
68
+ var checkLogin = setInterval(function () {
69
+ if (ctx.isLogin)
70
+ return;
71
+
72
+ log.warn("facebook.login.logout");
73
+ if (globalOptions.autoReconnect) {
74
+ log.warn("facebook.login.reconnect");
75
+ clearInterval(checkLogin);
76
+ return Login(false, loginCount += 1);
77
+ }
78
+ }, 5000);
79
+
80
+ return utils
81
+ .post("https://www.facebook.com/v1.0/dialog/oauth/confirm", ctx.jar, {
82
+ fb_dtsg: /\["DTSGInitData",\[],{"token":"(\S+)","async_get_token"/g.exec(body)[1],
83
+ app_id: "124024574287414",
84
+ redirect_uri: "fbconnect://success",
85
+ display: "popup",
86
+ return_format: "access_token",
87
+ })
88
+ .then(function (res) {
89
+ var body = res.body;
90
+ var token = body.match(/access_token=(.+?)&/);
91
+ if (token && token[1]) {
92
+ ctx.token = token[1];
93
+ log.info("facebook.access.token", token[1]);
94
+ } else
95
+ throw new Error("Token is undefined.");
96
+ })
97
+ .catch(function (error) {
98
+ log.warn("facebook.access.error", error.message);
99
+ console.log(error);
100
+ });
101
+ }
102
+
103
+ async function makeLogin(email, password, proxy) {
104
+ var Browser;
105
+ var Pup = require("puppeteer");
106
+ var Proxy = utils.parseProxy(proxy);
107
+
108
+ if (Proxy && Proxy.length > 0) {
109
+ Browser = await Pup.launch({ headless: true, args: ["--proxy-server=" + proxy] });
110
+ } else
111
+ Browser = await Pup.launch({ headless: false });
112
+
113
+ var Page = await Browser.newPage();
114
+
115
+ if (Proxy && Proxy[0] && Proxy[1])
116
+ await Page.authenticate({
117
+ username: Proxy[0],
118
+ password: Proxy[1]
119
+ });
120
+
121
+ await Page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36");
122
+ await Page.goto("https://www.facebook.com");
123
+ await Page.type("input[name=\"email\"]", email);
124
+ await Page.type("input[name=\"pass\"]", password);
125
+ await Page.click("button[name=\"login\"]");
126
+ var waitForLoad = Page.waitForNavigation({ waitUntil: "networkidle0" });
127
+ await new Promise(resolve => setTimeout(resolve, 5000));
128
+ var currentUrl = Page.url();
129
+
130
+ if (currentUrl.indexOf("/two_step_verification/two_factor/") > -1 || currentUrl.indexOf("/checkpoint/465803052217681") > -1) {
131
+ log.warn("facebook.login.checkpoint");
132
+ var timeoutCheckpoint = setTimeout(function () {
133
+ log.warn("facebook.login.missTimeCheckpoint");
134
+ process.exit(1);
135
+ }, 2 * 60 * 1000);
136
+ if (await Page.evaluate(_ => document.querySelectorAll("input").length) === 1) {
137
+ async function waitForCheckpointSMS(length) {
138
+ var NotChangeURL = _ => currentUrl === Page.url();
139
+ var ReClick = _ => Page.click("span[class=\"x1lliihq x193iq5w x6ikm8r x10wlt62 xlyipyv xuxw1ft\"]");
140
+ var GetCodeSMS = _ => log.input("facebook.access.codeSMS", "yellow", 60 * 1000);
141
+ var EnterCodeSMS = SMS => length === 0 ? Page.type("input[dir=\"ltr\"", SMS) : Page.evaluate(SMS => {
142
+ var input = document.querySelector("input[dir=\"ltr\"");
143
+ if (input) {
144
+ input.value = SMS;
145
+ input.dispatchEvent(new Event("input", { bubbles: true }));
146
+ }
147
+ }, SMS);
148
+
149
+ if (length > 2)
150
+ throw new Error("Cant Authenticated With CodeSMS.");
151
+ var SMS = await GetCodeSMS();
152
+ if (SMS) {
153
+ await EnterCodeSMS(SMS);
154
+ await ReClick();
155
+ await new Promise(resolve => setTimeout(resolve, 2000));
156
+ if (NotChangeURL()) {
157
+ log.warn("facebook.access.wrongSMS");
158
+ return waitForCheckpointSMS(length + 1);
159
+ }
160
+ } else
161
+ throw new Error("CodeSMS is undefined.");
162
+ }
163
+
164
+ await waitForCheckpointSMS(0);
165
+ } else {
166
+ var intervalCheckURL;
167
+ var waitForCheckpoint = new Promise(resolve => {
168
+ intervalCheckURL = setInterval(function () {
169
+ var currentURL = Page.url();
170
+ if (currentURL.indexOf("/two_factor/remember_browser/") > -1)
171
+ resolve();
172
+ }, 5000);
173
+ });
174
+ await waitForCheckpoint;
175
+ clearInterval(intervalCheckURL);
176
+ }
177
+ clearTimeout(timeoutCheckpoint);
178
+ await Page.click("div[class=\"x1n2onr6 x1ja2u2z x78zum5 x2lah0s xl56j7k x6s0dn4 xozqiw3 x1q0g3np x9f619 xi112ho x17zwfj4 x585lrc x1403ito x1qhmfi1 x1s9qjmn x39innc x7gj0x1 x1mpseq2 x13fuv20 xu3j5b3 x1q0q8m5 x26u7qi x178xt8z xm81vs4 xso031l xy80clv x1fq8qgq x1ghtduv x1oktzhs\"");
179
+ }
180
+
181
+ if (currentUrl.indexOf("/two_step_verification/authentication/") > -1) {
182
+ log.warn("facebook.login.checkpointImage");
183
+ process.exit(1);
184
+ }
185
+
186
+ if (currentUrl.indexOf("/login/") > -1) {
187
+ log.warn("facebook.login.wrong");
188
+ process.exit(1);
189
+ }
190
+
191
+ await waitForLoad;
192
+ var appState = await Page.cookies();
193
+ await Browser.close();
194
+ return appState;
195
+ }
196
+
197
+ function LoginHelper(appState, email, password, proxy, whiteID, loginCount) {
198
+ if (appState) {
199
+ var jar = utils.getJar();
200
+
201
+ if (typeof appState === "string") {
202
+ appState = decodeURIComponent(appState).split("; ").map(item => {
203
+ var [key, value] = item.split("=");
204
+ return {
205
+ key,
206
+ value,
207
+ domain: "facebook.com",
208
+ path: "/",
209
+ expires: new Date().getTime() + 1000 * 60 * 60 * 24 * 365
210
+ }
211
+ });
212
+ }
213
+ appState.map(item => {
214
+ var string = [
215
+ (item.key || item.name) + "=" + item.value,
216
+ "expires=" + item.expires,
217
+ "domain=" + item.domain,
218
+ "path=" + item.path
219
+ ].join("; ");
220
+ jar.setCookie(utils.cookie(string), "http://" + item.domain);
221
+ });
222
+
223
+ return utils
224
+ .get("https://www.facebook.com", jar, null, null, { noRef: true })
225
+ .then(function (res) {
226
+ var reg = /<meta http-equiv="refresh" content="0;url=([^"]+)[^>]+>/;
227
+ var redirect = reg.exec(res.body);
228
+ if (redirect && redirect[1]) {
229
+ return utils
230
+ .get(redirect[1], jar);
231
+ }
232
+ return res;
233
+ })
234
+ .then(res => buildAPIs(res.body, jar, whiteID, proxy, loginCount))
235
+ .catch(function (error) {
236
+ log.error("facebook.login.error");
237
+ console.log(error);
238
+ process.exit(1);
239
+ });
240
+ } else if (email && email.length > 0 && password && password.length > 0) {
241
+ return makeLogin(email, password, proxy)
242
+ .then(fbState => LoginHelper(fbState, email, password, proxy, whiteID, loginCount));
243
+ } else {
244
+ log.warn("facebook.missing");
245
+ process.exit(1);
246
+ }
247
+ }
248
+
249
+ function Login(firstLogin = true, loginCount = 1) {
250
+ if (loginCount >= 3) {
251
+ log.warn("facebook.relogin.error");
252
+ process.exit(1);
253
+ }
254
+
255
+ var appState;
256
+ var { email, password, cookies, facebookState, proxy, whiteID } = global.mira.config.facebookAccountOptions;
257
+ var path = __dirname + "/../../" + facebookState;
258
+
259
+ if (facebookState && facebookState.length > 0 && fs.existsSync(path))
260
+ appState = require(path);
261
+ else if (cookies && cookies.length > 0)
262
+ appState = cookies;
263
+
264
+ if (proxy && proxy.length > 0)
265
+ utils.setProxy(proxy);
266
+
267
+ proxy = proxy || "";
268
+ appState = firstLogin ? appState : null;
269
+ return LoginHelper(appState, email, password, proxy, whiteID, loginCount);
270
+ }
271
+
272
+ module.exports = Login;
source/apis/lib/Client.js ADDED
@@ -0,0 +1,1132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../utils");
2
+ var mqtt = require("mqtt");
3
+ var websocket = require("websocket-stream");
4
+ var HttpsProxyAgent = require("https-proxy-agent");
5
+ var EventEmitter = require("events");
6
+ var topics = [
7
+ "/legacy_web",
8
+ "/webrtc",
9
+ "/rtc_multi",
10
+ "/onevc",
11
+ "/br_sr",
12
+ "/sr_res",
13
+ "/t_ms",
14
+ "/thread_typing",
15
+ "/orca_typing_notifications",
16
+ "/notify_disconnect",
17
+ "/orca_presence",
18
+ "/legacy_web_mtouch",
19
+ "/t_rtc_multi",
20
+ "/ls_foreground_state",
21
+ "/ls_resp",
22
+ "/inbox",
23
+ "/mercury",
24
+ "/messaging_events",
25
+ "/orca_message_notifications",
26
+ "/pp",
27
+ "/webrtc_response"
28
+ ];
29
+
30
+ function notificationConnect(ctx) {
31
+ var next = true;
32
+ return utils
33
+ .get("https://www.facebook.com/notifications", ctx.jar)
34
+ .catch(function (error) {
35
+ if (error.type === "logout.")
36
+ ctx.isLogin = false;
37
+
38
+ next = false;
39
+ console.log(error);
40
+ })
41
+ .finally(function () {
42
+ if (next && ctx.listenNotif)
43
+ return setTimeout(notificationConnect, 1000, ctx);
44
+ });
45
+ }
46
+
47
+ function markDelivery(apis, threadID, messageID) {
48
+ if (threadID && messageID)
49
+ apis.markAsDelivered(threadID, messageID, error => !error ? apis.markAsRead(threadID) : null);
50
+ }
51
+
52
+ function parseAndReCallback(http, apis, ctx, globalCallback, deltails) {
53
+ function getExtension(original_extension, fullFileName = "") {
54
+ if (original_extension)
55
+ return original_extension;
56
+ else {
57
+ var extension = fullFileName.split(".").pop();
58
+ if (extension === fullFileName)
59
+ return "";
60
+ else
61
+ return extension;
62
+ }
63
+ }
64
+
65
+ function formatAttachment(attachment1, attachment2) {
66
+ var fullFileName = attachment1.filename;
67
+ var fileSize = Number(attachment1.fileSize || 0);
68
+ var durationVideo = attachment1.genericMetadata ? Number(attachment1.genericMetadata.videoLength) : undefined;
69
+ var durationAudio = attachment1.genericMetadata ? Number(attachment1.genericMetadata.duration) : undefined;
70
+ var mimeType = attachment1.mimeType;
71
+
72
+ attachment2 = attachment2 || { id: "", image_data: {} };
73
+ attachment1 = attachment1.mercury || attachment1;
74
+ var blob = attachment1.blob_attachment || attachment1.sticker_attachment;
75
+ var type = blob && blob.__typename ? blob.__typename : attachment1.attach_type;
76
+ if (!type && attachment1.sticker_attachment) {
77
+ type = "StickerAttachment";
78
+ blob = attachment1.sticker_attachment;
79
+ } else if (!type && attachment1.extensible_attachment) {
80
+ if (attachment1.extensible_attachment.story_attachment && attachment1.extensible_attachment.story_attachment.target && attachment1.extensible_attachment.story_attachment.target.__typename && attachment1.extensible_attachment.story_attachment.target.__typename === "MessageLocation")
81
+ type = "MessageLocation";
82
+ else
83
+ type = "ExtensibleAttachment";
84
+ blob = attachment1.extensible_attachment;
85
+ }
86
+ switch (type) {
87
+ case "sticker":
88
+ return {
89
+ type: "sticker",
90
+ ID: attachment1.metadata.stickerID.toString(),
91
+ url: attachment1.url,
92
+ packID: attachment1.metadata.packID.toString(),
93
+ spriteUrl: attachment1.metadata.spriteURI,
94
+ spriteUrl2x: attachment1.metadata.spriteURI2x,
95
+ width: attachment1.metadata.width,
96
+ height: attachment1.metadata.height,
97
+ caption: attachment2.caption,
98
+ description: attachment2.description,
99
+ frameCount: attachment1.metadata.frameCount,
100
+ frameRate: attachment1.metadata.frameRate,
101
+ framesPerRow: attachment1.metadata.framesPerRow,
102
+ framesPerCol: attachment1.metadata.framesPerCol,
103
+ stickerID: attachment1.metadata.stickerID.toString(),
104
+ spriteURI: attachment1.metadata.spriteURI,
105
+ spriteURI2x: attachment1.metadata.spriteURI2x
106
+ }
107
+ case "file":
108
+ return {
109
+ type: "file",
110
+ ID: attachment2.id.toString(),
111
+ fullFileName,
112
+ filename: attachment1.name,
113
+ fileSize,
114
+ original_extension: getExtension(attachment1.original_extension, fullFileName),
115
+ mimeType,
116
+ url: attachment1.url,
117
+ isMalicious: attachment2.is_malicious,
118
+ contentType: attachment2.mime_type,
119
+ name: attachment1.name
120
+ }
121
+ case "photo":
122
+ return {
123
+ type: "photo",
124
+ ID: attachment1.metadata.fbid.toString(),
125
+ filename: attachment1.fileName,
126
+ fullFileName,
127
+ fileSize,
128
+ original_extension: getExtension(attachment1.original_extension, fullFileName),
129
+ mimeType,
130
+ thumbnailUrl: attachment1.thumbnail_url,
131
+ previewUrl: attachment1.preview_url,
132
+ previewWidth: attachment1.preview_width,
133
+ previewHeight: attachment1.preview_height,
134
+ largePreviewUrl: attachment1.large_preview_url,
135
+ largePreviewWidth: attachment1.large_preview_width,
136
+ largePreviewHeight: attachment1.large_preview_height,
137
+ url: attachment1.metadata.url,
138
+ width: attachment1.metadata.dimensions.split(",")[0],
139
+ height: attachment1.metadata.dimensions.split(",")[1],
140
+ name: fullFileName
141
+ }
142
+ case "animated_image":
143
+ return {
144
+ type: "animated_image",
145
+ ID: attachment2.id.toString(),
146
+ filename: attachment2.filename,
147
+ fullFileName: fullFileName,
148
+ original_extension: getExtension(attachment2.original_extension, fullFileName),
149
+ mimeType,
150
+ previewUrl: attachment1.preview_url,
151
+ previewWidth: attachment1.preview_width,
152
+ previewHeight: attachment1.preview_height,
153
+ url: attachment2.image_data.url,
154
+ width: attachment2.image_data.width,
155
+ height: attachment2.image_data.height,
156
+ name: attachment1.name,
157
+ facebookUrl: attachment1.url,
158
+ thumbnailUrl: attachment1.thumbnail_url,
159
+ rawGifImage: attachment2.image_data.raw_gif_image,
160
+ rawWebpImage: attachment2.image_data.raw_webp_image,
161
+ animatedGifUrl: attachment2.image_data.animated_gif_url,
162
+ animatedGifPreviewUrl: attachment2.image_data.animated_gif_preview_url,
163
+ animatedWebpUrl: attachment2.image_data.animated_webp_url,
164
+ animatedWebpPreviewUrl: attachment2.image_data.animated_webp_preview_url
165
+ }
166
+ case "share":
167
+ return {
168
+ type: "share",
169
+ ID: attachment1.share.share_id.toString(),
170
+ url: attachment2.href,
171
+ title: attachment1.share.title,
172
+ description: attachment1.share.description,
173
+ source: attachment1.share.source,
174
+ image: attachment1.share.media.image,
175
+ width: attachment1.share.media.image_size.width,
176
+ height: attachment1.share.media.image_size.height,
177
+ playable: attachment1.share.media.playable,
178
+ duration: attachment1.share.media.duration,
179
+ subattachments: attachment1.share.subattachments,
180
+ properties: {},
181
+ animatedImageSize: attachment1.share.media.animated_image_size,
182
+ facebookUrl: attachment1.share.uri,
183
+ target: attachment1.share.target,
184
+ styleList: attachment1.share.style_list
185
+ }
186
+ case "video":
187
+ return {
188
+ type: "video",
189
+ ID: attachment1.metadata.fbid.toString(),
190
+ filename: attachment1.name,
191
+ fullFileName: fullFileName,
192
+ original_extension: getExtension(attachment1.original_extension, fullFileName),
193
+ mimeType,
194
+ duration: durationVideo,
195
+ previewUrl: attachment1.preview_url,
196
+ previewWidth: attachment1.preview_width,
197
+ previewHeight: attachment1.preview_height,
198
+ url: attachment1.url,
199
+ width: attachment1.metadata.dimensions.width,
200
+ height: attachment1.metadata.dimensions.height,
201
+ videoType: "unknown",
202
+ thumbnailUrl: attachment1.thumbnail_url
203
+ }
204
+ case "error":
205
+ return {
206
+ type: "error",
207
+ attachment1: attachment1,
208
+ attachment2: attachment2
209
+ }
210
+ case "MessageImage":
211
+ return {
212
+ type: "photo",
213
+ ID: blob.legacy_attachment_id,
214
+ filename: blob.filename,
215
+ fullFileName,
216
+ fileSize,
217
+ original_extension: getExtension(blob.original_extension, fullFileName),
218
+ mimeType,
219
+ thumbnailUrl: blob.thumbnail.uri,
220
+ previewUrl: blob.preview.uri,
221
+ previewWidth: blob.preview.width,
222
+ previewHeight: blob.preview.height,
223
+ largePreviewUrl: blob.large_preview.uri,
224
+ largePreviewWidth: blob.large_preview.width,
225
+ largePreviewHeight: blob.large_preview.height,
226
+ url: blob.large_preview.uri,
227
+ width: blob.original_dimensions.x,
228
+ height: blob.original_dimensions.y,
229
+ name: blob.filename
230
+ }
231
+ case "MessageAnimatedImage":
232
+ return {
233
+ type: "animated_image",
234
+ ID: blob.legacy_attachment_id,
235
+ filename: blob.filename,
236
+ fullFileName,
237
+ original_extension: getExtension(blob.original_extension, fullFileName),
238
+ mimeType,
239
+ previewUrl: blob.preview_image.uri,
240
+ previewWidth: blob.preview_image.width,
241
+ previewHeight: blob.preview_image.height,
242
+ url: blob.animated_image.uri,
243
+ width: blob.animated_image.width,
244
+ height: blob.animated_image.height,
245
+ thumbnailUrl: blob.preview_image.uri,
246
+ name: blob.filename,
247
+ facebookUrl: blob.animated_image.uri,
248
+ rawGifImage: blob.animated_image.uri,
249
+ animatedGifUrl: blob.animated_image.uri,
250
+ animatedGifPreviewUrl: blob.preview_image.uri,
251
+ animatedWebpUrl: blob.animated_image.uri,
252
+ animatedWebpPreviewUrl: blob.preview_image.uri
253
+ }
254
+ case "MessageVideo":
255
+ return {
256
+ type: "video",
257
+ ID: blob.legacy_attachment_id,
258
+ filename: blob.filename,
259
+ fullFileName,
260
+ original_extension: getExtension(blob.original_extension, fullFileName),
261
+ fileSize: fileSize,
262
+ duration: durationVideo,
263
+ mimeType,
264
+ previewUrl: blob.large_image.uri,
265
+ previewWidth: blob.large_image.width,
266
+ previewHeight: blob.large_image.height,
267
+ url: blob.playable_url,
268
+ width: blob.original_dimensions.x,
269
+ height: blob.original_dimensions.y,
270
+ videoType: blob.video_type.toLowerCase(),
271
+ thumbnailUrl: blob.large_image.uri
272
+ }
273
+ case "MessageAudio":
274
+ return {
275
+ type: "audio",
276
+ ID: blob.url_shimhash,
277
+ filename: blob.filename,
278
+ fullFileName,
279
+ fileSize,
280
+ duration: durationAudio,
281
+ original_extension: getExtension(blob.original_extension, fullFileName),
282
+ mimeType,
283
+ audioType: blob.audio_type,
284
+ url: blob.playable_url,
285
+ isVoiceMail: blob.is_voicemail
286
+ }
287
+ case "StickerAttachment":
288
+ case "Sticker":
289
+ return {
290
+ type: "sticker",
291
+ ID: blob.id,
292
+ url: blob.url,
293
+ packID: blob.pack ? blob.pack.id : null,
294
+ spriteUrl: blob.sprite_image,
295
+ spriteUrl2x: blob.sprite_image_2x,
296
+ width: blob.width,
297
+ height: blob.height,
298
+ caption: blob.label,
299
+ description: blob.label,
300
+ frameCount: blob.frame_count,
301
+ frameRate: blob.frame_rate,
302
+ framesPerRow: blob.frames_per_row,
303
+ framesPerCol: blob.frames_per_column,
304
+ stickerID: blob.id,
305
+ spriteURI: blob.sprite_image,
306
+ spriteURI2x: blob.sprite_image_2x
307
+ }
308
+ case "MessageLocation":
309
+ var urlAttach = blob.story_attachment.url;
310
+ var mediaAttach = blob.story_attachment.media;
311
+ var u = querystring.parse(url.parse(urlAttach).query).u;
312
+ var where1 = querystring.parse(url.parse(u).query).where1;
313
+ var address = where1.split(", ");
314
+ var latitude;
315
+ var longitude;
316
+
317
+ try {
318
+ latitude = Number.parseFloat(address[0]);
319
+ longitude = Number.parseFloat(address[1]);
320
+ } finally { }
321
+
322
+ var imageUrl;
323
+ var width;
324
+ var height;
325
+ if (mediaAttach && mediaAttach.image) {
326
+ imageUrl = mediaAttach.image.uri;
327
+ width = mediaAttach.image.width;
328
+ height = mediaAttach.image.height;
329
+ }
330
+
331
+ return {
332
+ type: "location",
333
+ ID: blob.legacy_attachment_id,
334
+ latitude,
335
+ longitude,
336
+ image: imageUrl,
337
+ width,
338
+ height,
339
+ url: u || urlAttach,
340
+ address: where1,
341
+ facebookUrl: blob.story_attachment.url,
342
+ target: blob.story_attachment.target,
343
+ styleList: blob.story_attachment.style_list
344
+ }
345
+ case "ExtensibleAttachment":
346
+ return {
347
+ type: "share",
348
+ ID: blob.legacy_attachment_id,
349
+ url: blob.story_attachment.url,
350
+ title: blob.story_attachment.title_with_entities.text,
351
+ description: blob.story_attachment.description && blob.story_attachment.description.text,
352
+ source: blob.story_attachment.source ? blob.story_attachment.source.text : null,
353
+ image: blob.story_attachment.media && blob.story_attachment.media.image && blob.story_attachment.media.image.uri,
354
+ width: blob.story_attachment.media && blob.story_attachment.media.image && blob.story_attachment.media.image.width,
355
+ height: blob.story_attachment.media && blob.story_attachment.media.image && blob.story_attachment.media.image.height,
356
+ playable: blob.story_attachment.media && blob.story_attachment.media.is_playable,
357
+ duration: blob.story_attachment.media && blob.story_attachment.media.playable_duration_in_ms,
358
+ playableUrl: !blob.story_attachment.media ? null : blob.story_attachment.media.playable_url,
359
+ subattachments: blob.story_attachment.subattachments,
360
+ properties: blob.story_attachment.properties.reduce(function (obj, cur) {
361
+ obj[cur.key] = cur.value.text;
362
+ return obj;
363
+ }, {}),
364
+ facebookUrl: blob.story_attachment.url,
365
+ target: blob.story_attachment.target,
366
+ styleList: blob.story_attachment.style_list
367
+ }
368
+ case "MessageFile":
369
+ return {
370
+ type: "file",
371
+ ID: blob.message_file_fbid,
372
+ fullFileName,
373
+ filename: blob.filename,
374
+ fileSize,
375
+ mimeType: blob.mimetype,
376
+ original_extension: blob.original_extension || fullFileName.split(".").pop(),
377
+ url: blob.url,
378
+ isMalicious: blob.is_malicious,
379
+ contentType: blob.content_type,
380
+ name: blob.filename
381
+ }
382
+ default:
383
+ throw new Error("unrecognized attach_file of type " + type + "`" + JSON.stringify(attachment1, null, 4) + " attachment2: " + JSON.stringify(attachment2, null, 4) + "`");
384
+ }
385
+ }
386
+
387
+ if (deltails.class === "NewMessage") {
388
+ function formatMessage() {
389
+ var md = deltails.messageMetadata;
390
+ var mdata = !deltails.data ? [] : !deltails.data.prng ? [] : JSON.parse(deltails.data.prng);
391
+ var m_id = mdata.map(u => u.i);
392
+ var m_offset = mdata.map(u => u.o);
393
+ var m_length = mdata.map(u => u.l);
394
+ var mentions = {};
395
+ for (var i = 0; i < m_id.length; i++)
396
+ mentions[m_id[i]] = deltails.body.substring(m_offset[i], m_offset[i] + m_length[i]);
397
+
398
+ return {
399
+ type: "message",
400
+ senderID: utils.formatID(md.actorFbId.toString()),
401
+ body: deltails.body || "",
402
+ threadID: utils.formatID((md.threadKey.threadFbId || md.threadKey.otherUserFbId).toString()),
403
+ messageID: md.messageId,
404
+ attachments: (deltails.attachments || []).map(v => formatAttachment(v)),
405
+ mentions,
406
+ timestamp: md.timestamp,
407
+ isGroup: !!md.threadKey.threadFbId,
408
+ participantIDs: deltails.participants || []
409
+ }
410
+ }
411
+
412
+ (function resolveAttachmentUrl(i) {
413
+ if (i === (deltails.attachments || []).length) {
414
+ try {
415
+ var message = formatMessage();
416
+ (message.senderID !== ctx.userID || ctx.globalOptions.listenSelf) ? globalCallback(null, message) : null;
417
+ if (ctx.globalOptions.autoMarkDelivery)
418
+ markDelivery(apis, message.threadID, message.messageID);
419
+ } catch (error) {
420
+ error = {
421
+ error: "Problem parsing message object. Please open an issue at https://github.com/GiaKhang1810/mira-bot-v1/issues.",
422
+ detail: error,
423
+ response: deltails,
424
+ type: "parse_error"
425
+ }
426
+ globalCallback(error);
427
+ }
428
+ } else {
429
+ if (deltails.attachments[i].mercury.attach_type === "photo") {
430
+ apis.resolvePhotoUrl(deltails.attachments[i].fbid, (e, u) => e ? deltails.attachments[i].mercury.metadata.url = u : null, resolveAttachmentUrl(i + 1));
431
+ } else {
432
+ return resolveAttachmentUrl(i + 1);
433
+ }
434
+ }
435
+ })(0);
436
+ }
437
+
438
+ if (deltails.class === "ClientPayload") {
439
+ var ClientPayload = JSON.parse(String.fromCharCode.apply(null, deltails.payload));
440
+ if (ClientPayload && ClientPayload.deltas) {
441
+ for (var i in ClientPayload.deltas) {
442
+ var delta = ClientPayload.deltas[i];
443
+
444
+ if (delta.deltaMessageReaction && ctx.globalOptions.listenEvents) {
445
+ var reaction = {
446
+ type: "message_reaction",
447
+ threadID: (delta.deltaMessageReaction.threadKey.threadFbId ? delta.deltaMessageReaction.threadKey.threadFbId : delta.deltaMessageReaction.threadKey.otherUserFbId).toString(),
448
+ messageID: delta.deltaMessageReaction.messageId,
449
+ reaction: delta.deltaMessageReaction.reaction,
450
+ senderID: delta.deltaMessageReaction.senderId.toString(),
451
+ userID: (delta.deltaMessageReaction.userId || delta.deltaMessageReaction.senderId).toString()
452
+ }
453
+ globalCallback(null, reaction);
454
+ } else if (delta.deltaRecallMessageData && ctx.globalOptions.listenEvents) {
455
+ var unsend = {
456
+ type: "message_unsend",
457
+ threadID: (delta.deltaRecallMessageData.threadKey.threadFbId ? delta.deltaRecallMessageData.threadKey.threadFbId : delta.deltaRecallMessageData.threadKey.otherUserFbId).toString(),
458
+ messageID: delta.deltaRecallMessageData.messageID,
459
+ senderID: delta.deltaRecallMessageData.senderID.toString(),
460
+ deletionTimestamp: delta.deltaRecallMessageData.deletionTimestamp,
461
+ timestamp: delta.deltaRecallMessageData.timestamp
462
+ }
463
+ globalCallback(null, unsend);
464
+ } else if (delta.deltaRemoveMessage && ctx.globalOptions.listenEvents) {
465
+ var del = {
466
+ type: "message_self_delete",
467
+ threadID: (delta.deltaRemoveMessage.threadKey.threadFbId ? delta.deltaRemoveMessage.threadKey.threadFbId : delta.deltaRemoveMessage.threadKey.otherUserFbId).toString(),
468
+ messageID: delta.deltaRemoveMessage.messageIds.length === 1 ? delta.deltaRemoveMessage.messageIds[0] : delta.deltaRemoveMessage.messageIds,
469
+ senderID: ctx.userID,
470
+ deletionTimestamp: delta.deltaRemoveMessage.deletionTimestamp,
471
+ timestamp: delta.deltaRemoveMessage.timestamp
472
+ }
473
+ globalCallback(null, del);
474
+ } else if (delta.deltaMessageReply) {
475
+ var mdata = !delta.deltaMessageReply.message ? [] : !delta.deltaMessageReply.message.data ? [] : !delta.deltaMessageReply.message.data.prng ? [] : JSON.parse(delta.deltaMessageReply.message.data.prng);
476
+ var m_id = mdata.map(u => u.i);
477
+ var m_offset = mdata.map(u => u.o);
478
+ var m_length = mdata.map(u => u.l);
479
+ var mentions = {}
480
+
481
+ for (var i = 0; i < m_id.length; i++)
482
+ mentions[m_id[i]] = (delta.deltaMessageReply.message.body || "").substring(m_offset[i], m_offset[i] + m_length[i]);
483
+
484
+ var callbackToReturn = {
485
+ type: "message_reply",
486
+ threadID: (delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId ? delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId : delta.deltaMessageReply.message.messageMetadata.threadKey.otherUserFbId).toString(),
487
+ messageID: delta.deltaMessageReply.message.messageMetadata.messageId,
488
+ senderID: delta.deltaMessageReply.message.messageMetadata.actorFbId.toString(),
489
+ attachments: (delta.deltaMessageReply.message.attachments || []).map(att => {
490
+ var mercury = JSON.parse(att.mercuryJSON);
491
+ Object.assign(att, mercury);
492
+ return att;
493
+ }).map(att => {
494
+ var x;
495
+ try {
496
+ x = formatAttachment(att);
497
+ } catch (ex) {
498
+ x = att;
499
+ x.error = ex;
500
+ x.type = "unknown";
501
+ }
502
+ return x;
503
+ }),
504
+ body: delta.deltaMessageReply.message.body || "",
505
+ isGroup: !!delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId,
506
+ mentions,
507
+ timestamp: delta.deltaMessageReply.message.messageMetadata.timestamp,
508
+ participantIDs: (delta.deltaMessageReply.message.messageMetadata.cid.canonicalParticipantFbids || delta.deltaMessageReply.message.participants || []).map(e => e.toString())
509
+ }
510
+
511
+ if (delta.deltaMessageReply.repliedToMessage) {
512
+ mdata = !delta.deltaMessageReply.repliedToMessage ? [] : !delta.deltaMessageReply.repliedToMessage.data ? [] : !delta.deltaMessageReply.repliedToMessage.data.prng ? [] : JSON.parse(delta.deltaMessageReply.repliedToMessage.data.prng);
513
+ m_id = mdata.map(u => u.i);
514
+ m_offset = mdata.map(u => u.o);
515
+ m_length = mdata.map(u => u.l);
516
+ var rmentions = {}
517
+
518
+ for (var i = 0; i < m_id.length; i++)
519
+ rmentions[m_id[i]] = (delta.deltaMessageReply.repliedToMessage.body || "").substring(m_offset[i], m_offset[i] + m_length[i]);
520
+
521
+ callbackToReturn.messageReply = {
522
+ threadID: (delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId ? delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId : delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.otherUserFbId).toString(),
523
+ messageID: delta.deltaMessageReply.repliedToMessage.messageMetadata.messageId,
524
+ senderID: delta.deltaMessageReply.repliedToMessage.messageMetadata.actorFbId.toString(),
525
+ attachments: delta.deltaMessageReply.repliedToMessage.attachments.map(att => {
526
+ var mercury = JSON.parse(att.mercuryJSON);
527
+ Object.assign(att, mercury);
528
+ return att;
529
+ }).map(att => {
530
+ var x;
531
+ try {
532
+ x = formatAttachment(att);
533
+ } catch (ex) {
534
+ x = att;
535
+ x.error = ex;
536
+ x.type = "unknown";
537
+ }
538
+ return x;
539
+ }),
540
+ body: delta.deltaMessageReply.repliedToMessage.body || "",
541
+ isGroup: !!delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId,
542
+ mentions: rmentions,
543
+ timestamp: delta.deltaMessageReply.repliedToMessage.messageMetadata.timestamp
544
+ };
545
+ } else if (delta.deltaMessageReply.replyToMessageId) {
546
+ return http
547
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, {
548
+ queries: JSON.stringify({
549
+ o0: {
550
+ doc_id: "2848441488556444",
551
+ query_params: {
552
+ thread_and_message_id: {
553
+ thread_id: callbackToReturn.threadID,
554
+ message_id: delta.deltaMessageReply.replyToMessageId.id
555
+ }
556
+ }
557
+ }
558
+ })
559
+ })
560
+ .then(utils.parseAndCheckLogin(ctx, http))
561
+ .then(resData => {
562
+ if (resData[resData.length - 1].error_results > 0)
563
+ throw resData[0].o0.errors;
564
+
565
+
566
+ if (resData[resData.length - 1].successful_results === 0)
567
+ throw { error: "forcedFetch: there was no successful_results", response: resData };
568
+
569
+ var fetchData = resData[0].o0.data.message;
570
+ var mobj = {}
571
+ for (var n in fetchData.message.ranges)
572
+ mobj[fetchData.message.ranges[n].entity.id] = (fetchData.message.text || "").substr(fetchData.message.ranges[n].offset, fetchData.message.ranges[n].length);
573
+
574
+
575
+ callbackToReturn.messageReply = {
576
+ threadID: callbackToReturn.threadID,
577
+ messageID: fetchData.message_id,
578
+ senderID: fetchData.message_sender.id.toString(),
579
+ attachments: fetchData.message.blob_attachment.map(att => {
580
+ var x;
581
+ try {
582
+ x = formatAttachment({ blob_attachment: att });
583
+ } catch (ex) {
584
+ x = att;
585
+ x.error = ex;
586
+ x.type = "unknown";
587
+ }
588
+ return x;
589
+ }),
590
+ body: fetchData.message.text || "",
591
+ isGroup: callbackToReturn.isGroup,
592
+ mentions: mobj,
593
+ timestamp: parseInt(fetchData.timestamp_precise)
594
+ };
595
+ })
596
+ .catch(console.log)
597
+ .finally(function () {
598
+ if (ctx.globalOptions.autoMarkDelivery)
599
+ markDelivery(apis, callbackToReturn.threadID, callbackToReturn.messageID);
600
+
601
+ (callbackToReturn.senderID !== ctx.userID || ctx.globalOptions.listenSelf) ? globalCallback(null, callbackToReturn) : null;
602
+ });
603
+ } else
604
+ callbackToReturn.delta = delta;
605
+
606
+ if (ctx.globalOptions.autoMarkDelivery)
607
+ markDelivery(apis, callbackToReturn.threadID, callbackToReturn.messageID);
608
+
609
+ return (callbackToReturn.senderID !== ctx.userID || ctx.globalOptions.listenSelf) ? globalCallback(null, callbackToReturn) : null;
610
+ }
611
+ }
612
+ return;
613
+ }
614
+ }
615
+
616
+ if (deltails.class !== "NewMessage" && !ctx.globalOptions.listenEvents)
617
+ return;
618
+
619
+ function getAdminTextMessageType(type) {
620
+ switch (type) {
621
+ case 'unpin_messages_v2':
622
+ return 'log:unpin-message';
623
+ case 'pin_messages_v2':
624
+ return 'log:pin-message';
625
+ case "change_thread_theme":
626
+ return "log:thread-color";
627
+ case "change_thread_icon":
628
+ return "log:thread-icon";
629
+ case "change_thread_nickname":
630
+ return "log:user-nickname";
631
+ case "change_thread_admins":
632
+ return "log:thread-admins";
633
+ case "group_poll":
634
+ return "log:thread-poll";
635
+ case "change_thread_approval_mode":
636
+ return "log:thread-approval-mode";
637
+ case "messenger_call_log":
638
+ case "participant_joined_group_call":
639
+ return "log:thread-call";
640
+ default:
641
+ return type;
642
+ }
643
+ }
644
+ function formatDeltaEvent() {
645
+ var logMessageType;
646
+ var logMessageData;
647
+ switch (deltails.class) {
648
+ case "AdminTextMessage":
649
+ logMessageData = deltails.untypedData;
650
+ logMessageType = getAdminTextMessageType(deltails.type);
651
+ break;
652
+ case "ThreadName":
653
+ logMessageType = "log:thread-name";
654
+ logMessageData = { name: deltails.name };
655
+ break;
656
+ case "ParticipantsAddedToGroupThread":
657
+ logMessageType = "log:subscribe";
658
+ logMessageData = { addedParticipants: deltails.addedParticipants };
659
+ break;
660
+ case "ParticipantLeftGroupThread":
661
+ logMessageType = "log:unsubscribe";
662
+ logMessageData = { leftParticipantFbId: deltails.leftParticipantFbId };
663
+ break;
664
+ case "ApprovalQueue":
665
+ logMessageType = "log:approval-queue";
666
+ logMessageData = {
667
+ approvalQueue: {
668
+ action: deltails.action,
669
+ recipientFbId: deltails.recipientFbId,
670
+ requestSource: deltails.requestSource,
671
+ ...deltails.messageMetadata
672
+ }
673
+ }
674
+ }
675
+
676
+ return {
677
+ type: "event",
678
+ threadID: utils.formatID((deltails.messageMetadata.threadKey.threadFbId || deltails.messageMetadata.threadKey.otherUserFbId).toString()),
679
+ messageID: deltails.messageMetadata.messageId.toString(),
680
+ logMessageType: logMessageType,
681
+ logMessageData: logMessageData,
682
+ logMessageBody: deltails.messageMetadata.adminText,
683
+ timestamp: deltails.messageMetadata.timestamp,
684
+ author: deltails.messageMetadata.actorFbId,
685
+ participantIDs: deltails.participants
686
+ }
687
+ }
688
+
689
+ function getAdminTextMessageType(type) {
690
+ switch (type) {
691
+ case 'unpin_messages_v2':
692
+ return 'log:unpin-message';
693
+ case 'pin_messages_v2':
694
+ return 'log:pin-message';
695
+ case "change_thread_theme":
696
+ return "log:thread-color";
697
+ case "change_thread_icon":
698
+ return "log:thread-icon";
699
+ case "change_thread_nickname":
700
+ return "log:user-nickname";
701
+ case "change_thread_admins":
702
+ return "log:thread-admins";
703
+ case "group_poll":
704
+ return "log:thread-poll";
705
+ case "change_thread_approval_mode":
706
+ return "log:thread-approval-mode";
707
+ case "messenger_call_log":
708
+ case "participant_joined_group_call":
709
+ return "log:thread-call";
710
+ default:
711
+ return type;
712
+ }
713
+ }
714
+ function formatDeltaEvent() {
715
+ var logMessageType;
716
+ var logMessageData;
717
+ switch (deltails.class) {
718
+ case "AdminTextMessage":
719
+ logMessageData = deltails.untypedData;
720
+ logMessageType = getAdminTextMessageType(deltails.type);
721
+ break;
722
+ case "ThreadName":
723
+ logMessageType = "log:thread-name";
724
+ logMessageData = { name: deltails.name };
725
+ break;
726
+ case "ParticipantsAddedToGroupThread":
727
+ logMessageType = "log:subscribe";
728
+ logMessageData = { addedParticipants: deltails.addedParticipants };
729
+ break;
730
+ case "ParticipantLeftGroupThread":
731
+ logMessageType = "log:unsubscribe";
732
+ logMessageData = { leftParticipantFbId: deltails.leftParticipantFbId };
733
+ break;
734
+ case "ApprovalQueue":
735
+ logMessageType = "log:approval-queue";
736
+ logMessageData = {
737
+ approvalQueue: {
738
+ action: deltails.action,
739
+ recipientFbId: deltails.recipientFbId,
740
+ requestSource: deltails.requestSource,
741
+ ...deltails.messageMetadata
742
+ }
743
+ }
744
+ }
745
+
746
+ return {
747
+ type: "event",
748
+ threadID: utils.formatID((deltails.messageMetadata.threadKey.threadFbId || deltails.messageMetadata.threadKey.otherUserFbId).toString()),
749
+ messageID: deltails.messageMetadata.messageId.toString(),
750
+ logMessageType: logMessageType,
751
+ logMessageData: logMessageData,
752
+ logMessageBody: deltails.messageMetadata.adminText,
753
+ timestamp: deltails.messageMetadata.timestamp,
754
+ author: deltails.messageMetadata.actorFbId,
755
+ participantIDs: deltails.participants
756
+ }
757
+ }
758
+ switch (deltails.class) {
759
+ case "ReadReceipt":
760
+ try {
761
+ var readReceipt = {
762
+ reader: (deltails.threadKey.otherUserFbId || deltails.actorFbId).toString(),
763
+ time: deltails.actionTimestampMs,
764
+ threadID: utils.formatID((deltails.threadKey.otherUserFbId || deltails.threadKey.threadFbId).toString()),
765
+ type: "read_receipt"
766
+ }
767
+ globalCallback(null, readReceipt);
768
+ } catch (error) {
769
+ error = {
770
+ error: "Problem parsing message object. Please open an issue at https://github.com/GiaKhang1810/mira-bot-v1/issues.",
771
+ detail: error,
772
+ response: deltails,
773
+ type: "parse_error"
774
+ }
775
+ globalCallback(error);
776
+ }
777
+ break;
778
+ case "AdminTextMessage":
779
+ switch (deltails.type) {
780
+ case "change_thread_theme":
781
+ case "change_thread_nickname":
782
+ case "change_thread_icon":
783
+ case "change_thread_quick_reaction":
784
+ case "change_thread_admins":
785
+ case "group_poll":
786
+ case "joinable_group_link_mode_change":
787
+ case "magic_words":
788
+ case "change_thread_approval_mode":
789
+ case "messenger_call_log":
790
+ case "participant_joined_group_call":
791
+ try {
792
+ var detailsEvent = formatDeltaEvent();
793
+ globalCallback(null, detailsEvent);
794
+ } catch (error) {
795
+ error = {
796
+ error: "Problem parsing message object. Please open an issue at https://github.com/GiaKhang1810/mira-bot-v1/issues.",
797
+ detail: error,
798
+ response: deltails,
799
+ type: "parse_error"
800
+ }
801
+ }
802
+ break;
803
+ default:
804
+ break;
805
+ }
806
+ break;
807
+ case "ForcedFetch":
808
+ if (!deltails.threadKey)
809
+ return;
810
+ var mid = deltails.messageId;
811
+ var tid = deltails.threadKey.threadFbId;
812
+ if (mid && tid) {
813
+ var form = {
814
+ queries: JSON.stringify({
815
+ o0: {
816
+ doc_id: "2848441488556444",
817
+ query_params: {
818
+ thread_and_message_id: {
819
+ thread_id: tid.toString(),
820
+ message_id: mid
821
+ }
822
+ }
823
+ }
824
+ })
825
+ }
826
+
827
+ http
828
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
829
+ .then(utils.parseAndCheckLogin(ctx, http))
830
+ .then(resData => {
831
+ if (resData[resData.length - 1].error_results > 0)
832
+ throw resData[0].o0.errors;
833
+ if (resData[resData.length - 1].successful_results === 0)
834
+ throw { error: "forcedFetch: there was no successful_results", response: resData }
835
+
836
+ var fetchData = resData[0].o0.data.message;
837
+ if (utils.getType(fetchData) !== "Object")
838
+ return;
839
+
840
+ switch (fetchData.__typename) {
841
+ case "ThreadImageMessage":
842
+ (fetchData.message_sender.id.toString() !== ctx.userID || ctx.globalOptions.listenEventsSelf) ? globalCallback(null, {
843
+ type: "event",
844
+ threadID: utils.formatID(tid.toString()),
845
+ messageID: fetchData.message_id,
846
+ logMessageType: "log:thread-image",
847
+ logMessageData: {
848
+ attachmentID: fetchData.image_with_metadata && fetchData.image_with_metadata.legacy_attachment_id,
849
+ width: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.x,
850
+ height: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.y,
851
+ url: fetchData.image_with_metadata && fetchData.image_with_metadata.preview.uri
852
+ },
853
+ logMessageBody: fetchData.snippet,
854
+ timestamp: fetchData.timestamp_precise,
855
+ author: fetchData.message_sender.id
856
+ }) : null;
857
+ break;
858
+ case "UserMessage":
859
+ globalCallback(null, {
860
+ type: "message",
861
+ senderID: utils.formatID(fetchData.message_sender.id),
862
+ body: fetchData.message.text || "",
863
+ threadID: utils.formatID(tid.toString()),
864
+ messageID: fetchData.message_id,
865
+ attachments: [{
866
+ type: "share",
867
+ ID: fetchData.extensible_attachment.legacy_attachment_id,
868
+ url: fetchData.extensible_attachment.story_attachment.url,
869
+
870
+ title: fetchData.extensible_attachment.story_attachment.title_with_entities.text,
871
+ description: fetchData.extensible_attachment.story_attachment.description.text,
872
+ source: fetchData.extensible_attachment.story_attachment.source,
873
+
874
+ image: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).uri,
875
+ width: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).width,
876
+ height: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).height,
877
+ playable: (fetchData.extensible_attachment.story_attachment.media || {}).is_playable || false,
878
+ duration: (fetchData.extensible_attachment.story_attachment.media || {}).playable_duration_in_ms || 0,
879
+
880
+ subattachments: fetchData.extensible_attachment.subattachments,
881
+ properties: fetchData.extensible_attachment.story_attachment.properties
882
+ }],
883
+ mentions: {},
884
+ timestamp: parseInt(fetchData.timestamp_precise),
885
+ participantIDs: (fetchData.participants || (fetchData.messageMetadata ? fetchData.messageMetadata.cid ? fetchData.messageMetadata.cid.canonicalParticipantFbids : fetchData.messageMetadata.participantIds : []) || []),
886
+ isGroup: (fetchData.message_sender.id != tid.toString())
887
+ });
888
+ break;
889
+ }
890
+ })
891
+ .catch(console.log);
892
+ }
893
+ break;
894
+ case "ThreadName":
895
+ case "ParticipantsAddedToGroupThread":
896
+ case "ParticipantLeftGroupThread":
897
+ case "ApprovalQueue":
898
+ try {
899
+ var detailsEvent = formatDeltaEvent();
900
+ globalCallback(null, detailsEvent);
901
+ } catch (error) {
902
+ error = {
903
+ error: "Problem parsing message object. Please open an issue at https://github.com/ntkhang03/fb-chat-api/issues.",
904
+ detail: error,
905
+ response: deltails,
906
+ type: "parse_error"
907
+ }
908
+ globalCallback(error);
909
+ }
910
+ break;
911
+ }
912
+ }
913
+
914
+ function connectClientWs(http, apis, ctx, globalCallback) {
915
+ var chatOn = ctx.globalOptions.online;
916
+ var foreground = false;
917
+ var sessionID = Math.floor(Math.random() * 9007199254740991) + 1;
918
+ var username = JSON.stringify({
919
+ u: ctx.userID,
920
+ s: sessionID,
921
+ chat_on: chatOn,
922
+ fg: foreground,
923
+ d: utils.getGUID(),
924
+ ct: "websocket",
925
+ aid: "219994525426954",
926
+ mqtt_sid: "",
927
+ cp: 3,
928
+ ecp: 10,
929
+ st: [],
930
+ pm: [],
931
+ dc: "",
932
+ no_auto_fg: true,
933
+ gas: null,
934
+ pack: [],
935
+ a: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Kbody, like Gecko) Chrome/127.0.0.0 Safari/537.36",
936
+ aids: null
937
+ });
938
+ var host = ctx.endpoint ? ctx.endpoint + "&sid=" + sessionID : ctx.region ? "wss://edge-chat.facebook.com/chat?region=" + ctx.region.toLocaleLowerCase() + "&sid=" + sessionID : "wss://edge-chat.facebook.com/chat?sid=" + sessionID;
939
+ var options = {
940
+ clientId: "mqttwsclient",
941
+ protocolId: "MQIsdp",
942
+ protocolVersion: 3,
943
+ username,
944
+ clean: true,
945
+ wsOptions: {
946
+ headers: {
947
+ "Cookie": ctx.jar.getCookies("https://www.facebook.com").join("; "),
948
+ "Origin": "https://www.facebook.com",
949
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Kbody, like Gecko) Chrome/127.0.0.0 Safari/537.36",
950
+ "Referer": "https://www.facebook.com/",
951
+ "Host": new URL(host).hostname
952
+ },
953
+ origin: "https://www.facebook.com",
954
+ protocolVersion: 13
955
+ },
956
+ keepalive: 10,
957
+ reschedulePings: true
958
+ }
959
+
960
+ if (ctx.proxy) {
961
+ var agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
962
+ options.wsOptions.agent = agent;
963
+ }
964
+
965
+ ctx.Client = new mqtt.Client(_ => websocket(host, options.wsOptions), options);
966
+ var Client = ctx.Client;
967
+ Client
968
+ .on("error", function (error) {
969
+ if (error.message === "Invalid header flag bits, must be 0x0 for puback packet")
970
+ return;
971
+ if (ctx.Client)
972
+ ctx.Client.end(false, _ => ctx.Client = null);
973
+ if (ctx.globalOptions.autoReconnect) {
974
+ return getSeqID(http, apis, ctx, globalCallback);
975
+ }
976
+ error = {
977
+ type: "disconnect",
978
+ message: "Connection refused: Server unavailable",
979
+ error
980
+ }
981
+ globalCallback(error);
982
+ })
983
+ .on("close", _ => { })
984
+ .on("connect", function () {
985
+ topics.map(topic => Client.subscribe(topic));
986
+
987
+ var topic;
988
+ var queue = {
989
+ sync_api_version: 10,
990
+ max_deltas_able_to_process: 1000,
991
+ delta_batch_size: 500,
992
+ encoding: "JSON",
993
+ entity_fbid: ctx.userID
994
+ }
995
+
996
+ if (ctx.syncToken) {
997
+ topic = "/messenger_sync_get_diffs";
998
+ queue.last_seq_id = ctx.lastSeqID;
999
+ queue.sync_token = ctx.syncToken;
1000
+ } else {
1001
+ topic = "/messenger_sync_create_queue";
1002
+ queue.initial_titan_sequence_id = ctx.lastSeqID;
1003
+ queue.device_params = null;
1004
+ }
1005
+
1006
+ Client.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
1007
+ Client.publish("/foreground_state", JSON.stringify({ foreground: chatOn }), { qos: 1 });
1008
+ Client.publish("/set_client_settings", JSON.stringify({ make_user_available_when_in_foreground: true }), { qos: 1 });
1009
+ ctx.listenNotif ? notificationConnect(ctx) : null;
1010
+ })
1011
+ .on("message", function (topic, message) {
1012
+ var Message = JSON.parse(Buffer.from(message).toString());
1013
+ if (Message.type === "jewel_requests_add") {
1014
+ globalCallback(null, {
1015
+ type: "friend_request_received",
1016
+ actorFbId: Message.frodeltails.toString(),
1017
+ timestamp: Date.now().toString()
1018
+ });
1019
+ }
1020
+ else if (Message.type === "jewel_requests_remove_old") {
1021
+ globalCallback(null, {
1022
+ type: "friend_request_cancel",
1023
+ actorFbId: Message.frodeltails.toString(),
1024
+ timestamp: Date.now().toString()
1025
+ });
1026
+ }
1027
+ else if (topic === "/t_ms") {
1028
+ if (Message.firstDeltaSeqId && Message.syncToken) {
1029
+ ctx.lastSeqID = Message.firstDeltaSeqId;
1030
+ ctx.syncToken = Message.syncToken;
1031
+ }
1032
+
1033
+ if (Message.lastIssuedSeqId) {
1034
+ ctx.lastSeqID = parseInt(Message.lastIssuedSeqId);
1035
+ }
1036
+
1037
+ for (var i in Message.deltas) {
1038
+ var deltails = Message.deltas[i];
1039
+ parseAndReCallback(http, apis, ctx, globalCallback, deltails);
1040
+ }
1041
+ } else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") {
1042
+ var typ = {
1043
+ type: "typ",
1044
+ isTyping: !!Message.state,
1045
+ from: Message.sender_fbid.toString(),
1046
+ threadID: utils.formatID((Message.thread || Message.sender_fbid).toString())
1047
+ };
1048
+ globalCallback(null, typ);
1049
+ } else if (topic === "/orca_presence") {
1050
+ if (!ctx.globalOptions.updatePresence) {
1051
+ for (var i in Message.list) {
1052
+ var data = Message.list[i];
1053
+ var userID = data["u"];
1054
+
1055
+ var presence = {
1056
+ type: "presence",
1057
+ userID: userID.toString(),
1058
+ timestamp: data["l"] * 1000,
1059
+ statuses: data["p"]
1060
+ }
1061
+ globalCallback(null, presence);
1062
+ }
1063
+ }
1064
+ } else if (Message.type === "notifications_seen") {
1065
+ var notif = {
1066
+ type: "notification",
1067
+ alertIDs: Message.alert_ids,
1068
+ graphQLIDs: Message.graphql_ids,
1069
+ notiGraphQLIDs: Message.notif_graphql_ids,
1070
+ timestamp: Date.now().toString()
1071
+ }
1072
+ globalCallback(null, notif);
1073
+ } else {
1074
+ console.log(topic, Message);
1075
+ }
1076
+ });
1077
+ }
1078
+
1079
+ function getSeqID(http, apis, ctx, globalCallback) {
1080
+ var headers = {
1081
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/600.3.18 (KHTML, like Gecko) Version/8.0.3 Safari/600.3.18"
1082
+ }
1083
+ utils
1084
+ .get("https://www.facebook.com/", ctx.jar, null, null, headers)
1085
+ .then(function (res) {
1086
+ var reg = /<meta http-equiv="refresh" content="0;url=([^"]+)[^>]+>/;
1087
+ var redirect = reg.exec(res.body);
1088
+ if (redirect && redirect[1]) {
1089
+ delete headers.noRef;
1090
+ return utils
1091
+ .get(redirect[1], ctx.jar, null, null, headers);
1092
+ }
1093
+
1094
+ return res;
1095
+ })
1096
+ .then(function (res) {
1097
+ var seqRegex = /irisSeqID:"(\d+)"/.exec(res.body);
1098
+ if (seqRegex && seqRegex[1]) {
1099
+ ctx.lastSeqID = seqRegex[1];
1100
+ connectClientWs(http, apis, ctx, globalCallback);
1101
+ } else {
1102
+ var error = new Error("seqID is undefined.");
1103
+ error.type = "logout.";
1104
+ throw error;
1105
+ }
1106
+ })
1107
+ .catch(function (error) {
1108
+ if (error.type === "logout.")
1109
+ ctx.isLogin = false;
1110
+ console.log(error);
1111
+ return globalCallback(error);
1112
+ });
1113
+ }
1114
+
1115
+ module.exports = function (http, apis, ctx) {
1116
+ var globalCallback;
1117
+ return class Client extends EventEmitter {
1118
+ constructor() {
1119
+ super();
1120
+
1121
+ globalCallback = (error, message) => error ? this.emit("error", error) : this.emit("message", message);
1122
+ getSeqID(http, apis, ctx, globalCallback);
1123
+ return this;
1124
+ }
1125
+
1126
+ disconnect() {
1127
+ globalCallback = () => { }
1128
+ if (ctx.Client)
1129
+ ctx.Client.end(false, _ => ctx.Client = null);
1130
+ }
1131
+ }
1132
+ }
source/apis/lib/Messenger.js ADDED
@@ -0,0 +1,464 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ Origin Source: https://github.com/ntkhang03/Goat-Bot-V2/blob/main/fb-chat-api/
3
+ Converter: Khang
4
+ */
5
+
6
+ var utils = require("../utils");
7
+ var allowedProperties = {
8
+ attachments: true,
9
+ url: true,
10
+ sticker: true,
11
+ emoji: true,
12
+ emojiSize: true,
13
+ body: true,
14
+ mentions: true,
15
+ location: true
16
+ }
17
+
18
+ function removeSpecialChar(inputString) {
19
+ if (typeof inputString !== "string")
20
+ return inputString;
21
+ var buffer = Buffer.from(inputString, "utf8");
22
+ let filteredBuffer = Buffer.alloc(0);
23
+ for (let i = 0; i < buffer.length; i++) {
24
+ if (buffer[i] === 0xEF && buffer[i + 1] === 0xB8 && buffer[i + 2] === 0x8F)
25
+ i += 2;
26
+ else
27
+ filteredBuffer = Buffer.concat([filteredBuffer, buffer.slice(i, i + 1)]);
28
+
29
+ }
30
+
31
+ var convertedString = filteredBuffer.toString("utf8");
32
+ return convertedString;
33
+ }
34
+
35
+ module.exports = function (http, apis, ctx) {
36
+ function edit(message, messageID, callback) {
37
+ var pCallback;
38
+ var returnPromise = new Promise(function (resolve, reject) {
39
+ pCallback = error => error ? reject(error) : resolve();
40
+ });
41
+
42
+ if (typeof message === "function") {
43
+ callback = message;
44
+ message = "";
45
+ }
46
+ if (typeof messageID === "function") {
47
+ callback = messageID;
48
+ messageID = null;
49
+ }
50
+ if (typeof callback !== "function")
51
+ callback = pCallback;
52
+ if (typeof messageID !== "string")
53
+ callback(new Error("messageID must be an string"));
54
+ else if (!ctx.Client)
55
+ callback(new Error("You must connect mqtt first"));
56
+ else {
57
+ var queryPayload = {
58
+ message_id: messageID,
59
+ text: message
60
+ }
61
+ var query = {
62
+ failure_count: null,
63
+ label: "742",
64
+ payload: JSON.stringify(queryPayload),
65
+ queue_name: "edit_message",
66
+ task_id: Math.round(Math.random() * 312312721413).toString()
67
+ }
68
+
69
+ var context = {
70
+ app_id: "2220391788200892",
71
+ payload: JSON.stringify({
72
+ data_trace_id: null,
73
+ epoch_id: 0,
74
+ tasks: [query],
75
+ version_id: "6903494529735864"
76
+ }),
77
+ request_id: Math.round(Math.random() * 312312721413).toString(),
78
+ type: 3
79
+ }
80
+ ctx.Client.publish("/ls_req", JSON.stringify(context), { qos: 1, retain: false });
81
+ callback();
82
+ }
83
+
84
+ return returnPromise;
85
+ }
86
+
87
+ function react(reaction, messageID, callback) {
88
+ var pCallback;
89
+ var returnPromise = new Promise(function (resolve, reject) {
90
+ pCallback = error => error ? reject(error) : resolve();
91
+ });
92
+
93
+ if (typeof reaction === "function") {
94
+ callback = reaction;
95
+ reaction = "";
96
+ }
97
+ if (typeof messageID === "function") {
98
+ callback = messageID;
99
+ messageID = null;
100
+ }
101
+ if (typeof callback !== "function")
102
+ callback = pCallback;
103
+ if (typeof messageID !== "string")
104
+ callback(new Error("messageID must be an string"));
105
+ else {
106
+ var variables = {
107
+ data: {
108
+ client_mutation_id: Math.round(Math.random() * 312312721413).toString(),
109
+ actor_id: ctx.userID,
110
+ action: reaction === "" ? "REMOVE_REACTION" : "ADD_REACTION",
111
+ message_id: messageID,
112
+ reaction
113
+ }
114
+ }
115
+ var qs = {
116
+ doc_id: "1491398900900362",
117
+ variables: JSON.stringify(variables),
118
+ dpr: 1
119
+ }
120
+
121
+ http
122
+ .postData("https://www.facebook.com/webgraphql/mutation/", ctx.jar, {}, qs)
123
+ .then(utils.parseAndCheckLogin(ctx.jar, http))
124
+ .then(function (res) {
125
+ if (res.error || res.errors)
126
+ throw res;
127
+ return callback();
128
+ })
129
+ .catch(function (error) {
130
+ if (error.type === "logout.")
131
+ ctx.isLogin = false;
132
+
133
+ return callback(error);
134
+ });
135
+ }
136
+
137
+ return returnPromise;
138
+ }
139
+
140
+ function unsend(messageID, callback) {
141
+ var pCallback;
142
+ var returnPromise = new Promise(function (resolve, reject) {
143
+ pCallback = error => error ? reject(error) : resolve();
144
+ });
145
+
146
+ if (typeof messageID === "function") {
147
+ callback = messageID;
148
+ messageID = null;
149
+ }
150
+ if (typeof callback !== "function")
151
+ callback = pCallback;
152
+ if (typeof messageID !== "string")
153
+ callback(new Error("messageID must be an string"));
154
+ else {
155
+ http
156
+ .post("https://www.facebook.com/messaging/unsend_message/", ctx.jar, { message_id: messageID })
157
+ .then(utils.parseAndCheckLogin(ctx, http))
158
+ .then(function (res) {
159
+ if (res.error || res.errors)
160
+ throw res;
161
+
162
+ return callback();
163
+ })
164
+ .catch(function (error) {
165
+ if (error.type === "logout.")
166
+ ctx.isLogin = false;
167
+
168
+ return callback(error);
169
+ });
170
+ }
171
+
172
+ return returnPromise;
173
+ }
174
+
175
+ function send(message, threadID, messageID, callback, isGroup) {
176
+ var pCallback;
177
+ var returnPromise = new Promise(function (resolve, reject) {
178
+ pCallback = error => error ? reject(error) : resolve();
179
+ });
180
+
181
+ if (typeof message === "function") {
182
+ callback = message;
183
+ message = null;
184
+ }
185
+ if (typeof threadID === "function") {
186
+ callback = threadID;
187
+ threadID = null;
188
+ }
189
+ if (typeof messageID === "function") {
190
+ callback = messageID;
191
+ messageID = null;
192
+ }
193
+ if (typeof callback !== "function")
194
+ callback = pCallback;
195
+
196
+ var messageType = utils.getType(message);
197
+ var threadIDType = utils.getType(threadID);
198
+ var messageIDType = utils.getType(messageID);
199
+
200
+ var error;
201
+ if (messageType !== "String" && messageType !== "Object")
202
+ error = new Error("Message should be of type string or object and not " + messageType + ".");
203
+ if (threadIDType !== "Array" && threadIDType !== "Number" && threadIDType !== "String")
204
+ error = new Error("ThreadID should be of type number, string, or array and not " + threadIDType + ".");
205
+ if (messageID && messageIDType !== "String")
206
+ error = new Error("MessageID should be of type string and not " + messageIDType + ".");
207
+ if (messageType === "String")
208
+ message = {
209
+ body: message
210
+ }
211
+ if (utils.getType(message.body) === "String")
212
+ message.body = removeSpecialChar(message.body);
213
+
214
+ var disallowedProperties = Object.keys(message).filter(prop => !allowedProperties[prop]);
215
+ if (disallowedProperties.length > 0)
216
+ error = new Error("Dissallowed props: `" + disallowedProperties.join(", ") + "`");
217
+ if (error)
218
+ callback(error);
219
+ else {
220
+ var messageAndOTID = utils.generateOfflineThreadingID();
221
+ var form = {
222
+ client: "mercury",
223
+ action_type: "ma-type:user-generated-message",
224
+ author: "fbid:" + ctx.userID,
225
+ timestamp: Date.now(),
226
+ timestamp_absolute: "Today",
227
+ timestamp_relative: utils.generateTimestampRelative(),
228
+ timestamp_time_passed: "0",
229
+ is_unread: false,
230
+ is_cleared: false,
231
+ is_forward: false,
232
+ is_filtered_content: false,
233
+ is_filtered_content_bh: false,
234
+ is_filtered_content_account: false,
235
+ is_filtered_content_quasar: false,
236
+ is_filtered_content_invalid_app: false,
237
+ is_spoof_warning: false,
238
+ source: "source:chat:web",
239
+ "source_tags[0]": "source:chat",
240
+ body: message.body ? message.body.toString() : "",
241
+ html_body: false,
242
+ ui_push_phase: "V3",
243
+ status: "0",
244
+ offline_threading_id: messageAndOTID,
245
+ message_id: messageAndOTID,
246
+ threading_id: utils.generateThreadingID(ctx.clientID),
247
+ "ephemeral_ttl_mode:": "0",
248
+ manual_retry_cnt: "0",
249
+ has_attachment: !!(message.attachments || message.url || message.sticker),
250
+ signatureID: utils.getSignatureID(),
251
+ replied_to_message_id: messageID
252
+ }
253
+ handleLocation(message, form, threadID, messageAndOTID, callback, isGroup)
254
+ .then(handleSticker)
255
+ .then(handleAttachment)
256
+ .then(handleURL)
257
+ .then(handleEmoji)
258
+ .then(handleMention)
259
+ .then(function (input) {
260
+ if (utils.getType(input[2]) === "Array")
261
+ input[5] = false;
262
+ else {
263
+ if (utils.getType(input[5]) !== "Boolean")
264
+ input[5] = input[2].toString().length < 16;
265
+ else
266
+ input[5] = !input[5];
267
+ }
268
+
269
+ return input;
270
+ })
271
+ .then(sendContent)
272
+ .catch(function (error) {
273
+ if (error.type === "logout.")
274
+ ctx.isLogin = false;
275
+
276
+ return callback(error);
277
+ });
278
+ }
279
+
280
+ return returnPromise;
281
+ }
282
+
283
+ function handleLocation(...input) {
284
+ if (input[0].location) {
285
+ if (input[0].location.latitude === null || input[0].location.longitude === null)
286
+ return Promise.reject(new Error("location property needs both latitude and longitude"));
287
+
288
+ input[1]["location_attachment[coordinates][latitude]"] = input[0].location.latitude;
289
+ input[1]["location_attachment[coordinates][longitude]"] = input[0].location.longitude;
290
+ input[1]["location_attachment[is_current_location]"] = !!input[0].location.current;
291
+ }
292
+ return Promise.resolve(input);
293
+ }
294
+
295
+ function handleEmoji(input) {
296
+ if (input[0].emojiSize !== null && input[0].emoji === null)
297
+ return Promise.reject(new Error("emoji property is empty"));
298
+ if (input[0].emoji) {
299
+ if (input[0].emojiSize == null)
300
+ input[0].emojiSize = "medium";
301
+ if (input[0].emojiSize !== "small" && input[0].emojiSize !== "medium" && input[0].emojiSize !== "large")
302
+ return Promise.reject(new Error("emojiSize property is invalid"));
303
+ if (input[1].body !== null && input[1].body !== "")
304
+ return Promise.reject(new Error("body is not empty"));
305
+ input[1].body = input[0].emoji;
306
+ input[1]["tags[0]"] = "hot_emoji_size:" + input[0].emojiSize;
307
+ }
308
+
309
+ return Promise.resolve(input);
310
+ }
311
+
312
+ function handleSticker(input) {
313
+ if (input[0].sticker)
314
+ input[1].sticker_id = input[0].sticker;
315
+ return Promise.resolve(input);
316
+ }
317
+
318
+ function handleAttachment(input) {
319
+ if (input[0].attachments) {
320
+ input[1].image_ids = [];
321
+ input[1].gif_ids = [];
322
+ input[1].file_ids = [];
323
+ input[1].video_ids = [];
324
+ input[1].audio_ids = [];
325
+
326
+ input[0].attachments = Array.isArray(input[0].attachments) ? input[0].attachments : [input[0].attachments];
327
+
328
+ return handleUpload(input[0].attachments)
329
+ .then(function (files) {
330
+ files.forEach(function (file) {
331
+ var key = Object.keys(file);
332
+ var type = key[0];
333
+ input[1][type + "s"].push(file[type]);
334
+ });
335
+ return input;
336
+ });
337
+ }
338
+
339
+ return Promise.resolve(input);
340
+ }
341
+
342
+ function handleUpload(attachments) {
343
+ var uploads = [];
344
+
345
+ for (var i = 0; i < attachments.length; i++) {
346
+ if (!utils.isReadableStream(attachments[i]))
347
+ throw new Error("Attachment should be a readable stream and not " + utils.getType(attachments[i]) + ".");
348
+
349
+ var form = {
350
+ upload_1024: attachments[i],
351
+ voice_clip: "true"
352
+ }
353
+
354
+ uploads.push(
355
+ http
356
+ .postData("https://upload.facebook.com/ajax/mercury/upload.php", ctx.jar, form)
357
+ .then(utils.parseAndCheckLogin(ctx, http))
358
+ .then(function (res) {
359
+ if (res.error || res.errors)
360
+ throw res;
361
+ return res.payload.metadata[0];
362
+ })
363
+ );
364
+ }
365
+
366
+ return Promise.all(uploads);
367
+ }
368
+
369
+ function handleURL(input) {
370
+ if (input[0].url) {
371
+ input[1]["shareable_attachment[share_type]"] = "100";
372
+
373
+ return getURL()
374
+ .then(function (params) {
375
+ input[1]["shareable_attachment[share_params]"] = params;
376
+ return input;
377
+ });
378
+ }
379
+
380
+ return Promise.resolve(input);
381
+ }
382
+
383
+ function getURL(url) {
384
+ var form = {
385
+ image_height: 960,
386
+ image_width: 960,
387
+ uri: url
388
+ }
389
+
390
+ return http
391
+ .post("https://www.facebook.com/message_share_attachment/fromURI/", ctx.jar, form)
392
+ .then(utils.parseAndCheckLogin(ctx, http))
393
+ .then(function (res) {
394
+ if (res.error || res.errors || !res.payload)
395
+ throw res;
396
+
397
+ return res.payload.share_data.share_params;
398
+ });
399
+ }
400
+
401
+ function handleMention(input) {
402
+ if (input[0].mentions) {
403
+ for (var i = 0; i < input[0].mentions.length; i++) {
404
+ var mention = input[0].mentions[i];
405
+
406
+ var tag = mention.tag;
407
+ if (typeof tag !== "string")
408
+ return Promise.reject(new Error("Mention tags must be strings."));
409
+
410
+ var offset = input[0].body.indexOf(tag, mention.fromIndex || 0);
411
+
412
+ var id = mention.id || 0;
413
+ input[1]["profile_xmd[" + i + "][offset]"] = offset;
414
+ input[1]["profile_xmd[" + i + "][length]"] = tag.length;
415
+ input[1]["profile_xmd[" + i + "][id]"] = id;
416
+ input[1]["profile_xmd[" + i + "][type]"] = "p";
417
+ }
418
+ }
419
+
420
+ return Promise.resolve(input);
421
+ }
422
+
423
+ function sendContent(input) {
424
+ if (utils.getType(input[2]) === "Array") {
425
+ for (var i = 0; i < input[2].length; i++)
426
+ input[1]["specific_to_list[" + i + "]"] = "fbid:" + input[2][i];
427
+
428
+ input[1]["specific_to_list[" + input[2].length + "]"] = "fbid:" + ctx.userID;
429
+ input[1]["client_thread_id"] = "root:" + input[3];
430
+ } else {
431
+ if (input[5]) {
432
+ input[1]["specific_to_list[0]"] = "fbid:" + input[2];
433
+ input[1]["specific_to_list[1]"] = "fbid:" + ctx.userID;
434
+ input[1]["other_user_fbid"] = input[2];
435
+ } else
436
+ input[1]["thread_fbid"] = input[2];
437
+ }
438
+
439
+ return http
440
+ .post("https://www.facebook.com/messaging/send/", ctx.jar, input[1])
441
+ .then(utils.parseAndCheckLogin(ctx, http))
442
+ .then(function (res) {
443
+ if (!res || res.error || res.errors)
444
+ throw res
445
+
446
+ var messageInfo = res.payload.actions.reduce(function (p, v) {
447
+ return {
448
+ threadID: v.thread_fbid || v.other_user_fbid,
449
+ messageID: v.message_id,
450
+ timestamp: v.timestamp
451
+ }
452
+ }, null);
453
+
454
+ return input[4](null, messageInfo);
455
+ });
456
+ }
457
+
458
+ return {
459
+ send,
460
+ edit,
461
+ react,
462
+ unsend
463
+ }
464
+ }
source/apis/lib/changeAvatar.js ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../utils");
2
+
3
+ module.exports = function (http, apis, ctx) {
4
+ function handleUpload(image) {
5
+ var callback;
6
+ var returnPromise = new Promise(function (resolve, reject) {
7
+ callback = (error, data) => error ? reject(error) : resolve(data);
8
+ });
9
+
10
+ if (!utils.isReadableStream(image))
11
+ callback(new Error("image is not a readable stream"));
12
+
13
+ var form = {
14
+ profile_id: ctx.userID,
15
+ photo_source: 57,
16
+ av: ctx.userID,
17
+ file: image
18
+ }
19
+
20
+ http
21
+ .postData("https://www.facebook.com/profile/picture/upload/", ctx.jar, form)
22
+ .then(utils.parseAndCheckLogin(ctx, http))
23
+ .then(function (res) {
24
+ if (res.error || res.errors)
25
+ throw res;
26
+
27
+ return callback(null, res);
28
+ })
29
+ .catch(callback);
30
+
31
+ return returnPromise;
32
+ }
33
+
34
+ return function changeAvatar(image, caption = "", timestamp = null, callback) {
35
+ var pCallback;
36
+ var returnPromise = new Promise(function (resolve, reject) {
37
+ pCallback = (error, data) => error ? reject(error) : resolve(data);
38
+ });
39
+
40
+ if (typeof caption === "number") {
41
+ timestamp = caption;
42
+ caption = "";
43
+ }
44
+ if (typeof caption === "function") {
45
+ callback = caption;
46
+ caption = "";
47
+ }
48
+ if (typeof timestamp === "function") {
49
+ callback = timestamp;
50
+ timestamp = null;
51
+ }
52
+ if (typeof callback !== "function")
53
+ callback = pCallback;
54
+
55
+ handleUpload(image)
56
+ .then(res => ({
57
+ fb_api_req_friendly_name: "ProfileCometProfilePictureSetMutation",
58
+ doc_id: "5066134240065849",
59
+ variables: JSON.stringify({
60
+ input: {
61
+ caption,
62
+ existing_photo_id: res.payload.fbid,
63
+ expiration_time: timestamp,
64
+ profile_id: ctx.userID,
65
+ profile_pic_method: "EXISTING",
66
+ profile_pic_source: "TIMELINE",
67
+ scaled_crop_rect: {
68
+ height: 1,
69
+ width: 1,
70
+ x: 0,
71
+ y: 0
72
+ },
73
+ skip_cropping: true,
74
+ actor_id: ctx.userID,
75
+ client_mutation_id: Math.round(Math.random() * 19).toString()
76
+ },
77
+ isPage: false,
78
+ isProfile: true,
79
+ scale: 3
80
+ }),
81
+ fb_api_caller_class: "RelayModern"
82
+ }))
83
+ .then(form => http.post("https://www.facebook.com/api/graphql", ctx.jar, form))
84
+ .then(utils.parseAndCheckLogin(ctx, http))
85
+ .then(function (res) {
86
+ if (res.error || res.errors)
87
+ throw res;
88
+
89
+ return callback(null, {
90
+ url: res.data.user_update_cover_photo.user.cover_photo.photo.url
91
+ });
92
+ })
93
+ .catch(function (error) {
94
+ if (error.type === "logout.")
95
+ ctx.isLogin = false;
96
+
97
+ console.log(error);
98
+ return callback(error);
99
+ });
100
+
101
+ return returnPromise;
102
+ }
103
+ }
source/apis/lib/changeBio.js ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../utils");
2
+
3
+ module.exports = function (http, apis, ctx) {
4
+ return function changeBio(bio, publish, callback) {
5
+ var pCallback;
6
+ var returnPromise = new Promise(function (resolve, reject) {
7
+ pCallback = error => error ? reject(error) : resolve();
8
+ });
9
+
10
+ if (typeof bio === "function") {
11
+ callback = bio;
12
+ bio = "";
13
+ }
14
+ if (typeof bio === "boolean") {
15
+ publish = bio;
16
+ bio = "";
17
+ }
18
+ if (typeof publish === "function") {
19
+ callback = publish;
20
+ publish = false;
21
+ }
22
+ if (typeof publish !== "boolean")
23
+ publish = false;
24
+ if (typeof callback !== "function")
25
+ callback = pCallback;
26
+
27
+ var form = {
28
+ fb_api_caller_class: "RelayModern",
29
+ fb_api_req_friendly_name: "ProfileCometSetBioMutation",
30
+ doc_id: "2725043627607610",
31
+ variables: JSON.stringify({
32
+ input: {
33
+ bio,
34
+ publish_bio_feed_story: publish,
35
+ actor_id: ctx.userID,
36
+ client_mutation_id: Math.round(Math.random() * 1024).toString()
37
+ },
38
+ hasProfileTileViewID: false,
39
+ profileTileViewID: null,
40
+ scale: 1
41
+ })
42
+ }
43
+
44
+ http
45
+ .post("https://www.facebook.com/api/graphql/", ctx.jar, form)
46
+ .then(utils.parseAndCheckLogin(ctx, http))
47
+ .then(function (res) {
48
+ if (res.error || res.errors)
49
+ throw res;
50
+ return callback();
51
+ })
52
+ .catch(function (error) {
53
+ if (error.type === "logout.")
54
+ ctx.isLogin = false;
55
+
56
+ return callback(error);
57
+ });
58
+
59
+ return returnPromise;
60
+ }
61
+ }
source/apis/lib/changeCover.js ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../utils");
2
+
3
+ module.exports = function (http, apis, ctx) {
4
+ function handleUpload(image) {
5
+ var callback;
6
+ var returnPromise = new Promise(function (resolve, reject) {
7
+ callback = (error, data) => error ? reject(error) : resolve(data);
8
+ });
9
+
10
+ if (!utils.isReadableStream(image))
11
+ callback(new Error("image is not a readable stream"));
12
+
13
+ var form = {
14
+ profile_id: ctx.userID,
15
+ photo_source: 57,
16
+ av: ctx.userID,
17
+ file: image
18
+ }
19
+
20
+ http
21
+ .postData("https://www.facebook.com/profile/picture/upload/", ctx.jar, form)
22
+ .then(utils.parseAndCheckLogin(ctx, http))
23
+ .then(function (res) {
24
+ if (res.error || res.errors)
25
+ throw res;
26
+
27
+ return callback(null, res);
28
+ })
29
+ .catch(callback);
30
+
31
+ return returnPromise;
32
+ }
33
+
34
+ return function changeCover(image, callback) {
35
+ var pCallback;
36
+ var returnPromise = new Promise(function (resolve, reject) {
37
+ pCallback = (error, data) => error ? reject(error) : resolve(data);
38
+ });
39
+
40
+ if (typeof callback !== "function")
41
+ callback = pCallback;
42
+
43
+ handleUpload(image)
44
+ .then(res => ({
45
+ fb_api_caller_class: "RelayModern",
46
+ fb_api_req_friendly_name: "ProfileCometCoverPhotoUpdateMutation",
47
+ variables: JSON.stringify({
48
+ input: {
49
+ attribution_id_v2: `ProfileCometCollectionRoot.react,comet.profile.collection.photos_by,unexpected,${Date.now()},770083,,;ProfileCometCollectionRoot.react,comet.profile.collection.photos_albums,unexpected,${Date.now()},470774,,;ProfileCometCollectionRoot.react,comet.profile.collection.photos,unexpected,${Date.now()},94740,,;ProfileCometCollectionRoot.react,comet.profile.collection.saved_reels_on_profile,unexpected,${Date.now()},89669,,;ProfileCometCollectionRoot.react,comet.profile.collection.reels_tab,unexpected,${Date.now()},152201,,`,
50
+ cover_photo_id: res.payload.fbid,
51
+ focus: {
52
+ x: 0.5,
53
+ y: 1
54
+ },
55
+ target_user_id: ctx.userID,
56
+ actor_id: ctx.userID,
57
+ client_mutation_id: Math.round(Math.random() * 19).toString()
58
+ },
59
+ scale: 1,
60
+ contextualProfileContext: null
61
+ }),
62
+ doc_id: "8247793861913071"
63
+ }))
64
+ .then(form => http.post("https://www.facebook.com/api/graphql", ctx.jar, form))
65
+ .then(utils.parseAndCheckLogin(ctx, http))
66
+ .then(function (res) {
67
+ if (res.error || res.errors)
68
+ throw res;
69
+
70
+ return callback(null, {
71
+ url: res.data.profile_picture_set
72
+ });
73
+ })
74
+ .catch(function (error) {
75
+ if (error.type === "logout.")
76
+ ctx.isLogin = false;
77
+
78
+ console.log(error);
79
+ return callback(error);
80
+ });
81
+
82
+ return returnPromise;
83
+ }
84
+ }
source/apis/lib/getAccessToken.js ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../utils");
2
+
3
+ module.exports = function (http, apis, ctx) {
4
+ return function getAccessToken(callback) {
5
+ var pCallback;
6
+ var returnPromise = new Promise(function (resolve, reject) {
7
+ pCallback = (error, token) => error ? reject(error) : resolve(token);
8
+ });
9
+
10
+ if (typeof callback !== "function")
11
+ callback = pCallback;
12
+
13
+ if (ctx.token) {
14
+ var qs = {
15
+ access_token: ctx.token
16
+ }
17
+ utils
18
+ .get("https://graph.facebook.com/me/permissions", ctx.jar, qs)
19
+ .then(function () {
20
+ return callback(null, ctx.token);
21
+ })
22
+ .catch(function () {
23
+ return utils
24
+ .getDTSGInitData(ctx)
25
+ .then(function () {
26
+ return utils
27
+ .post("https://www.facebook.com/v1.0/dialog/oauth/confirm", ctx.jar, {
28
+ fb_dtsg: ctx.fb_dtsg,
29
+ app_id: "124024574287414",
30
+ redirect_uri: "fbconnect://success",
31
+ display: "popup",
32
+ return_format: "access_token",
33
+ });
34
+ })
35
+ .then(function (res) {
36
+ var body = res.body;
37
+ var token = body.match(/access_token=(.*?)&/);
38
+ if (token && token[1])
39
+ ctx.token = token[1];
40
+ else
41
+ throw new Error("Token is undefined.");
42
+
43
+ return callback(null, ctx.token);
44
+ })
45
+ .catch(function (error) {
46
+ console.log(error);
47
+ return callback(error);
48
+ });
49
+ });
50
+ } else {
51
+ utils
52
+ .getDTSGInitData(ctx)
53
+ .then(function () {
54
+ return utils
55
+ .post("https://www.facebook.com/v1.0/dialog/oauth/confirm", ctx.jar, {
56
+ fb_dtsg: ctx.fb_dtsg,
57
+ app_id: "124024574287414",
58
+ redirect_uri: "fbconnect://success",
59
+ display: "popup",
60
+ return_format: "access_token",
61
+ });
62
+ })
63
+ .then(function (res) {
64
+ var body = res.body;
65
+ var token = body.match(/access_token=(.*?)&/);
66
+ if (token && token[1])
67
+ ctx.token = token[1];
68
+ else
69
+ throw new Error("Token is undefined.");
70
+
71
+ return callback(null, ctx.token);
72
+ })
73
+ .catch(function (error) {
74
+ console.log(error);
75
+ return callback(error);
76
+ });
77
+ }
78
+
79
+ return returnPromise;
80
+ }
81
+ }
source/apis/lib/getCurrentUserID.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ module.exports = function (http, apis, ctx) {
2
+ return function getCurrentUserID() {
3
+ return ctx.userID;
4
+ }
5
+ }
source/apis/lib/getThreadInfo.js ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../utils");
2
+
3
+ function formatEventReminders(reminder) {
4
+ return {
5
+ reminderID: reminder.id,
6
+ eventCreatorID: reminder.lightweight_event_creator.id,
7
+ time: reminder.time,
8
+ eventType: reminder.lightweight_event_type.toLowerCase(),
9
+ locationName: reminder.location_name,
10
+ locationCoordinates: reminder.location_coordinates,
11
+ locationPage: reminder.location_page,
12
+ eventStatus: reminder.lightweight_event_status.toLowerCase(),
13
+ note: reminder.note,
14
+ repeatMode: reminder.repeat_mode.toLowerCase(),
15
+ eventTitle: reminder.event_title,
16
+ triggerMessage: reminder.trigger_message,
17
+ secondsToNotifyBefore: reminder.seconds_to_notify_before,
18
+ allowsRsvp: reminder.allows_rsvp,
19
+ relatedEvent: reminder.related_event,
20
+ members: reminder.event_reminder_members.edges.map(member => ({
21
+ memberID: member.node.id,
22
+ state: member.guest_list_state.toLowerCase()
23
+ }))
24
+ }
25
+ }
26
+
27
+ function formatThreadGraphQLResponse(data) {
28
+ var messageThread = data.message_thread;
29
+ if (!messageThread) return {}
30
+
31
+ var threadID = messageThread.thread_key.thread_fbid || messageThread.thread_key.other_user_id;
32
+
33
+ var lastM = messageThread.last_message;
34
+ var snippetID = lastM && lastM.nodes && lastM.nodes[0] && lastM.nodes[0].message_sender && lastM.nodes[0].message_sender.messaging_actor ? lastM.nodes[0].message_sender.messaging_actor.id : null;
35
+ var snippetText = lastM && lastM.nodes && lastM.nodes[0] ? lastM.nodes[0].snippet : null;
36
+ var lastR = messageThread.last_read_receipt;
37
+ var lastReadTimestamp = lastR && lastR.nodes && lastR.nodes[0] && lastR.nodes[0].timestamp_precise ? lastR.nodes[0].timestamp_precise : null;
38
+
39
+ return {
40
+ threadID: threadID,
41
+ threadName: messageThread.name,
42
+ participantIDs: messageThread.all_participants.edges.map(d => d.node.messaging_actor.id),
43
+ userInfo: messageThread.all_participants.edges.map(d => ({
44
+ id: d.node.messaging_actor.id,
45
+ name: d.node.messaging_actor.name,
46
+ firstName: d.node.messaging_actor.short_name,
47
+ vanity: d.node.messaging_actor.username,
48
+ thumbSrc: d.node.messaging_actor.big_image_src.uri,
49
+ profileUrl: d.node.messaging_actor.big_image_src.uri,
50
+ gender: d.node.messaging_actor.gender,
51
+ type: d.node.messaging_actor.__typename,
52
+ isFriend: d.node.messaging_actor.is_viewer_friend,
53
+ isBirthday: !!d.node.messaging_actor.is_birthday
54
+ })),
55
+ unreadCount: messageThread.unread_count,
56
+ messageCount: messageThread.messages_count,
57
+ timestamp: messageThread.updated_time_precise,
58
+ muteUntil: messageThread.mute_until,
59
+ isGroup: messageThread.thread_type == "GROUP",
60
+ isSubscribed: messageThread.is_viewer_subscribed,
61
+ isArchived: messageThread.has_viewer_archived,
62
+ folder: messageThread.folder,
63
+ cannotReplyReason: messageThread.cannot_reply_reason,
64
+ eventReminders: messageThread.event_reminders ? messageThread.event_reminders.nodes.map(formatEventReminders) : null,
65
+ emoji: messageThread.customization_info ? messageThread.customization_info.emoji : null,
66
+ color: messageThread.customization_info && messageThread.customization_info.outgoing_bubble_color ? messageThread.customization_info.outgoing_bubble_color.slice(2) : null,
67
+ nicknames:
68
+ messageThread.customization_info &&
69
+ messageThread.customization_info.participant_customizations
70
+ ? messageThread.customization_info.participant_customizations.reduce(function (res, val) {
71
+ if (val.nickname) res[val.participant_id] = val.nickname;
72
+ return res;
73
+ }, {})
74
+ : {},
75
+ adminIDs: messageThread.thread_admins,
76
+ approvalMode: Boolean(messageThread.approval_mode),
77
+ approvalQueue: messageThread.group_approval_queue.nodes.map(a => ({
78
+ inviterID: a.inviter.id,
79
+ requesterID: a.requester.id,
80
+ timestamp: a.request_timestamp,
81
+ request_source: a.request_source
82
+ })),
83
+ reactionsMuteMode: messageThread.reactions_mute_mode.toLowerCase(),
84
+ mentionsMuteMode: messageThread.mentions_mute_mode.toLowerCase(),
85
+ isPinProtected: messageThread.is_pin_protected,
86
+ relatedPageThread: messageThread.related_page_thread,
87
+ name: messageThread.name,
88
+ snippet: snippetText,
89
+ snippetSender: snippetID,
90
+ snippetAttachments: [],
91
+ serverTimestamp: messageThread.updated_time_precise,
92
+ imageSrc: messageThread.image ? messageThread.image.uri : null,
93
+ isCanonicalUser: messageThread.is_canonical_neo_user,
94
+ isCanonical: messageThread.thread_type != "GROUP",
95
+ recipientsLoadable: true,
96
+ hasEmailParticipant: false,
97
+ readOnly: false,
98
+ canReply: messageThread.cannot_reply_reason == null,
99
+ lastMessageTimestamp: messageThread.last_message ? messageThread.last_message.timestamp_precise : null,
100
+ lastMessageType: "message",
101
+ lastReadTimestamp: lastReadTimestamp,
102
+ threadType: messageThread.thread_type == "GROUP" ? 2 : 1,
103
+ inviteLink: {
104
+ enable: messageThread.joinable_mode ? messageThread.joinable_mode.mode == 1 : false,
105
+ link: messageThread.joinable_mode ? messageThread.joinable_mode.link : null
106
+ }
107
+ }
108
+ }
109
+
110
+ module.exports = function (http, apis, ctx) {
111
+ return function getThreadInfo(threadIDs, callback) {
112
+ var pCallback;
113
+ var returnPromise = new Promise(function (resolve, reject) {
114
+ pCallback = (error, data) => error ? reject(error) : resolve(data);
115
+ });
116
+
117
+ if (typeof callback !== "function") callback = pCallback;
118
+ if (!Array.isArray(threadIDs)) threadIDs = [threadIDs];
119
+
120
+ var form = {}
121
+ threadIDs.map((id, i) => form["o" + i] = {
122
+ doc_id: "3449967031715030",
123
+ query_params: {
124
+ id,
125
+ message_limit: 0,
126
+ load_messages: false,
127
+ load_read_receipts: false,
128
+ before: null
129
+ }
130
+ });
131
+
132
+ form = {
133
+ queries: JSON.stringify(form),
134
+ batch_name: "MessengerGraphQLThreadFetcher"
135
+ }
136
+
137
+ http
138
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
139
+ .then(utils.parseAndCheckLogin(ctx, http))
140
+ .then(function (resData) {
141
+ if (resData.error) throw resData;
142
+ var threadInfos = {}
143
+ for (let i = resData.length - 2; i >= 0; i--) {
144
+ var threadInfo = formatThreadGraphQLResponse(resData[i][Object.keys(resData[i])[0]].data);
145
+ threadInfos[threadInfo.threadID] = threadInfo;
146
+ }
147
+ if (Object.values(threadInfos).length === 1) {
148
+ callback(null, Object.values(threadInfos)[0]);
149
+ }
150
+ else {
151
+ callback(null, threadInfos);
152
+ }
153
+ })
154
+ .catch(function (error) {
155
+ if (error.type === "logout.")
156
+ ctx.isLogin = false;
157
+ console.log(error);
158
+ return callback(error);
159
+ });
160
+
161
+ return returnPromise;
162
+ }
163
+ }
source/apis/lib/getUserID.js ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../utils");
2
+
3
+ function format(res) {
4
+ var res = res.relay_rendering_strategy.view_model.profile;
5
+ return {
6
+ userID: res.id,
7
+ name: res.name,
8
+ isVerified: res.is_verified,
9
+ profileUrl: res.url,
10
+ photoUrl: res.profile_picture.uri
11
+ }
12
+ }
13
+
14
+ module.exports = function (http, apis, ctx) {
15
+ return function getUserID(name, callback) {
16
+ var pCallback;
17
+ var returnPromise = new Promise(function (resolve, reject) {
18
+ pCallback = (error, data) => error ? reject(error) : resolve(data);
19
+ });
20
+
21
+ if (typeof name === "function") {
22
+ callback = name;
23
+ name = null;
24
+ }
25
+ if (typeof callback !== "function")
26
+ callback = pCallback;
27
+
28
+ if (typeof name !== "string")
29
+ callback(new Error("name must be an string"));
30
+ else {
31
+ var form = {
32
+ fb_api_caller_class: "RelayModern",
33
+ fb_api_req_friendly_name: "SearchCometResultsInitialResultsQuery",
34
+ variables: JSON.stringify({
35
+ count: 5,
36
+ allow_streaming: false,
37
+ args: {
38
+ callsite: "COMET_GLOBAL_SEARCH",
39
+ config: {
40
+ exact_match: false,
41
+ high_confidence_config: null,
42
+ intercept_config: null,
43
+ sts_disambiguation: null,
44
+ watch_config: null
45
+ },
46
+ context: {
47
+ bsid: utils.getGUID(),
48
+ tsid: null
49
+ },
50
+ experience: {
51
+ encoded_server_defined_params: null,
52
+ fbid: null,
53
+ type: "PEOPLE_TAB"
54
+ },
55
+ filters: [],
56
+ text: name.toLowerCase()
57
+ },
58
+ cursor: null,
59
+ feedbackSource: 23,
60
+ fetch_filters: true,
61
+ renderLocation: "search_results_page",
62
+ scale: 1,
63
+ stream_initial_count: 0,
64
+ useDefaultActor: false,
65
+ __relay_internal__pv__IsWorkUserrelayprovider: false,
66
+ __relay_internal__pv__IsMergQAPollsrelayprovider: false,
67
+ __relay_internal__pv__StoriesArmadilloReplyEnabledrelayprovider: false,
68
+ __relay_internal__pv__StoriesRingrelayprovider: false
69
+ }),
70
+ doc_id: "9946783172059974"
71
+ }
72
+
73
+ http
74
+ .post("https://www.facebook.com/api/graphql/", ctx.jar, form)
75
+ .then(utils.parseAndCheckLogin(ctx, http))
76
+ .then(function (res) {
77
+ if (res.error || res.errors)
78
+ throw res;
79
+ return callback(null, res.data.serpResponse.results.edges.map(format));
80
+ })
81
+ .catch(function (error) {
82
+ if (error.type === "logout.")
83
+ ctx.isLogin = false;
84
+
85
+ return callback(error);
86
+ });
87
+ }
88
+
89
+ return returnPromise;
90
+ }
91
+ }
source/apis/lib/getUserInfo.js ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../utils");
2
+
3
+ module.exports = function (http, apis, ctx) {
4
+ function parseData(data) {
5
+ var retObj = {}
6
+
7
+ for (var prop in data) {
8
+ if (data.hasOwnProperty(prop)) {
9
+ var innerObj = data[prop];
10
+ retObj[prop] = {
11
+ name: innerObj.name,
12
+ firstName: innerObj.firstName,
13
+ vanity: innerObj.vanity,
14
+ thumbSrc: innerObj.thumbSrc,
15
+ profileUrl: innerObj.uri,
16
+ gender: innerObj.gender,
17
+ type: innerObj.type,
18
+ isFriend: innerObj.is_friend,
19
+ isBirthday: !!innerObj.is_birthday,
20
+ searchTokens: innerObj.searchTokens,
21
+ alternateName: innerObj.alternateName,
22
+ }
23
+ }
24
+ }
25
+
26
+ return retObj;
27
+ }
28
+
29
+ function parseDataHighLevel(userIDs, data) {
30
+ var retObj = {}
31
+
32
+ for (var index in userIDs) {
33
+ var res = data[index];
34
+ if (Object.keys(res).length > 0) {
35
+ retObj[res.id] = {
36
+ id: res.id,
37
+ name: res.name,
38
+ shortName: res.short_name || null,
39
+ verified: res.verified != false ? true : false,
40
+ email: res.email || null,
41
+ website: res.website || null,
42
+ follower: !!res.subscribers == true ? res.subscribers.summary.total_count : null,
43
+ lover: res.significant_other || null,
44
+ cover: !!res.cover == true ? res.cover.source : null,
45
+ first_name: res.first_name || null,
46
+ middle_name: res.middle_name || null,
47
+ last_name: res.last_name || null,
48
+ about: res.about || null,
49
+ birthday: res.birthday || null,
50
+ languages: res.languages || [],
51
+ gender: res.gender || null,
52
+ hometown: !!res.hometown == true ? res.hometown.name : null,
53
+ profileUrl: res.link || null,
54
+ location: !!res.location == true ? res.location.name : null,
55
+ username: res.username || null,
56
+ avatar: !!res.picture == true ? res.picture.data.url : null,
57
+ relationship_status: !!res.relationship_status == true ? res.relationship_status : null,
58
+ subscribers: !!res.subscribers == true ? res.subscribers.data : null,
59
+ favorite_athletes: !!res.favorite_athletes == false ? [] : res.favorite_athletes.map(v => ({
60
+ name: v.name
61
+ })),
62
+ education: !!res.education == true ? res.education.map(v => ({
63
+ type: v.type,
64
+ school: v.school.name
65
+ })) : [],
66
+ work: !!res.work == true ? res.work : []
67
+ }
68
+ } else
69
+ retObj[userIDs[index]] = {}
70
+ }
71
+
72
+ return retObj;
73
+ }
74
+
75
+ function requestData(userID) {
76
+ var qs = {
77
+ fields: "id,name,verified,cover,first_name,email,about,birthday,gender,website,hometown,link,location,quotes,relationship_status,significant_other,username,subscribers.limite(0),short_name,last_name,middle_name,education,picture,work,languages,favorite_athletes",
78
+ access_token: ctx.token
79
+ }
80
+ return utils
81
+ .get("https://graph.facebook.com/v1.0/" + userID, ctx.jar, qs)
82
+ .then(function (res) {
83
+ return JSON.parse(res.body);
84
+ })
85
+ .catch(function (error) {
86
+ console.log(error);
87
+ return {}
88
+ });
89
+ }
90
+
91
+ return function getUserInfo(userIDs, deprecated = false, callback) {
92
+ var pCallback;
93
+ var returnPromise = new Promise(function (resolve, reject) {
94
+ pCallback = (error, data) => error ? reject(error) : resolve(data);
95
+ });
96
+
97
+ if (typeof deprecated === "function") {
98
+ callback = deprecated;
99
+ deprecated = false;
100
+ }
101
+ if (typeof callback !== "function")
102
+ callback = pCallback;
103
+ if (!Array.isArray(userIDs))
104
+ userIDs = [userIDs];
105
+ if (!deprecated) {
106
+ var form = [];
107
+ apis
108
+ .getAccessToken()
109
+ .then(function () {
110
+ userIDs.map(userID => form.push(requestData(userID)));
111
+ return Promise.all(form);
112
+ })
113
+ .then(function (res) {
114
+ return callback(null, parseDataHighLevel(userIDs, res));
115
+ })
116
+ .catch(function (error) {
117
+ if (error.type === "logout.")
118
+ ctx.isLogin = false;
119
+ console.log(error);
120
+ return callback(error);
121
+ });
122
+ } else {
123
+ var form = {}
124
+ userIDs.map((value, index) => form["ids[" + index + "]"] = value);
125
+ http
126
+ .post("https://www.facebook.com/chat/user_info/", ctx.jar, form)
127
+ .then(utils.parseAndCheckLogin(ctx, http))
128
+ .then(function (res) {
129
+ if (res.error || res.errors)
130
+ throw new Error(res);
131
+
132
+ return callback(null, parseData(res.payload.profiles));
133
+ })
134
+ .catch(function (error) {
135
+ if (error.type === "logout.")
136
+ ctx.isLogin = false;
137
+ console.log(error);
138
+ return callback(error);
139
+ });
140
+ }
141
+
142
+ return returnPromise;
143
+ }
144
+ }
source/apis/lib/logout.js ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../utils");
2
+
3
+ module.exports = function (http, apis, ctx) {
4
+ return function logout(callback) {
5
+ var pCallback;
6
+ var returnPromise = new Promise(function (resolve, reject) {
7
+ pCallback = error => error ? reject(error) : resolve();
8
+ });
9
+
10
+ if (typeof callback !== "function")
11
+ callback = pCallback
12
+
13
+ var form = {
14
+ pmid: "0"
15
+ }
16
+
17
+ http
18
+ .post("https://www.facebook.com/bluebar/modern_settings_menu/?help_type=364455653583099&show_contextual_help=1", ctx.jar, form)
19
+ .then(utils.parseAndCheckLogin(ctx, http))
20
+ .then(function (res) {
21
+ var elem = res.jsmods.instances[0][2][0].filter(item => item.value === "logout")[0];
22
+ var body = res.jsmods.markup.filter(item => item[0] === elem.markup.__m)[0][1].__html;
23
+ var match = [...body.matchAll(/<input[^>]*name="([^"]+)"[^>]*value="([^"]+)"[^>]*>/g)].map(item => item[2]);
24
+ var form = {
25
+ jazoest: match[0],
26
+ fb_dtsg: match[1],
27
+ ref: match[2],
28
+ h: match[3]
29
+ }
30
+
31
+ return http.post("https://www.facebook.com/logout.php", ctx.jar, form);
32
+ })
33
+ .then(function (res) {
34
+ if (!res.headers)
35
+ throw {
36
+ error: "An error occurred when logging out."
37
+ }
38
+
39
+ return http.get(res.headers.location, ctx.jar);
40
+ })
41
+ .then(function () {
42
+ ctx.isLogin = false;
43
+ return callback();
44
+ })
45
+ .catch(function (error) {
46
+ if (error.type === "logout.")
47
+ ctx.isLogin = false;
48
+ return callback(error);
49
+ });
50
+
51
+ return returnPromise;
52
+ }
53
+ }
source/apis/lib/markAsDelivered.js ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../utils");
2
+
3
+ module.exports = function (http, apis, ctx) {
4
+ return function markAsDelivered(threadID, messageID, callback) {
5
+ var pCallback;
6
+ var returnPromise = new Promise(function (resolve, reject) {
7
+ pCallback = error => error ? reject(error) : resolve();
8
+ });
9
+
10
+ if (typeof callback !== "function") callback = pCallback;
11
+ if (threadID && messageID) {
12
+ var form = {}
13
+ form["message_ids[0]"] = messageID;
14
+ form["thread_ids[" + threadID + "][0]"] = messageID;
15
+
16
+ http
17
+ .post("https://www.facebook.com/ajax/mercury/delivery_receipts.php", ctx.jar, form)
18
+ .then(utils.parseAndCheckLogin(ctx, http))
19
+ .then(function (resData) {
20
+ if (resData.error)
21
+ throw resData;
22
+
23
+ return callback();
24
+ })
25
+ .catch(function (error) {
26
+ if (error.type === "logout.")
27
+ ctx.isLogin = false;
28
+ return callback(error);
29
+ });
30
+ } else
31
+ callback("Error: messageID or threadID is not defined");
32
+
33
+ return returnPromise;
34
+ }
35
+ }
source/apis/lib/markAsRead.js ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../utils");
2
+
3
+ module.exports = function (http, apis, ctx) {
4
+ return function markAsRead(threadID, read = true, callback) {
5
+ var pCallback;
6
+ var returnPromise = new Promise(function (resolve, reject) {
7
+ pCallback = error => error ? reject(error) : resolve();
8
+ });
9
+
10
+ if (typeof read === "function") {
11
+ callback = read;
12
+ read = true;
13
+ }
14
+
15
+ if (typeof callback !== "function")
16
+ callback = pCallback;
17
+
18
+ var form = {}
19
+ form["source"] = "PagesManagerMessagesInterface";
20
+ form["request_user_id"] = ctx.userID;
21
+ form["ids[" + threadID + "]"] = read;
22
+ form["watermarkTimestamp"] = new Date().getTime();
23
+ form["shouldSendReadReceipt"] = true;
24
+ form["commerce_last_message_type"] = "";
25
+
26
+ http
27
+ .post("https://www.facebook.com/ajax/mercury/change_read_status.php", ctx.jar, form)
28
+ .then(utils.parseAndCheckLogin(ctx, http))
29
+ .then(function () {
30
+ return callback();
31
+ })
32
+ .catch(function (error) {
33
+ if (error.type === "logout.")
34
+ ctx.isLogin = false;
35
+
36
+ return callback(error);
37
+ })
38
+
39
+ return returnPromise;
40
+ }
41
+ }
source/apis/lib/muteThread.js ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../utils");
2
+
3
+ module.exports = function (http, apis, ctx) {
4
+ return function muteThread(threadID, muteSeconds = 0, callback) {
5
+ var pCallback;
6
+ var returnPromise = new Promise(function (resolve, reject) {
7
+ pCallback = error => error ? reject(error) : resolve();
8
+ });
9
+
10
+ if (typeof threadID === "function") {
11
+ callback = threadID;
12
+ threadID = null;
13
+ }
14
+ if (typeof muteSeconds === "function") {
15
+ callback = muteSeconds;
16
+ muteSeconds = 0;
17
+ }
18
+ if (typeof callback !== "function")
19
+ callback = pCallback;
20
+ if (utils.getType(threadID) !== "String" || utils.getType(threadID) !== "Number")
21
+ callback(new Error("threadID must be an string number"));
22
+ else if (utils.getType(muteSeconds) !== "String" || utils.getType(muteSeconds) !== "Number")
23
+ callback(new Error("muteSeconds must be an string number"));
24
+ else {
25
+ var form = {
26
+ thread_fbid: threadID,
27
+ mute_settings: muteSeconds
28
+ }
29
+
30
+ http
31
+ .post("https://www.facebook.com/ajax/mercury/change_mute_thread.php", ctx.jar, form)
32
+ .then(utils.parseAndCheckLogin(ctx, http))
33
+ .then(function (res) {
34
+ if (res.error || res.errors)
35
+ throw res;
36
+
37
+ return callback();
38
+ })
39
+ .catch(function (error) {
40
+ if (error.type === "logout.")
41
+ ctx.isLogin = false;
42
+
43
+ return callback(error);
44
+ });
45
+ }
46
+
47
+ return returnPromise;
48
+ }
49
+ }
source/apis/lib/post.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../utils");
2
+
3
+ module.exports = function (http, apis, ctx) {
4
+ return function HttpClientPostData(url, form, parse = false, callback) {
5
+ var pCallback;
6
+ var returnPromise = new Promise(function (resolve, reject) {
7
+ pCallback = error => error ? reject(error) : resolve();
8
+ });
9
+
10
+ return returnPromise;
11
+ }
12
+ }
source/apis/lib/resolvePhotoUrl.js ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../utils");
2
+
3
+ module.exports = function (http, apis, ctx) {
4
+ function getPhotoUrls(photoIDs) {
5
+ var callback;
6
+ var uploads = [];
7
+ var returnPromise = new Promise(function (resolve, reject) {
8
+ callback = (error, photoUrl) => photoUrl ? resolve(photoUrl) : reject(error);
9
+ });
10
+
11
+ photoIDs.map(function (id) {
12
+ var httpPromise = http
13
+ .get("https://www.facebook.com/mercury/attachments/photo", ctx.jar, {
14
+ photo_id: id
15
+ })
16
+ .then(utils.parseAndCheckLogin(ctx, http))
17
+ .then(function (res) {
18
+ if (res.error) throw res;
19
+ return res.jsmods.require[0][3][0]
20
+ })
21
+ .catch(function (error) {
22
+ return callback(error);
23
+ });
24
+ uploads.push(httpPromise);
25
+ });
26
+
27
+ Promise
28
+ .all(uploads)
29
+ .then(function (res) {
30
+ return callback(null, res.reduce(function (form, v, i) {
31
+ form[photoIDs[i]] = v;
32
+ return form;
33
+ }, {}));
34
+ })
35
+
36
+ return returnPromise;
37
+ }
38
+
39
+ return function resolvePhotoUrl(photoIDs, callback) {
40
+ var pCallback;
41
+ var returnPromise = new Promise(function (resolve, reject) {
42
+ pCallback = (error, photoUrl) => photoUrl ? resolve(photoUrl) : reject(error);
43
+ });
44
+
45
+ if (!Array.isArray(photoIDs)) photoIDs = [photoIDs];
46
+ if (typeof callback !== 'function') callback = pCallback;
47
+
48
+ getPhotoUrls(photoIDs)
49
+ .then(function (photoUrl) {
50
+ if (Object.keys(photoUrl).length == 1) {
51
+ callback(null, photoUrl[photoIDs[0]]);
52
+ } else callback(null, photoUrl);
53
+ })
54
+ .catch(function (error) {
55
+ if (error.type === "logout.")
56
+ ctx.isLogin = false;
57
+ console.log(error);
58
+ return callback(error);
59
+ });
60
+
61
+ return returnPromise;
62
+ }
63
+ }
source/apis/lib/screenshot.js ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../utils");
2
+ var Pup = require("puppeteer");
3
+
4
+ module.exports = function (http, apis, ctx) {
5
+ return async function ScreenShotChromium(url, path, Options, callback) {
6
+ var pCallback;
7
+ var returnPromise = new Promise(function (resolve, reject) {
8
+ pCallback = error => error ? reject(error) : resolve();
9
+ });
10
+
11
+ if (typeof url === "function") {
12
+ callback = url;
13
+ url = null;
14
+ }
15
+ if (typeof path === "function") {
16
+ callback = path;
17
+ path = null;
18
+ }
19
+ if (typeof Options === "function") {
20
+ callback = Options;
21
+ Options = {}
22
+ }
23
+ if (typeof Options !== "object")
24
+ Options = {}
25
+ if (typeof callback !== "function")
26
+ callback = pCallback;
27
+
28
+ Options.userAgent = Options.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36";
29
+ Options.width = Options.width || 1920;
30
+ Options.height = Options.height || 1080;
31
+ Options.fullPage = Options.fullPage || false;
32
+
33
+ if (!utils.isURL(url))
34
+ callback(new Error("url is invalid"));
35
+ else if (!utils.isPath(path))
36
+ callback(new Error("path is invalid"));
37
+ else {
38
+ var Browser;
39
+ var Proxy = utils.parseProxy(ctx.proxy);
40
+
41
+ if (Proxy && Proxy.length > 0) {
42
+ Browser = await Pup.launch({ headless: true, args: ["--proxy-server=" + ctx.proxy] });
43
+ } else
44
+ Browser = await Pup.launch({ headless: true });
45
+
46
+ var Page = await Browser.newPage();
47
+
48
+ if (Proxy && Proxy[0] && Proxy[1])
49
+ await Page.authenticate({
50
+ username: Proxy[0],
51
+ password: Proxy[1]
52
+ });
53
+
54
+ var Cookies = apis.getAppState().map(item => ({
55
+ name: item.key,
56
+ value: item.value,
57
+ domain: ".facebook.com",
58
+ path: "/",
59
+ secure: true,
60
+ httpOnly: true
61
+ }));
62
+
63
+ await Page.setViewport({
64
+ width: Options.width,
65
+ height: Options.height
66
+ });
67
+ await Page.setUserAgent(Options.userAgent);
68
+ await Page.setCookie(...Cookies);
69
+ await Page.goto(url, { waitUntil: "networkidle0" });
70
+ await Page.screenshot({ path, fullPage: Options.fullPage });
71
+ await Browser.close();
72
+ callback();
73
+ }
74
+
75
+ return returnPromise;
76
+ }
77
+ }
source/apis/utils.js ADDED
@@ -0,0 +1,372 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var request = require("request").defaults({ jar: true });
2
+ var parseProxy = proxy => (proxy.match(/^(?:http:\/\/)?(?:(\S+):(\S+)@)?([A-Za-z0-9.-]+):(\d{2,5})$/) || []).slice(1);
3
+ var utilsLib = require("../lib/utils");
4
+ var log = require("../lib/log");
5
+ var stream = require("stream");
6
+
7
+ function setProxy(proxy) {
8
+ var proxyArray = parseProxy(proxy);
9
+
10
+ if (proxyArray)
11
+ request = require("request").defaults({ jar: true, proxy });
12
+ else
13
+ request = require("request").defaults({ jar: true });
14
+ }
15
+
16
+ function setHeaders(url, ctx, customHeaders) {
17
+ var headers = {
18
+ "Content-Type": "application/x-www-form-urlencoded",
19
+ Referer: "https://www.facebook.com/",
20
+ Host: new URL(url).hostname,
21
+ Origin: "https://www.facebook.com",
22
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Kbody, like Gecko) Chrome/127.0.0.0 Safari/537.36",
23
+ Connection: "keep-alive",
24
+ "sec-fetch-site": "same-origin"
25
+ }
26
+ if (customHeaders)
27
+ Object.assign(headers, customHeaders);
28
+ if (customHeaders && customHeaders.noRef) {
29
+ delete headers.Referer;
30
+ delete headers.noRef;
31
+ }
32
+ if (ctx && ctx.region)
33
+ headers["X-MSGR-Region"] = ctx.region;
34
+
35
+ return headers;
36
+ }
37
+
38
+ function get(url, jar, qs, ctx, customHeaders) {
39
+ if (utilsLib.getType(qs) === "Object") {
40
+ for (var prop in qs) {
41
+ if (qs.hasOwnProperty(prop) && utilsLib.getType(qs[prop]) === "Object") {
42
+ qs[prop] = JSON.stringify(qs[prop]);
43
+ }
44
+ }
45
+ }
46
+ var options = {
47
+ headers: setHeaders(url, ctx, customHeaders),
48
+ timeout: 60000,
49
+ qs,
50
+ url,
51
+ method: "GET",
52
+ jar,
53
+ gzip: true
54
+ }
55
+
56
+ var callback;
57
+ var returnPromise = new Promise(function (resolve, reject) {
58
+ callback = (error, response) => error ? reject(error) : resolve(response);
59
+ });
60
+ request(options, callback);
61
+
62
+ return returnPromise;
63
+ }
64
+
65
+ function post(url, jar, form, ctx, customHeaders) {
66
+ var options = {
67
+ headers: setHeaders(url, ctx, customHeaders),
68
+ timeout: 60000,
69
+ url,
70
+ method: "POST",
71
+ form,
72
+ jar,
73
+ gzip: true
74
+ }
75
+
76
+ var callback;
77
+ var returnPromise = new Promise(function (resolve, reject) {
78
+ callback = (error, response) => error ? reject(error) : resolve(response);
79
+ });
80
+ request(options, callback);
81
+
82
+ return returnPromise;
83
+ }
84
+
85
+ function postData(url, jar, formData, qs, ctx, customHeaders) {
86
+ if (utilsLib.getType(qs) === "Object") {
87
+ for (var prop in qs) {
88
+ if (qs.hasOwnProperty(prop) && utilsLib.getType(qs[prop]) === "Object") {
89
+ qs[prop] = JSON.stringify(qs[prop]);
90
+ }
91
+ }
92
+ }
93
+ var headers = setHeaders(url, ctx, customHeaders);
94
+ headers["Content-Type"] = "multipart/form-data";
95
+ var options = {
96
+ headers,
97
+ timeout: 60000,
98
+ url,
99
+ method: "POST",
100
+ formData,
101
+ qs,
102
+ jar,
103
+ gzip: true
104
+ }
105
+
106
+ var callback;
107
+ var returnPromise = new Promise(function (resolve, reject) {
108
+ callback = (error, response) => error ? reject(error) : resolve(response);
109
+ });
110
+ request(options, callback);
111
+
112
+ return returnPromise;
113
+ }
114
+
115
+ function isReadableStream(maybeStream) {
116
+ return (maybeStream instanceof stream.Stream && typeof maybeStream._read === "function" && utilsLib.getType(maybeStream._readableState) === "Object");
117
+ }
118
+
119
+ function getGUID() {
120
+ var sectionLength = Date.now();
121
+ var id = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
122
+ var r = Math.floor((sectionLength + Math.random() * 16) % 16);
123
+ sectionLength = Math.floor(sectionLength / 16);
124
+ var _guid = (c === "x" ? r : (r & 7) | 8).toString(16);
125
+ return _guid;
126
+ });
127
+ return id;
128
+ }
129
+
130
+ function getSignatureID() {
131
+ return Math.floor(Math.random() * 2147483648).toString(16);
132
+ }
133
+
134
+ function makeDefaults(body, userID, ctx) {
135
+ var reqCounter = 1;
136
+ var fb_dtsg = /\["DTSGInitData",\[],{"token":"(\S+)","async_get_token"/g.exec(body)[1];
137
+ var revision = /"server_revision":(\d+)/g.exec(body)[1];
138
+
139
+ function mergeWithDefaults(Obj) {
140
+ fb_dtsg = ctx.fb_dtsg ? ctx.fb_dtsg : fb_dtsg
141
+ var ttstamp = "2";
142
+ for (var i = 0; i < fb_dtsg.length; i++) {
143
+ ttstamp += fb_dtsg.charCodeAt(i);
144
+ }
145
+ var newObj = {
146
+ av: userID,
147
+ __user: userID,
148
+ __req: (reqCounter++).toString(36),
149
+ __rev: revision,
150
+ __a: 1,
151
+ fb_dtsg,
152
+ jazoest: ctx.ttstamp ? ctx.ttstamp : ttstamp
153
+ }
154
+ if (!Obj)
155
+ return newObj;
156
+
157
+ for (var prop in Obj)
158
+ if (Obj.hasOwnProperty(prop))
159
+ if (!newObj[prop])
160
+ newObj[prop] = Obj[prop];
161
+
162
+ return newObj;
163
+ }
164
+
165
+ function postWithDefaults(url, jar, form, ctxx = ctx, customHeaders) {
166
+ return post(url, jar, mergeWithDefaults(form), ctxx, customHeaders);
167
+ }
168
+
169
+ function getWithDefaults(url, jar, qs, ctxx = ctx, customHeaders) {
170
+ return get(url, jar, mergeWithDefaults(qs), ctxx, customHeaders);
171
+ }
172
+
173
+ function postDataWithDefault(url, jar, form, qs, ctxx = ctx, customHeaders) {
174
+ return postData(url, jar, mergeWithDefaults(form), mergeWithDefaults(qs), ctxx, customHeaders);
175
+ }
176
+
177
+ return {
178
+ get: getWithDefaults,
179
+ post: postWithDefaults,
180
+ postData: postDataWithDefault
181
+ }
182
+ }
183
+
184
+ function makeParsable(body) {
185
+ var withoutForLoop = body.replace(/for\s*\(\s*;\s*;\s*\)\s*;\s*/, "");
186
+ var maybeMultipleObjects = withoutForLoop.split(/\}\r\n *\{/);
187
+ if (maybeMultipleObjects.length === 1)
188
+ return maybeMultipleObjects;
189
+ return "[" + maybeMultipleObjects.join("},{") + "]";
190
+ }
191
+
192
+ function parseAndCheckLogin(ctx, http, retryCount) {
193
+ var delay = ms => new Promise(resolve => setTimeout(resolve, ms));
194
+ var _try = tryData => new Promise(function (resolve, reject) {
195
+ try {
196
+ resolve(tryData());
197
+ } catch (error) {
198
+ reject(error);
199
+ }
200
+ });
201
+ if (retryCount === undefined) retryCount = 0;
202
+
203
+ return function (data) {
204
+ function any() {
205
+ if (data.statusCode >= 500 && data.statusCode < 600) {
206
+ if (retryCount >= 5) {
207
+ var err = new Error("Request retry failed. Check the `res` and `statusCode` property on this error.");
208
+ err.statusCode = data.statusCode;
209
+ err.res = data.body;
210
+ err.error = "Request retry failed. Check the `res` and `statusCode` property on this error.";
211
+ throw err;
212
+ }
213
+ retryCount++;
214
+ var retryTime = Math.floor(Math.random() * 5000);
215
+ var url = data.request.uri.protocol + "//" + data.request.uri.hostname + data.request.uri.pathname;
216
+ if (data.request.headers["Content-Type"].split(";")[0] === "multipart/form-data") {
217
+ return delay(retryTime)
218
+ .then(function () {
219
+ return http
220
+ .postData(url, ctx.jar, data.request.formData);
221
+ })
222
+ .then(parseAndCheckLogin(ctx, http, retryCount));
223
+ }
224
+ else {
225
+ return delay(retryTime)
226
+ .then(function () {
227
+ return http
228
+ .post(url, ctx.jar, data.request.formData);
229
+ })
230
+ .then(parseAndCheckLogin(ctx, http, retryCount));
231
+ }
232
+ }
233
+ if (data.statusCode !== 200)
234
+ throw new Error("parseAndCheckLogin got status code: " + data.statusCode + ". Bailing out of trying to parse response.");
235
+
236
+ var res = null;
237
+ try {
238
+ res = JSON.parse(makeParsable(data.body));
239
+ } catch (e) {
240
+ var err = new Error("JSON.parse error. Check the `detail` property on this error.");
241
+ err.error = "JSON.parse error. Check the `detail` property on this error.";
242
+ err.detail = e;
243
+ err.res = data.body;
244
+ throw err;
245
+ }
246
+
247
+ if (res.redirect && data.request.method === "GET") {
248
+ return http
249
+ .get(res.redirect, ctx.jar)
250
+ .then(parseAndCheckLogin(ctx, http));
251
+ }
252
+
253
+ if (res.jsmods && res.jsmods.require && Array.isArray(res.jsmods.require[0]) && res.jsmods.require[0][0] === "Cookie") {
254
+ res.jsmods.require[0][3][0] = res.jsmods.require[0][3][0].replace("_js_", "");
255
+ var cookie = formatCookie(res.jsmods.require[0][3], "facebook");
256
+ var cookie2 = formatCookie(res.jsmods.require[0][3], "messenger");
257
+ ctx.jar.setCookie(request.cookie(cookie), "https://www.facebook.com");
258
+ ctx.jar.setCookie(request.cookie(cookie2), "https://www.messenger.com");
259
+ }
260
+
261
+ if (res.jsmods && Array.isArray(res.jsmods.require)) {
262
+ var arr = res.jsmods.require;
263
+ for (var i in arr) {
264
+ if (arr[i][0] === "DTSG" && arr[i][1] === "setToken") {
265
+ ctx.fb_dtsg = arr[i][3][0];
266
+
267
+ ctx.ttstamp = "2";
268
+ for (var j = 0; j < ctx.fb_dtsg.length; j++) {
269
+ ctx.ttstamp += ctx.fb_dtsg.charCodeAt(j);
270
+ }
271
+ }
272
+ }
273
+ }
274
+
275
+ if (res.error === 1357001) {
276
+ var err = new Error('Facebook blocked login. Please visit https://facebook.com and check your account.');
277
+ err.type = "Logout.";
278
+ throw err;
279
+ }
280
+ return res;
281
+ }
282
+ return _try(any);
283
+ }
284
+ }
285
+
286
+ function formatID(id) {
287
+ if (id != undefined && id != null) {
288
+ return id.replace(/(fb)?id[:.]/, "");
289
+ }
290
+ else {
291
+ return id;
292
+ }
293
+ }
294
+
295
+ function getDTSGInitData(ctx) {
296
+ return get("https://www.facebook.com", ctx.jar)
297
+ .then(function (res) {
298
+ var body = res.body;
299
+ ctx.fb_dtsg = /\["DTSGInitData",\[],{"token":"(.+?)"/g.exec(body)[1];
300
+ });
301
+ }
302
+
303
+ function binaryToDecimal(data) {
304
+ var ret = "";
305
+ while (data !== "0") {
306
+ var end = 0;
307
+ var fullName = "";
308
+ var i = 0;
309
+ for (; i < data.length; i++) {
310
+ end = 2 * end + parseInt(data[i], 10);
311
+ if (end >= 10) {
312
+ fullName += "1";
313
+ end -= 10;
314
+ } else
315
+ fullName += "0";
316
+ }
317
+ ret = end.toString() + ret;
318
+ data = fullName.slice(fullName.indexOf("1"));
319
+ }
320
+ return ret;
321
+ }
322
+
323
+ function generateOfflineThreadingID() {
324
+ var ret = Date.now();
325
+ var value = Math.floor(Math.random() * 4294967295);
326
+ var str = ("0000000000000000000000" + value.toString(2)).slice(-22);
327
+ var message = ret.toString(2) + str;
328
+ return binaryToDecimal(message);
329
+ }
330
+
331
+ function generateTimestampRelative() {
332
+ var d = new Date();
333
+ return d.getHours() + ":" + padZeros(d.getMinutes());
334
+ }
335
+
336
+ function padZeros(val, len) {
337
+ val = String(val);
338
+ len = len || 2;
339
+ while (val.length < len) val = "0" + val;
340
+ return val;
341
+ }
342
+
343
+ function generateThreadingID(clientID) {
344
+ var k = Date.now();
345
+ var l = Math.floor(Math.random() * 4294967295);
346
+ var m = clientID;
347
+ return "<" + k + ":" + l + "-" + m + "@mail.projektitan.com>";
348
+ }
349
+
350
+ var utils = {
351
+ getJar: request.jar,
352
+ cookie: request.cookie,
353
+ setProxy,
354
+ parseProxy,
355
+ get,
356
+ post,
357
+ postData,
358
+ isReadableStream,
359
+ getGUID,
360
+ getSignatureID,
361
+ makeDefaults,
362
+ parseAndCheckLogin,
363
+ formatID,
364
+ getDTSGInitData,
365
+ generateOfflineThreadingID,
366
+ generateTimestampRelative,
367
+ generateThreadingID
368
+ }
369
+
370
+ Object.assign(utils, utilsLib);
371
+
372
+ module.exports = utils;
source/control/events/cache.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {}
source/control/index.js ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../lib/utils");
2
+ var log = require("../lib/log");
3
+ var fs = require("fs");
4
+ var Child = require("child_process");
5
+
6
+ var requiredOptions = {
7
+ name: [
8
+ "String",
9
+ true
10
+ ],
11
+ version: [
12
+ "String",
13
+ true
14
+ ],
15
+ role: [
16
+ "Number",
17
+ true
18
+ ],
19
+ author: [
20
+ "Array",
21
+ true
22
+ ],
23
+ category: [
24
+ "String",
25
+ true
26
+ ],
27
+ description: [
28
+ "Object",
29
+ true
30
+ ],
31
+ delay: [
32
+ "Number",
33
+ true
34
+ ],
35
+ guides: [
36
+ "Object",
37
+ true
38
+ ],
39
+ dependencies: [
40
+ "Array",
41
+ false
42
+ ],
43
+ envConfig: [
44
+ "Object",
45
+ false
46
+ ]
47
+ }
48
+
49
+ module.exports = function () {
50
+ var apis = global.mira.apis;
51
+ var { systemOptions } = global.mira.config;
52
+
53
+ if (systemOptions.autoLoadPlugins.enable) {
54
+ var path = require("path");
55
+ var fs = require("fs");
56
+ var dir = path.resolve(global.mira.dir, "plugins");
57
+ var plugins = fs.readdirSync(dir).filter(item => item.endsWith(".js") && !systemOptions.autoLoadPlugins.ignore.includes(item));
58
+
59
+ function LoadPlugin(pluginPath) {
60
+ delete require.cache[pluginPath];
61
+ function error(message) {
62
+ var error = new Error(message);
63
+ error.at = pluginPath;
64
+ return error;
65
+ }
66
+ try {
67
+ var pl = new (require(pluginPath))();
68
+ var Options = pl.Options;
69
+ var Langs = pl.Langs;
70
+ var Reply = pl.Reply;
71
+ var React = pl.React;
72
+ var Schedule = pl.Schedule;
73
+ var Events = pl.Events;
74
+ var Main = pl.Main;
75
+ if (utils.getType(Options) !== "Object")
76
+ throw error("Object is required in Options");
77
+
78
+ for (var item of Object.entries(requiredOptions)) {
79
+ if (item[1][1] && !Options[item[0]].toString() || utils.getType(Options[item[0]]) !== item[1][0])
80
+ throw error(item[0] + " is not accepted.");
81
+ }
82
+
83
+ if (typeof Main !== "function")
84
+ throw error("function is required in Main.");
85
+
86
+ if (utils.getType(Langs) !== "Object")
87
+ throw error("Object is required in " + lang);
88
+
89
+ for (var lang in Langs) {
90
+ if (utils.getType(Langs[lang]) !== "Object")
91
+ throw error("Object is required in " + lang);
92
+
93
+ for (var content in Langs[lang]) {
94
+ if (utils.getType(Langs[lang][content]) !== "String")
95
+ throw error("String is required in " + content);
96
+ }
97
+ }
98
+
99
+ if (Reply && typeof Reply !== "function")
100
+ throw error("Reply must be function");
101
+ if (React && typeof React !== "function")
102
+ throw error("React must be function");
103
+ if (Schedule && typeof Schedule !== "function")
104
+ throw error("Schedule must be function");
105
+ if (Events && typeof Events !== "function")
106
+ throw error("Events must be function");
107
+
108
+ if (Options.dependencies) {
109
+ pl.dependencies = {}
110
+ var execOptions = {
111
+ cwd: dir,
112
+ stdio: "inherit",
113
+ shell: true
114
+ }
115
+ for (var dependencie of Options.dependencies) {
116
+ try {
117
+ pl.dependencies[dependencie] = require(dependencie);
118
+ } catch (error) {
119
+ Child.execSync("npm install " + dependencie, execOptions);
120
+ pl.dependencies[dependencie] = require(dependencie);
121
+ }
122
+ }
123
+ }
124
+
125
+ if (Options.envConfig) {
126
+ var envConfig = {}
127
+ envConfig[Options.name] = {}
128
+ for (var env in Options.envConfig) {
129
+ envConfig[Options.name][env] = Options.envConfig[env];
130
+ }
131
+ global.mira.configCommands = envConfig;
132
+ }
133
+
134
+ global.modules.cmds = global.modules.cmds.filter(item => item[0] !== Options.name);
135
+ global.modules.cmds.push([Options.name, pl]);
136
+ } catch (error) {
137
+ console.log(error);
138
+ }
139
+ }
140
+
141
+ plugins.forEach(plugin => {
142
+ var pluginPath = path.resolve(dir, plugin);
143
+ LoadPlugin(pluginPath);
144
+ if (systemOptions.autoReloadPlugins.enable && !systemOptions.autoReloadPlugins.ignore.includes(plugin)) {
145
+ var lastReload = fs.statSync(pluginPath).mtimeMs;
146
+ var reloading = false;
147
+
148
+ function reload() {
149
+ if (reloading)
150
+ return;
151
+ reloading = true;
152
+ setTimeout(function () {
153
+ reloading = false;
154
+ if (lastReload === fs.statSync(pluginPath).mtimeMs)
155
+ return;
156
+
157
+ try {
158
+ LoadPlugin(pluginPath);
159
+ log.info("control.modules.reload", plugin);
160
+ } finally {}
161
+ }, 2000);
162
+ }
163
+ fs.watch(pluginPath, type => {
164
+ if (type === "change")
165
+ reload();
166
+ });
167
+ }
168
+ });
169
+
170
+ log.info("control.modules.len", global.modules.cmds.length);
171
+ }
172
+
173
+ var Client = new apis.Client();
174
+ var model = require("./model");
175
+ log.info("control.model.connect");
176
+ log.info("control.Client.connect");
177
+ log.wall();
178
+ global.mira.Client = Client;
179
+ return Client
180
+ .on("message", message => {
181
+ switch (message.type) {
182
+ case "message":
183
+ model.Main(message);
184
+ case "message_reply":
185
+ case "message_unsend":
186
+ model.db.createDataBase(message);
187
+ model.Events(message);
188
+ break;
189
+ case "event":
190
+ break;
191
+ case "message_reaction":
192
+ break;
193
+ default:
194
+ break;
195
+ }
196
+ })
197
+ .on("error", function (error) {
198
+ if (error.type === "disconnect")
199
+ Client.disconnect();
200
+
201
+ log.error("control.Client.error", error.message || error);
202
+ console.log(error);
203
+ });
204
+ }
source/control/model.js ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var utils = require("../lib/utils");
2
+ var log = require("../lib/log");
3
+ var { apis } = global.mira;
4
+ var { model } = global.database;
5
+ var ScheduleInterval;
6
+
7
+ var bannedAPIs = [
8
+ "Messenger",
9
+ "Client",
10
+ "markAsDelivered",
11
+ "markAsRead",
12
+ "getUserInfo"
13
+ ];
14
+
15
+ async function createDataBase(message) {
16
+ var { User, Thread } = model;
17
+ var { threadID, participantIDs, isGroup } = message;
18
+
19
+ if (isGroup) {
20
+ try {
21
+ await Thread.findOne(threadID);
22
+ } catch (error) {
23
+ log.error("database.create.fail", threadID, error.message);
24
+ console.log(error);
25
+ }
26
+ } else {
27
+ try {
28
+ await User.findOne(threadID);
29
+ } catch (error) {
30
+ log.error("database.create.fail", threadID, error.message);
31
+ console.log(error);
32
+ }
33
+ }
34
+
35
+ for (var userID of participantIDs || []) {
36
+ try {
37
+ await User.findOne(userID);
38
+ await new Promise(resolve => setTimeout(resolve, 2000));
39
+ } catch (error) {
40
+ log.error("database.create.fail", userID, error.message);
41
+ console.log(error);
42
+ }
43
+ }
44
+ }
45
+
46
+ async function updateDataBase(message) {
47
+
48
+ }
49
+
50
+ function levenshteinDistance(str1, str2) {
51
+ var len1 = str1.length;
52
+ var len2 = str2.length;
53
+ var dp = [];
54
+
55
+ for (var i = 0; i <= len1; i++)
56
+ dp[i] = [i];
57
+ for (var j = 0; j <= len2; j++)
58
+ dp[0][j] = j;
59
+ for (var i = 1; i <= len1; i++) {
60
+ for (var j = 1; j <= len2; j++) {
61
+ var cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
62
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
63
+ }
64
+ }
65
+
66
+ return dp[len1][len2];
67
+ }
68
+
69
+ function similarityScore(str1, str2) {
70
+ var maxLength = Math.max(str1.length, str2.length);
71
+ if (maxLength === 0)
72
+ return 1.0;
73
+ var distance = levenshteinDistance(str1, str2);
74
+ return (maxLength - distance) / maxLength;
75
+ }
76
+
77
+ function findBestMatch(target, possibleMatches) {
78
+ var bestMatch = "";
79
+ var bestScore = 0;
80
+
81
+ for (var match of possibleMatches) {
82
+ var score = similarityScore(target, match);
83
+ if (score > bestScore) {
84
+ bestScore = score;
85
+ bestMatch = match;
86
+ }
87
+ }
88
+
89
+ return {
90
+ target: bestMatch,
91
+ rating: bestScore
92
+ }
93
+ }
94
+
95
+ async function Main(info) {
96
+ var allCMD = [...global.modules.cmds];
97
+ var { prefix, adminOnly, adminIDs } = global.mira.config.botOptions;
98
+ var { language } = require("../../config.json").systemOptions;
99
+ var { senderID, threadID, isGroup } = info;
100
+ var { User, Thread } = model;
101
+ var escapedPrefix = prefix.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
102
+ var prefixRegex = new RegExp("^" + escapedPrefix, "i");
103
+ var isCalled = prefixRegex.test(info.body);
104
+ if (!isCalled)
105
+ return;
106
+
107
+ var [namePlg, ...args] = info.body.replace(prefixRegex, "").trim().split(/\s+/);
108
+ var cmd = allCMD.find(item => item[0] === namePlg);
109
+ var Messenger = utils.createMessenger(apis, info);
110
+ var userData = await User.findOne(senderID);
111
+ var userID = parseInt(senderID);
112
+
113
+ if (adminOnly && !adminIDs.includes(userID))
114
+ return;
115
+
116
+ if (isGroup) {
117
+ var threadData = await Thread.findOne(threadID);
118
+ if (threadData.banAt > 0 && !adminIDs.includes(userID))
119
+ return Messenger.reply(log.parseDir("control.model.threadBanned", [threadData.reason, utils.getTime(null, threadData.banAt)]));
120
+ }
121
+
122
+ if (userData.banAt > 0 && !adminIDs.includes(userID))
123
+ return Messenger.reply(log.parseDir("control.model.userBanned", [userData.reason, utils.getTime(null, userData.banAt)]));
124
+
125
+ if (namePlg.length === 0)
126
+ return Messenger.reply(log.parseDir("control.model.noPluginCalled", prefix));
127
+
128
+ if (!cmd) {
129
+ var allPlugins = allCMD.map(item => item[0]);
130
+ var bestMatch = findBestMatch(namePlg, allPlugins);
131
+ var content = log.parseDir("control.model.notExist", [namePlg, bestMatch.target]);
132
+ return Messenger.reply(content);
133
+ }
134
+
135
+ var pl = cmd[1];
136
+ var Langs = pl.Langs[language] || pl.Langs[Object.keys(pl.Langs)[0]];
137
+
138
+ function getText(dir, inputs) {
139
+ try {
140
+ if (Langs.hasOwnProperty(dir)) {
141
+ var content = Langs[dir];
142
+
143
+ if (inputs.length > 0) {
144
+ for (var index = 1; index <= inputs.length; index++)
145
+ content = content.replace("%" + index, inputs[index - 1]);
146
+ }
147
+
148
+ content = content.replace(/{p}|{n}/g, match => match === "{p}" ? prefix : namePlg);
149
+ return content;
150
+ }
151
+ return dir;
152
+ } catch (error) {
153
+ console.log(error);
154
+ return dir;
155
+ }
156
+ }
157
+
158
+ var subAPIs = { ...apis }
159
+
160
+ for (var api of bannedAPIs)
161
+ delete subAPIs[api];
162
+
163
+ Messenger.getText = getText;
164
+
165
+ var util = {
166
+ args,
167
+ Messenger,
168
+ events: info,
169
+ ...model,
170
+ apis: subAPIs
171
+ }
172
+
173
+ try {
174
+ pl.Main(util);
175
+ } catch (error) {
176
+ console.log(error);
177
+ }
178
+ }
179
+
180
+ async function Events(info) {
181
+ var allCMD = [...global.modules.cmds];
182
+ var { prefix, adminOnly, adminIDs } = global.mira.config.botOptions;
183
+ var { language } = require("../../config.json").systemOptions;
184
+ var { senderID, threadID, isGroup } = info;
185
+ var { User, Thread } = model;
186
+ var escapedPrefix = prefix.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
187
+ var prefixRegex = new RegExp("^" + escapedPrefix, "i");
188
+ var isCalled = prefixRegex.test(info.body);
189
+ if (isCalled || senderID === apis.getCurrentUserID())
190
+ return;
191
+
192
+ var args = info.body.trim().split(/\s+/);
193
+ var Messenger = utils.createMessenger(apis, info);
194
+ var userData = await User.findOne(senderID);
195
+ var userID = parseInt(senderID);
196
+
197
+ if (adminOnly && !adminIDs.includes(userID))
198
+ return;
199
+
200
+ if (isGroup) {
201
+ var threadData = await Thread.findOne(threadID);
202
+ if (threadData.banAt > 0 && !adminIDs.includes(userID))
203
+ return;
204
+ }
205
+
206
+ if (userData.banAt > 0 && !adminIDs.includes(userID))
207
+ return;
208
+
209
+ for (var item of allCMD.filter(item => !!item[1].Events)) {
210
+ var pl = item[1];
211
+ var Langs = pl.Langs[language] || pl.Langs[Object.keys(pl.Langs)[0]];
212
+
213
+ function getText(dir, inputs) {
214
+ try {
215
+ if (Langs.hasOwnProperty(dir)) {
216
+ var content = Langs[dir];
217
+
218
+ if (inputs.length > 0) {
219
+ for (var index = 1; index <= inputs.length; index++)
220
+ content = content.replace("%" + index, inputs[index - 1]);
221
+ }
222
+
223
+ content = content.replace(/{p}|{n}/g, match => match === "{p}" ? prefix : namePlg);
224
+ return content;
225
+ }
226
+ return dir;
227
+ } catch (error) {
228
+ console.log(error);
229
+ return dir;
230
+ }
231
+ }
232
+
233
+ var subAPIs = { ...apis }
234
+
235
+ for (var api of bannedAPIs)
236
+ delete subAPIs[api];
237
+
238
+ Messenger.getText = getText;
239
+
240
+ var util = {
241
+ args,
242
+ Messenger,
243
+ events: info,
244
+ ...model,
245
+ apis: subAPIs
246
+ }
247
+
248
+ try {
249
+ pl.Events(util);
250
+ } catch (error) {
251
+ console.log(error);
252
+ }
253
+ }
254
+ }
255
+
256
+ async function Reply(info) {
257
+
258
+ }
259
+
260
+ module.exports = {
261
+ db: {
262
+ createDataBase,
263
+ updateDataBase
264
+ },
265
+ Main,
266
+ Events
267
+ }
source/dashboard/assets/css/404.css ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ font-family: "Montserrat", sans-serif;
5
+ }
6
+
7
+ body {
8
+ display: flex;
9
+ justify-content: center;
10
+ align-items: center;
11
+ height: 100vh;
12
+
13
+ }
14
+
15
+ main {
16
+ display: flex;
17
+ flex-direction: row;
18
+ padding: 0 10%;
19
+ gap: 40px;
20
+ }
21
+
22
+ .main-text {
23
+ width: 50%;
24
+ text-align: start;
25
+ padding: 10px;
26
+ margin-right: 10px;
27
+ }
28
+
29
+ .main-text h3 {
30
+ font-size: 15px;
31
+ color: #ABB0B3;
32
+ text-transform: capitalize;
33
+ letter-spacing: 2px;
34
+ margin-bottom: 16px;
35
+ }
36
+
37
+ .main-text h1 {
38
+ font-size: 57px;
39
+ color: #121212;
40
+ font-weight: bold;
41
+ margin-top: 12px;
42
+ }
43
+
44
+ .main-text h2 {
45
+ font-size: 33px;
46
+ color: #121212;
47
+ font-weight: 400;
48
+ margin-bottom: 40px;
49
+ line-height: 48px;
50
+
51
+
52
+ }
53
+
54
+ .main-text a {
55
+ padding: 10px 20px;
56
+ background: linear-gradient(90deg, #fef506, #ff6575, #ae4df9, #029aff);
57
+ background-size: 500%;
58
+ color: white;
59
+ text-decoration: none;
60
+ border-radius: 5px;
61
+ animation: color 5s infinite alternate;
62
+
63
+ }
64
+
65
+ @keyframes color {
66
+ 0% {
67
+ background-position: left;
68
+ }
69
+
70
+ 100% {
71
+ background-position: right;
72
+ }
73
+
74
+ }
75
+
76
+ .main-text a:hover {
77
+ animation: color 1s infinite alternate;
78
+ }
79
+
80
+ .main-image {
81
+ display: flex;
82
+ justify-content: center;
83
+ width: 50%;
84
+ background-color: transparent;
85
+
86
+ }
87
+
88
+ @keyframes move {
89
+ 0% {
90
+ transform: translateY(-10px);
91
+ }
92
+
93
+ 25% {
94
+ transform: translateX(10px);
95
+ }
96
+
97
+ 50% {
98
+ transform: translateY(10px);
99
+ }
100
+
101
+ 75% {
102
+ transform: translateX(-10px);
103
+ }
104
+
105
+ 100% {
106
+ transform: translateY(-10px);
107
+ }
108
+ }
109
+
110
+ .main-image img {
111
+ width: 720px;
112
+ animation: move 10s infinite linear;
113
+ }
source/dashboard/assets/css/login.css ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ font-family: "Montserrat", sans-serif;
5
+ }
6
+
7
+ body {
8
+ display: flex;
9
+ justify-content: center;
10
+ align-items: center;
11
+ height: 100vh;
12
+ }
13
+
14
+ main {
15
+ display: flex;
16
+ flex-direction: row;
17
+ padding: 0 10%;
18
+ gap: 40px;
19
+ }
20
+
21
+ .main-text {
22
+ width: 50%;
23
+ text-align: start;
24
+ padding: 10px;
25
+ margin-right: 10px;
26
+ justify-content: right;
27
+ }
28
+
29
+ .text {
30
+ font-size: 33px;
31
+ font-weight: 600;
32
+ margin-bottom: 35px;
33
+ color: #595959;
34
+ text-align: center;
35
+ }
36
+
37
+ @keyframes color {
38
+ 0% {
39
+ background-position: left;
40
+ }
41
+
42
+ 100% {
43
+ background-position: right;
44
+ }
45
+ }
46
+
47
+ .main-image {
48
+ display: flex;
49
+ justify-content: left;
50
+ width: 50%;
51
+ background-color: transparent;
52
+ }
53
+
54
+ @keyframes move {
55
+ 0% {
56
+ transform: translateY(-10px);
57
+ }
58
+
59
+ 25% {
60
+ transform: translateX(10px);
61
+ }
62
+
63
+ 50% {
64
+ transform: translateY(10px);
65
+ }
66
+
67
+ 75% {
68
+ transform: translateX(-10px);
69
+ }
70
+
71
+ 100% {
72
+ transform: translateY(-10px);
73
+ }
74
+ }
75
+
76
+ .main-image img {
77
+ width: 720px;
78
+ animation: move 10s infinite linear;
79
+ }
80
+
81
+ .field {
82
+ height: 50px;
83
+ width: 100%;
84
+ display: flex;
85
+ position: relative;
86
+ }
87
+
88
+ .field:nth-child(2) {
89
+ margin-top: 20px;
90
+ }
91
+
92
+ .field input {
93
+ height: 100%;
94
+ width: 100%;
95
+ padding-left: 45px;
96
+ outline: none;
97
+ border: none;
98
+ font-size: 18px;
99
+ background: #dde1e7;
100
+ color: #595959;
101
+ border-radius: 25px;
102
+ box-shadow: inset 2px 2px 5px #BABECC,
103
+ inset -5px -5px 10px #ffffff73;
104
+ }
105
+
106
+ .field input:focus {
107
+ box-shadow: inset 1px 1px 2px #BABECC,
108
+ inset -1px -1px 2px #ffffff73;
109
+ }
110
+
111
+ .field span {
112
+ position: absolute;
113
+ left: 15px;
114
+ top: 50%;
115
+ transform: translateY(-50%);
116
+ color: #595959;
117
+ width: 50px;
118
+ line-height: 50px;
119
+ }
120
+
121
+ .field label {
122
+ position: absolute;
123
+ top: 50%;
124
+ transform: translateY(-50%);
125
+ left: 45px;
126
+ pointer-events: none;
127
+ color: #666666;
128
+ }
129
+
130
+ .field input:valid~label {
131
+ opacity: 0;
132
+ }
133
+
134
+ button {
135
+ margin: 15px 0;
136
+ width: 100%;
137
+ height: 50px;
138
+ font-size: 18px;
139
+ line-height: 50px;
140
+ font-weight: 600;
141
+ background: #dde1e7;
142
+ border-radius: 25px;
143
+ border: none;
144
+ outline: none;
145
+ cursor: pointer;
146
+ color: #595959;
147
+ box-shadow: 2px 2px 5px #BABECC,
148
+ -5px -5px 10px #ffffff73;
149
+ }
150
+
151
+ button:focus {
152
+ color: #3498db;
153
+ box-shadow: inset 2px 2px 5px #BABECC,
154
+ inset -5px -5px 10px #ffffff73;
155
+ }
156
+
157
+ .remember {
158
+ text-align: left;
159
+ margin: 10px 0 10px 5px;
160
+ text-transform: capitalize;
161
+ }
162
+
163
+ #remember {
164
+ accent-color: #fff;
165
+ }
166
+
167
+ .remember label {
168
+ display: flex;
169
+ align-items: center;
170
+ font-size: 16px;
171
+ text-decoration: none;
172
+ }
173
+
174
+ .remember label p {
175
+ margin-left: 8px;
176
+ }
source/dashboard/assets/image/403.png ADDED
source/dashboard/assets/image/404.png ADDED
source/dashboard/assets/image/login.png ADDED
source/dashboard/assets/js/lockkey.js ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function () {
2
+ function detectDevTools() {
3
+ var threshold = 160;
4
+ var devToolsOpen = false;
5
+
6
+ function checkDevTools() {
7
+ if (window.outerWidth - window.innerWidth > threshold ||
8
+ window.outerHeight - window.innerHeight > threshold) {
9
+ devToolsOpen = true;
10
+ alert("Developer tools are open!");
11
+ }
12
+ }
13
+ setInterval(checkDevTools, 1000);
14
+
15
+ document.addEventListener("keydown", function (event) {
16
+ if (event.key === "F12") {
17
+ event.preventDefault();
18
+ event.stopPropagation();
19
+ }
20
+ });
21
+
22
+ document.addEventListener("keydown", function (event) {
23
+ if (
24
+ (event.ctrlKey && event.shiftKey && event.key === "I") ||
25
+ (event.ctrlKey && event.shiftKey && event.key === "C") ||
26
+ (event.ctrlKey && event.shiftKey && event.key === "J") ||
27
+ (event.ctrlKey && event.key === "U")
28
+ ) {
29
+ event.preventDefault();
30
+ event.stopPropagation();
31
+ }
32
+ });
33
+
34
+ document.addEventListener("contextmenu", function (event) {
35
+ event.preventDefault();
36
+ event.stopPropagation();
37
+ });
38
+
39
+ document.addEventListener("copy", function (event) {
40
+ event.preventDefault();
41
+ event.stopPropagation();
42
+ });
43
+
44
+ document.addEventListener("cut", function (event) {
45
+ event.preventDefault();
46
+ event.stopPropagation();
47
+ });
48
+ document.body.style.userSelect = "none";
49
+ document.body.style.webkitUserSelect = "none";
50
+ document.body.style.mozUserSelect = "none";
51
+ document.body.style.msUserSelect = "none";
52
+ }
53
+
54
+ document.addEventListener("DOMContentLoaded", detectDevTools);
55
+
56
+ window.onload = function () {
57
+ document.addEventListener("contextmenu", function (event) {
58
+ event.preventDefault();
59
+ }, false);
60
+
61
+ document.addEventListener("keydown", function (event) {
62
+ if (event.ctrlKey && event.shiftKey && event.keyCode === 73) { // Ctrl+Shift+I
63
+ disabledEvent(event);
64
+ }
65
+ if (event.ctrlKey && event.shiftKey && event.keyCode === 74) { // Ctrl+Shift+J
66
+ disabledEvent(event);
67
+ }
68
+ if (event.keyCode === 83 && (navigator.platform.match("Mac") ? event.metaKey : event.ctrlKey)) { // Ctrl+S or Cmd+S
69
+ disabledEvent(event);
70
+ }
71
+ if (event.ctrlKey && event.keyCode === 85) { // Ctrl+U
72
+ disabledEvent(event);
73
+ }
74
+ if (event.keyCode === 123) { // F12
75
+ disabledEvent(event);
76
+ }
77
+ }, false);
78
+
79
+ function disabledEvent(event) {
80
+ if (event.stopPropagation) {
81
+ event.stopPropagation();
82
+ } else if (window.event) {
83
+ window.event.cancelBubble = true;
84
+ }
85
+ event.preventDefault();
86
+ return false;
87
+ }
88
+ }
89
+ })();
source/dashboard/assets/js/login.js ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function DOMContentLoaded() {
2
+ var formLogin = document.querySelector("form");
3
+ var isSubmitting = false;
4
+
5
+ async function formLoginSubmit(event) {
6
+ event.preventDefault();
7
+
8
+ if (isSubmitting)
9
+ return;
10
+
11
+ isSubmitting = true;
12
+
13
+ var formData = new FormData(formLogin);
14
+ var formDataObject = Object.fromEntries(formData.entries());
15
+ formDataObject.url = "/login";
16
+ formDataObject.remember = formLogin.querySelector("#remember").checked;
17
+
18
+ var submitButton = formLogin.querySelector("button");
19
+ submitButton.disabled = true;
20
+
21
+ try {
22
+ var response = await fetch("/", {
23
+ method: "POST",
24
+ headers: {
25
+ "Content-Type": "application/json"
26
+ },
27
+ body: JSON.stringify(formDataObject)
28
+ });
29
+
30
+ response = await response.json();
31
+ if (response.isLogin)
32
+ window.location.href = "/dashboard";
33
+ else
34
+ alert(response.message);
35
+ } catch (error) {
36
+ console.error(error);
37
+ formLogin.reset();
38
+ alert(error.message);
39
+ } finally {
40
+ submitButton.disabled = false;
41
+ isSubmitting = false;
42
+ }
43
+ }
44
+ formLogin.addEventListener("submit", formLoginSubmit);
45
+ }
46
+ document.addEventListener("DOMContentLoaded", DOMContentLoaded);
source/dashboard/index.js ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var log = require("../lib/log");
2
+ var utils = require("../lib/utils");
3
+
4
+ var path = require("path");
5
+ var Express = require("express");
6
+ var ExpressWs = require("express-ws");
7
+ var ExpressSession = require("express-session")({
8
+ secret: "không có gì là bí mật.",
9
+ resave: false,
10
+ saveUninitialized: true,
11
+ cookie: {
12
+ secure: true
13
+ }
14
+ });
15
+ var Os = require("os");
16
+ var fs = require("fs");
17
+
18
+ var App = Express();
19
+
20
+ ExpressWs(App);
21
+ App.set("view engine", "ejs");
22
+ App.set("views", path.resolve(__dirname, "views"));
23
+ App.use("/assets", utils.requestChecked, Express.static(path.resolve(__dirname, "assets")));
24
+ App.use(Express.urlencoded({ extended: true }));
25
+ App.use(Express.json());
26
+ App.use(ExpressSession);
27
+
28
+ if (global.mira.config.dashboardOptions.resetAccount) {
29
+ var { Messenger } = global.mira.apis;
30
+ var { adminIDs } = global.mira.config.botOptions;
31
+ var templMail = utils.randomStr({ length: 10, letter: true, number: false }) + "@mira.com";
32
+ var templPass = utils.randomStr({ length: 16 });
33
+ global.mira.config = {
34
+ dashboardOptions: {
35
+ user: templMail,
36
+ password: templPass
37
+ }
38
+ }
39
+
40
+ var message =
41
+ "==========================" +
42
+ "\n• Email: " + templMail +
43
+ "\n• Password: " + templPass +
44
+ "\n• Timestamp: " + Date.now() +
45
+ "\n==========================";
46
+ adminIDs.map(id => Messenger.send(message, id, _ => {}));
47
+ }
48
+
49
+ App.post("/", function (req, res) {
50
+ var { dashboardOptions } = global.mira.config;
51
+ var body = req.body;
52
+ var status, resData;
53
+
54
+ if (body.url === "/login") {
55
+ var { username, password, remember } = body;
56
+ if (username === dashboardOptions.user && password === dashboardOptions.password) {
57
+ req.session.loggedIn = true;
58
+ req.session.username = username;
59
+
60
+ req.session.cookie.maxAge = remember ? 7 * 24 * 60 * 60 * 1000 : 3 * 60 * 60 * 1000;
61
+ req.session.cookie.secure = req.secure || req.headers["x-forwarded-proto"] === "https";
62
+ req.session.cookie.httpOnly = true;
63
+
64
+ status = 200;
65
+ resData = {
66
+ isLogin: true
67
+ }
68
+ } else {
69
+ status = 401;
70
+ resData = {
71
+ message: "Wrong Username/Password."
72
+ }
73
+ }
74
+ } else if (body.url === "/logout") {
75
+ req.session.destroy();
76
+ status = 200;
77
+ resData = {
78
+ isLogin: false
79
+ }
80
+ }
81
+
82
+ res.status(status || 404);
83
+ res.json(resData || {});
84
+ });
85
+
86
+ App.get("/login", function (req, res) {
87
+ if (utils.isAuthenticated(req))
88
+ res.redirect("/dashboard");
89
+ else
90
+ res.render("login");
91
+ });
92
+
93
+ App.get("/", function (req, res) {
94
+ if (utils.isAuthenticated(req))
95
+ res.redirect("/dashboard");
96
+ else
97
+ res.redirect("/login");
98
+ });
99
+
100
+ App.use(function (req, res) {
101
+ res.status(404);
102
+ res.render("404");
103
+ });
104
+
105
+ var interfaces = Os.networkInterfaces();
106
+ var info;
107
+
108
+ for (var network in interfaces) {
109
+ if (info)
110
+ break;
111
+
112
+ info = interfaces[network].find(item => !item.internal && item.family === "IPv4");
113
+ }
114
+
115
+ var Server = App.listen(global.mira.config.dashboardOptions.port, _ => {
116
+ var port = Server.address().port;
117
+
118
+ log.info("dashboard.port", port);
119
+ log.info("dashboard.ip", info.address);
120
+ log.info("dashboard.host", "http://" + info.address + ":" + port);
121
+ log.wall();
122
+ });
123
+
source/dashboard/views/403.ejs ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
9
+ <meta name="author" content="Khang">
10
+ <meta name="keywords" content="mira, bot">
11
+ <title>Sorry, This Page Can't Be Accessed | Mira</title>
12
+ <link rel="preconnect" href="https://fonts.googleapis.com">
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
+ <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&display=swap"
15
+ rel="stylesheet">
16
+ <link rel="stylesheet" href="/assets/css/404.css">
17
+ <script src="/assets/js/lockkey.js"></script>
18
+ <script>
19
+ function GoBack() {
20
+ if (document.referrer)
21
+ history.back();
22
+ else
23
+ window.location.href = "https://google.com/";
24
+ }
25
+ </script>
26
+ </head>
27
+
28
+ <body>
29
+ <main>
30
+ <div class="main-text">
31
+ <h3>ERROR CODE: 403</h3>
32
+ <h1>OOOPS!!</h1>
33
+ <h2>Please go back to the previous page to continue browsing.</h2>
34
+ <a onclick="GoBack()">Go Back</a>
35
+ </div>
36
+ <div class="main-image">
37
+ <img src="/assets/image/403.png" alt="">
38
+ </div>
39
+ </main>
40
+ </body>
41
+
42
+ </html>
source/dashboard/views/404.ejs ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
9
+ <meta name="author" content="Khang">
10
+ <meta name="keywords" content="mira, bot">
11
+ <title>Sorry, This Page Can't Be Found | Mira</title>
12
+ <link rel="preconnect" href="https://fonts.googleapis.com">
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
+ <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&display=swap"
15
+ rel="stylesheet">
16
+ <link rel="stylesheet" href="/assets/css/404.css">
17
+ <script src="/assets/js/lockkey.js"></script>
18
+ </head>
19
+
20
+ <body>
21
+ <main>
22
+ <div class="main-text">
23
+ <h3>ERROR CODE: 404</h3>
24
+ <h1>OOOPS!!</h1>
25
+ <h2>This page may have been moved, deleted, or may never have existed.</h2>
26
+ <a href="/">Go Home</a>
27
+ </div>
28
+ <div class="main-image">
29
+ <img src="/assets/image/404.png" alt="">
30
+ </div>
31
+ </main>
32
+ </body>
33
+
34
+ </html>
source/dashboard/views/login.ejs ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
9
+ <meta name="author" content="Khang">
10
+ <meta name="keywords" content="mira, bot">
11
+ <title>Login | Mira</title>
12
+ <link rel="preconnect" href="https://fonts.googleapis.com">
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
+ <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&display=swap"
15
+ rel="stylesheet">
16
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" />
17
+ <link rel="stylesheet" href="/assets/css/login.css">
18
+ <script src="/assets/js/login.js"></script>
19
+ <script src="/assets/js/lockkey.js"></script>
20
+ </head>
21
+
22
+ <body>
23
+ <main>
24
+ <div class="main-text">
25
+ <div class="text">Login</div>
26
+ <form>
27
+ <div class="field">
28
+ <input type="text" name="username" required>
29
+ <span class="fas fa-user"></span>
30
+ <label>Email</label>
31
+ </div>
32
+ <div class="field">
33
+ <input type="password" name="password" required>
34
+ <span class="fas fa-lock"></span>
35
+ <label>Password</label>
36
+ </div>
37
+ <div class="remember">
38
+ <label for="remember">
39
+ <input type="checkbox" id="remember">
40
+ <p>Remember me</p>
41
+ </label>
42
+ </div>
43
+ <button type="submit">Sign in</button>
44
+ </form>
45
+ </div>
46
+ <div class="main-image">
47
+ <img src="/assets/image/login.png" alt="">
48
+ </div>
49
+ </main>
50
+ </body>
51
+
52
+ </html>
source/database/database.sqlite ADDED
Binary file (414 kB). View file
 
source/database/index.js ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var { DataBase } = global.mira.config.systemOptions;
2
+ var log = require("../lib/log");
3
+
4
+ async function SQLiteDataBase(firstConnect = true) {
5
+ var sql = require("sequelize");
6
+ var SQL = new sql({
7
+ dialect: "sqlite",
8
+ storage: __dirname + "/database.sqlite",
9
+ pool: {
10
+ max: 20,
11
+ min: 0,
12
+ acquire: 60000,
13
+ idle: 20000
14
+ },
15
+ retry: {
16
+ match: [
17
+ /SQLITE_BUSY/,
18
+ ],
19
+ name: "query",
20
+ max: 20
21
+ },
22
+ logging: false,
23
+ transactionType: "IMMEDIATE",
24
+ define: {
25
+ underscored: false,
26
+ freezeTableName: true,
27
+ charset: "utf8",
28
+ dialectOptions: {
29
+ collate: "utf8_general_ci"
30
+ },
31
+ timestamps: true
32
+ },
33
+ sync: {
34
+ force: false
35
+ }
36
+ });
37
+
38
+ try {
39
+ await SQL.authenticate();
40
+ log.info("database.authenticate.type", "SQLite");
41
+ await require("./model/user.sqlite3.js")(SQL, sql);
42
+ await require("./model/thread.sqlite3.js")(SQL, sql);
43
+ return log.wall();
44
+ } catch (error) {
45
+ log.error("database.authenticate.error", "SQLite", error.message);
46
+ console.log(error);
47
+ if (firstConnect) {
48
+ log.warn("database.reAuthenticated", "MongoDB");
49
+ var newConfig = global.mira.config;
50
+ newConfig.systemOptions.DataBase.type = "mongodb";
51
+ global.mira.config = newConfig;
52
+ return MongoDataBase(false);
53
+ } else
54
+ process.exit(1);
55
+ }
56
+ }
57
+
58
+ async function MongoDataBase(firstConnect = true) {
59
+ var { mongoURI } = DataBase;
60
+ var Mongo = require("mongoose");
61
+ var mongoRegex = /^(mongodb:\/\/|mongodb+srv:\/\/)[\w.-]+(:\d{2,5})?(\/[\w.-]*)?(\?.*)?$/;
62
+
63
+ try {
64
+ if (!mongoRegex.test(mongoURI))
65
+ throw new Error("mongoURI is invalid.");
66
+ await Mongo.connect(mongoURI);
67
+ log.info("database.authenticate.type", "MongoDB");
68
+ await require("./model/user.mongodb.js")(Mongo);
69
+ await require("./model/thread.mongodb.js")(Mongo);
70
+ return log.wall();
71
+ } catch (error) {
72
+ log.error("database.authenticate.error", "MongoDB", error.message);
73
+ console.log(error);
74
+ if (firstConnect) {
75
+ log.warn("database.reAuthenticated", "SQLite");
76
+ var newConfig = global.mira.config;
77
+ newConfig.systemOptions.DataBase.type = "sqlite";
78
+ global.mira.config = newConfig;
79
+ return SQLiteDataBase(false);
80
+ } else
81
+ process.exit(1);
82
+ }
83
+ }
84
+
85
+ if (DataBase.type !== "sqlite" && DataBase.type !== "mongodb") {
86
+ log.warn("database.notSupport", DataBase.type);
87
+ log.warn("database.defaultType");
88
+ var newConfig = global.mira.config;
89
+ newConfig.systemOptions.DataBase.type = "sqlite";
90
+ global.mira.config = newConfig;
91
+ }
92
+
93
+ module.exports = DataBase.type.toLowerCase() === "sqlite" ? SQLiteDataBase : MongoDataBase;
source/database/model/thread.mongodb.js ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var { apis } = global.mira;
2
+ var log = require("../../lib/log");
3
+ var { getType } = require("../../lib/utils");
4
+
5
+ module.exports = async function ThreadDataBase(Client) {
6
+ var ThreadShema = new Client.Schema({
7
+ threadID: {
8
+ type: Client.Schema.Types.Decimal128,
9
+ unique: true,
10
+ required: true
11
+ },
12
+ name: {
13
+ type: String,
14
+ required: true,
15
+ validate: {
16
+ validator: v => v && v.trim().length > 0,
17
+ message: "Name must not be empty"
18
+ }
19
+ },
20
+ info: {
21
+ type: Client.Schema.Types.Mixed,
22
+ validate: {
23
+ validator: function (v) {
24
+ try {
25
+ JSON.stringify(v);
26
+ return true;
27
+ } catch (e) {
28
+ return false;
29
+ }
30
+ },
31
+ message: "Info must be a valid JSON object"
32
+ }
33
+ },
34
+ data: {
35
+ type: Client.Schema.Types.Mixed,
36
+ validate: {
37
+ validator: function (v) {
38
+ try {
39
+ JSON.stringify(v);
40
+ return true;
41
+ } catch (e) {
42
+ return false;
43
+ }
44
+ },
45
+ message: "Data must be a valid JSON object"
46
+ }
47
+ },
48
+ banAt: {
49
+ type: Number,
50
+ default: 0,
51
+ validate: {
52
+ validator: v => Number.isInteger(v) && v >= 0,
53
+ message: "banAt must be a non-negative integer"
54
+ }
55
+ },
56
+ reason: {
57
+ type: String,
58
+ required: false,
59
+ validate: {
60
+ validator: v => (!v || v.trim().length > 0),
61
+ message: "Reason must not be empty if providedy"
62
+ }
63
+ }
64
+ }, {
65
+ timestamps: true
66
+ });
67
+
68
+ ThreadShema.pre("save", function (next) {
69
+ this.updatedAt = Date.now();
70
+ next();
71
+ });
72
+ var Thread = Client.model("Thread", ThreadShema);
73
+
74
+ function getAll() {
75
+ return Thread.find();
76
+ }
77
+
78
+ function find(conditions = {}) {
79
+ if (getType(conditions) !== "Object")
80
+ throw new Error("conditions must be an JSON Object.");
81
+
82
+ return Thread.find(conditions);
83
+ }
84
+
85
+ async function getInfo(threadID) {
86
+ if (isNaN(parseInt(threadID)))
87
+ throw new Error("threadID must be a string number.");
88
+
89
+ var infoObj = await apis.getThreadInfo(threadID);
90
+ return infoObj;
91
+ }
92
+
93
+ async function findOne(threadID) {
94
+ if (isNaN(parseInt(threadID)))
95
+ throw new Error("threadID must be a string number.");
96
+
97
+ var thread = await Thread.findOne({ threadID });
98
+ if (!thread) {
99
+ await createData(threadID);
100
+ thread = await findOne(threadID);
101
+ }
102
+
103
+ return thread;
104
+ }
105
+
106
+ async function createData(threadID) {
107
+ var info = await getInfo(threadID);
108
+ var infoObj = {
109
+ threadID,
110
+ name: info.name,
111
+ money: 0,
112
+ info,
113
+ data: {},
114
+ banAt: 0,
115
+ reason: null
116
+ }
117
+ delete infoObj.info.name;
118
+ await Thread.findOneAndUpdate({ threadID }, infoObj, { upsert: true, new: true });
119
+ log.info("database.create.success", threadID);
120
+ return;
121
+ }
122
+
123
+ async function setData(threadID, data) {
124
+ if (isNaN(parseInt(threadID)))
125
+ throw new Error("threadID must be a string number.");
126
+ if (getType(data) !== "Object")
127
+ throw new Error("data must be an JSON Object.");
128
+
129
+ await Thread.updateOne({ threadID }, { $set: data });
130
+ return;
131
+ }
132
+
133
+ async function setDataAll(data, conditions = {}) {
134
+ if (getType(conditions) !== "Object")
135
+ throw new Error("conditions must be an JSON Object.");
136
+ if (getType(data) !== "Object")
137
+ throw new Error("data must be an JSON Object.");
138
+
139
+ await Thread.updateMany(conditions, { $set: data });
140
+ return;
141
+ }
142
+
143
+ async function deleteThread(threadID) {
144
+ if (isNaN(parseInt(threadID)))
145
+ throw new Error("threadID must be a string number.");
146
+ await Thread.deleteOne({ threadID });
147
+ return;
148
+ }
149
+
150
+ log.info("database.model", "Thread.mongodb");
151
+ return global.database.model.Thread = {
152
+ getAll,
153
+ find,
154
+ findOne,
155
+ setData,
156
+ setDataAll,
157
+ deleteThread
158
+ }
159
+ }
source/database/model/thread.sqlite3.js ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var { apis } = global.mira;
2
+ var log = require("../../lib/log");
3
+ var { getType } = require("../../lib/utils");
4
+
5
+ module.exports = async function ThreadDataBase(SQL, sql) {
6
+ var Thread = SQL.define("Thread", {
7
+ threadID: {
8
+ type: sql.BIGINT,
9
+ unique: true
10
+ },
11
+ name: {
12
+ type: sql.STRING,
13
+ allowNull: false,
14
+ validate: {
15
+ notEmpty: true
16
+ }
17
+ },
18
+ info: {
19
+ type: sql.JSON,
20
+ validate: {
21
+ isJSON: value => JSON.stringify(value)
22
+ }
23
+ },
24
+ data: {
25
+ type: sql.JSON,
26
+ validate: {
27
+ isJSON: value => JSON.stringify(value)
28
+ }
29
+ },
30
+ banAt: {
31
+ type: sql.BIGINT,
32
+ defaultValue: 0,
33
+ validate: {
34
+ isInt: true,
35
+ min: 0
36
+ }
37
+ },
38
+ reason: {
39
+ type: sql.STRING,
40
+ allowNull: true,
41
+ validate: {
42
+ notEmpty: true
43
+ }
44
+ }
45
+ });
46
+
47
+ Thread.sync({ force: false });
48
+
49
+ function getAll() {
50
+ return Thread.findAll();
51
+ }
52
+
53
+ function find(conditions = {}) {
54
+ if (getType(conditions) !== "Object")
55
+ throw new Error("conditions must be an JSON Object.");
56
+
57
+ return Thread.findAll({ where: conditions });
58
+ }
59
+
60
+ async function getInfo(threadID) {
61
+ if (isNaN(parseInt(threadID)))
62
+ throw new Error("ThreadID must be a string number.");
63
+
64
+ var infoObj = await apis.getThreadInfo(threadID);
65
+ return infoObj;
66
+ }
67
+
68
+ async function findOne(threadID) {
69
+ if (isNaN(parseInt(threadID)))
70
+ throw new Error("ThreadID must be a string number.");
71
+
72
+ var threadData = await Thread.findOne({ where: { threadID } });
73
+ if (!threadData) {
74
+ await createData(threadID);
75
+ return await findOne(threadID);
76
+ }
77
+
78
+ return threadData.get({ plain: true });
79
+ }
80
+
81
+ async function createData(threadID) {
82
+ var info = await getInfo(threadID);
83
+ var infoObj = {
84
+ threadID,
85
+ name: info.name,
86
+ money: 0,
87
+ info,
88
+ data: {}
89
+ }
90
+ delete infoObj.info.name;
91
+ await Thread.findOrCreate({ where: { threadID }, defaults: infoObj });
92
+ log.info("database.create.success", threadID);
93
+ return;
94
+ }
95
+
96
+ async function setData(threadID, data) {
97
+ if (isNaN(parseInt(threadID)))
98
+ throw new Error("ThreadID must be a string number.");
99
+ if (getType(data) !== "Object")
100
+ throw new Error("data must be an JSON Object.");
101
+
102
+ await Thread.update(data, { where: { threadID } });
103
+ return;
104
+ }
105
+
106
+ async function setDataAll(data, conditions = {}) {
107
+ if (getType(conditions) !== "Object")
108
+ throw new Error("conditions must be an JSON Object.");
109
+ if (getType(data) !== "Object")
110
+ throw new Error("data must be an JSON Object.");
111
+
112
+ await Thread.update(data, { where: conditions });
113
+ return;
114
+ }
115
+
116
+ async function deleteThread(threadID) {
117
+ if (isNaN(parseInt(threadID)))
118
+ throw new Error("threadID must be a string number.");
119
+ await Thread.destroy({ where: { threadID } });
120
+ return;
121
+ }
122
+
123
+ log.info("database.model", "Thread.sqlite3");
124
+ return global.database.model.Thread = {
125
+ getAll,
126
+ find,
127
+ findOne,
128
+ setData,
129
+ setDataAll,
130
+ deleteThread
131
+ }
132
+ }
source/database/model/user.mongodb.js ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var { apis } = global.mira;
2
+ var log = require("../../lib/log");
3
+ var { getType } = require("../../lib/utils");
4
+
5
+ module.exports = async function UserDataBase(Client) {
6
+ var UserShema = new Client.Schema({
7
+ userID: {
8
+ type: Client.Schema.Types.Decimal128,
9
+ unique: true,
10
+ required: true
11
+ },
12
+ name: {
13
+ type: String,
14
+ required: true,
15
+ validate: {
16
+ validator: v => v && v.trim().length > 0,
17
+ message: "Name must not be empty"
18
+ }
19
+ },
20
+ money: {
21
+ type: Client.Schema.Types.Decimal128,
22
+ default: 0,
23
+ validate: {
24
+ validator: v => Number.isInteger(v) && v >= 0,
25
+ message: "Money must be a non-negative integer"
26
+ }
27
+ },
28
+ info: {
29
+ type: Client.Schema.Types.Mixed,
30
+ validate: {
31
+ validator: function (v) {
32
+ try {
33
+ JSON.stringify(v);
34
+ return true;
35
+ } catch (e) {
36
+ return false;
37
+ }
38
+ },
39
+ message: "Info must be a valid JSON object"
40
+ }
41
+ },
42
+ data: {
43
+ type: Client.Schema.Types.Mixed,
44
+ validate: {
45
+ validator: function (v) {
46
+ try {
47
+ JSON.stringify(v);
48
+ return true;
49
+ } catch (e) {
50
+ return false;
51
+ }
52
+ },
53
+ message: "Data must be a valid JSON object"
54
+ }
55
+ },
56
+ banAt: {
57
+ type: Number,
58
+ default: 0,
59
+ validate: {
60
+ validator: v => Number.isInteger(v) && v >= 0,
61
+ message: "banAt must be a non-negative integer"
62
+ }
63
+ },
64
+ reason: {
65
+ type: String,
66
+ required: false,
67
+ validate: {
68
+ validator: v => (!v || v.trim().length > 0),
69
+ message: "Reason must not be empty if providedy"
70
+ }
71
+ }
72
+ }, {
73
+ timestamps: true
74
+ });
75
+
76
+ UserShema.pre("save", function (next) {
77
+ this.updatedAt = Date.now();
78
+ next();
79
+ });
80
+ var User = Client.model("User", UserShema);
81
+
82
+ function getAll() {
83
+ return User.find();
84
+ }
85
+
86
+ function find(conditions = {}) {
87
+ if (getType(conditions) !== "Object")
88
+ throw new Error("conditions must be an JSON Object.");
89
+
90
+ return User.find(conditions);
91
+ }
92
+
93
+ async function getInfo(userID) {
94
+ if (isNaN(parseInt(userID)))
95
+ throw new Error("userID must be a string number.");
96
+
97
+ var infoObj = await apis.getUserInfo(userID);
98
+ return infoObj[userID] || {}
99
+ }
100
+
101
+ async function findOne(userID) {
102
+ if (isNaN(parseInt(userID)))
103
+ throw new Error("userID must be a string number.");
104
+
105
+ var user = await User.findOne({ userID });
106
+ if (!user) {
107
+ await createData(userID);
108
+ user = await findOne(userID);
109
+ }
110
+
111
+ return user;
112
+ }
113
+
114
+ async function createData(userID) {
115
+ var info = await getInfo(userID);
116
+ var infoObj = {
117
+ userID,
118
+ name: info.name || "User",
119
+ money: 0,
120
+ info,
121
+ data: {},
122
+ banAt: 0,
123
+ reason: null
124
+ }
125
+ delete infoObj.info.name;
126
+ await User.findOneAndUpdate({ userID }, infoObj, { upsert: true, new: true });
127
+ log.info("database.create.success", userID);
128
+ return;
129
+ }
130
+
131
+ async function setData(userID, data) {
132
+ if (isNaN(parseInt(userID)))
133
+ throw new Error("userID must be a string number.");
134
+ if (getType(data) !== "Object")
135
+ throw new Error("data must be an JSON Object.");
136
+
137
+ await User.updateOne({ userID }, { $set: data });
138
+ return;
139
+ }
140
+
141
+ async function setDataAll(data, conditions = {}) {
142
+ if (getType(conditions) !== "Object")
143
+ throw new Error("conditions must be an JSON Object.");
144
+ if (getType(data) !== "Object")
145
+ throw new Error("data must be an JSON Object.");
146
+
147
+ await User.updateMany(conditions, { $set: data });
148
+ return;
149
+ }
150
+
151
+ async function deleteUser(userID) {
152
+ if (isNaN(parseInt(userID)))
153
+ throw new Error("userID must be a string number.");
154
+ await User.deleteOne({ userID });
155
+ return;
156
+ }
157
+
158
+ log.info("database.model", "User.mongodb");
159
+ return global.database.model.User = {
160
+ getAll,
161
+ find,
162
+ findOne,
163
+ setData,
164
+ setDataAll,
165
+ deleteUser
166
+ }
167
+ }
source/database/model/user.sqlite3.js ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var { apis } = global.mira;
2
+ var log = require("../../lib/log");
3
+ var { getType } = require("../../lib/utils");
4
+
5
+ module.exports = async function UserDataBase(SQL, sql) {
6
+ var User = SQL.define("User", {
7
+ userID: {
8
+ type: sql.BIGINT,
9
+ unique: true
10
+ },
11
+ name: {
12
+ type: sql.STRING,
13
+ allowNull: false,
14
+ validate: {
15
+ notEmpty: true
16
+ }
17
+ },
18
+ money: {
19
+ type: sql.BIGINT,
20
+ defaultValue: 0,
21
+ validate: {
22
+ isInt: true,
23
+ min: 0
24
+ }
25
+ },
26
+ info: {
27
+ type: sql.JSON,
28
+ validate: {
29
+ isJSON: value => JSON.stringify(value)
30
+ }
31
+ },
32
+ data: {
33
+ type: sql.JSON,
34
+ validate: {
35
+ isJSON: value => JSON.stringify(value)
36
+ }
37
+ },
38
+ banAt: {
39
+ type: sql.BIGINT,
40
+ defaultValue: 0,
41
+ validate: {
42
+ isInt: true,
43
+ min: 0
44
+ }
45
+ },
46
+ reason: {
47
+ type: sql.STRING,
48
+ allowNull: true,
49
+ validate: {
50
+ notEmpty: true
51
+ }
52
+ }
53
+ });
54
+
55
+ User.sync({ force: false });
56
+
57
+ function getAll() {
58
+ return User.findAll();
59
+ }
60
+
61
+ function find(conditions = {}) {
62
+ if (getType(conditions) !== "Object")
63
+ throw new Error("conditions must be an JSON Object.");
64
+
65
+ return User.findAll({ where: conditions });
66
+ }
67
+
68
+ async function getInfo(userID) {
69
+ if (isNaN(parseInt(userID)))
70
+ throw new Error("userID must be a string number.");
71
+
72
+ var infoObj = await apis.getUserInfo(userID);
73
+ return infoObj[userID] || {}
74
+ }
75
+
76
+ async function findOne(userID) {
77
+ if (isNaN(parseInt(userID)))
78
+ throw new Error("userID must be a string number.");
79
+
80
+ var userData = await User.findOne({ where: { userID } });
81
+ if (!userData) {
82
+ await createData(userID);
83
+ return await findOne(userID);
84
+ }
85
+
86
+ return userData.get({ plain: true });
87
+ }
88
+
89
+ async function createData(userID) {
90
+ var info = await getInfo(userID);
91
+ var infoObj = {
92
+ userID,
93
+ name: info.name || "User",
94
+ money: 0,
95
+ info,
96
+ data: {},
97
+ banAt: 0,
98
+ reason: null
99
+ }
100
+ delete infoObj.info.name;
101
+ await User.findOrCreate({ where: { userID }, defaults: infoObj });
102
+ log.info("database.create.success", userID);
103
+ return;
104
+ }
105
+
106
+ async function setData(userID, data) {
107
+ if (isNaN(parseInt(userID)))
108
+ throw new Error("userID must be a string number.");
109
+ if (getType(data) !== "Object")
110
+ throw new Error("data must be an JSON Object.");
111
+
112
+ await User.update(data, { where: { userID } });
113
+ return;
114
+ }
115
+
116
+ async function setDataAll(data, conditions = {}) {
117
+ if (getType(conditions) !== "Object")
118
+ throw new Error("conditions must be an JSON Object.");
119
+ if (getType(data) !== "Object")
120
+ throw new Error("data must be an JSON Object.");
121
+
122
+ await User.update(data, { where: conditions });
123
+ return;
124
+ }
125
+
126
+ async function deleteUser(userID) {
127
+ if (isNaN(parseInt(userID)))
128
+ throw new Error("userID must be a string number.");
129
+ await User.destroy({ where: { userID } });
130
+ return;
131
+ }
132
+
133
+ log.info("database.model", "User.sqlite3");
134
+ return global.database.model.User = {
135
+ getAll,
136
+ find,
137
+ findOne,
138
+ setData,
139
+ setDataAll,
140
+ deleteUser
141
+ }
142
+ }
source/index.js ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var { spawn } = require("child_process");
2
+ var { version, name } = require("../package.json");
3
+ var log = require("./lib/log");
4
+
5
+ function RunSystem() {
6
+ log.wall();
7
+ log.info("process.name", name);
8
+ log.info("process.version", version);
9
+ var Mira = spawn("node", ["mira.js"], {
10
+ cwd: __dirname,
11
+ stdio: "inherit",
12
+ shell: true
13
+ });
14
+
15
+ Mira.on("close", exitCode => {
16
+ if (exitCode === 2) {
17
+ log.info("process.restarting");
18
+ setTimeout(_ => {
19
+ console.clear();
20
+ RunSystem();
21
+ }, 5000);
22
+ } else {
23
+ log.info("process.exit", exitCode);
24
+ }
25
+ });
26
+ }
27
+ RunSystem();