Spaces:
Running
Running
Duongkum999
commited on
Commit
•
91d9d20
1
Parent(s):
a47764e
Upload 56 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +35 -35
- Dockerfile +49 -49
- LICENSE +21 -0
- README.md +12 -12
- changelog.json +33 -0
- config.json +62 -0
- configCommands.json +4 -0
- package-lock.json +0 -0
- package.json +44 -0
- plugins/fb.js +63 -0
- source/apis/index.js +272 -0
- source/apis/lib/Client.js +1132 -0
- source/apis/lib/Messenger.js +464 -0
- source/apis/lib/changeAvatar.js +103 -0
- source/apis/lib/changeBio.js +61 -0
- source/apis/lib/changeCover.js +84 -0
- source/apis/lib/getAccessToken.js +81 -0
- source/apis/lib/getCurrentUserID.js +5 -0
- source/apis/lib/getThreadInfo.js +163 -0
- source/apis/lib/getUserID.js +91 -0
- source/apis/lib/getUserInfo.js +144 -0
- source/apis/lib/logout.js +53 -0
- source/apis/lib/markAsDelivered.js +35 -0
- source/apis/lib/markAsRead.js +41 -0
- source/apis/lib/muteThread.js +49 -0
- source/apis/lib/post.js +12 -0
- source/apis/lib/resolvePhotoUrl.js +63 -0
- source/apis/lib/screenshot.js +77 -0
- source/apis/utils.js +372 -0
- source/control/events/cache.json +1 -0
- source/control/index.js +204 -0
- source/control/model.js +267 -0
- source/dashboard/assets/css/404.css +113 -0
- source/dashboard/assets/css/login.css +176 -0
- source/dashboard/assets/image/403.png +0 -0
- source/dashboard/assets/image/404.png +0 -0
- source/dashboard/assets/image/login.png +0 -0
- source/dashboard/assets/js/lockkey.js +89 -0
- source/dashboard/assets/js/login.js +46 -0
- source/dashboard/index.js +123 -0
- source/dashboard/views/403.ejs +42 -0
- source/dashboard/views/404.ejs +34 -0
- source/dashboard/views/login.ejs +52 -0
- source/database/database.sqlite +0 -0
- source/database/index.js +93 -0
- source/database/model/thread.mongodb.js +159 -0
- source/database/model/thread.sqlite3.js +132 -0
- source/database/model/user.mongodb.js +167 -0
- source/database/model/user.sqlite3.js +142 -0
- 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 |
-
|
37 |
-
|
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": "zpNJSxhCuG@mira.com",
|
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:Promaxcapdo123@mira.kcuhmnf.mongodb.net/?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();
|