From d09a43c3d9998aeb6066586e3af15c65fc854cdb Mon Sep 17 00:00:00 2001
From: JianMiau <bir840124@gmail.com>
Date: Thu, 24 Apr 2025 15:31:46 +0800
Subject: [PATCH] [add] /everyone

---
 .env.local        |   3 +-
 app.js            |  32 ++++++++
 db.js             |  13 +++
 models/User.js    |   9 +++
 package-lock.json | 200 +++++++++++++++++++++++++++++++++++++++++++++-
 package.json      |   1 +
 6 files changed, 254 insertions(+), 4 deletions(-)
 create mode 100644 db.js
 create mode 100644 models/User.js

diff --git a/.env.local b/.env.local
index 450d9f0..9c40b43 100644
--- a/.env.local
+++ b/.env.local
@@ -1,3 +1,2 @@
 BOT_TOKEN=
-WEBHOOK_DOMAIN=
-PORT=
\ No newline at end of file
+MONGODB_URI=
\ No newline at end of file
diff --git a/app.js b/app.js
index e48d9ca..c2eb2c6 100644
--- a/app.js
+++ b/app.js
@@ -4,6 +4,7 @@
 
 
 require('dotenv').config(); // 加這行載入 .env 檔案
+const User = require('./models/User');
 const TelegramBot = require('node-telegram-bot-api');
 
 const token = process.env.BOT_TOKEN; // 從環境變數讀取 token
@@ -41,4 +42,35 @@ bot.onText(/\/roll(?:@[\w_]+)?(?:\s+(\d*)d(\d+))?/i, function (msg, match) {
 
 	const resp = `🎲 ${count}d${sides} 擲出:\n🎯 ${rolls.join(' + ')} = ${total}`;
 	bot.sendMessage(chatId, resp);
+});
+
+bot.onText(/\/everyone/, async (msg) => {
+	const chatId = msg.chat.id;
+
+	const users = await User.find({});
+	if (users.length === 0) {
+		return bot.sendMessage(chatId, '尚未記錄任何使用者。');
+	}
+
+	const tags = users
+		.map((u) => u.username ? `@${u.username}` : u.firstName || '')
+		.filter(Boolean)
+		.join(' ');
+
+	bot.sendMessage(chatId, `📣 呼叫所有人:\n${tags}`);
+});
+
+bot.on('message', async (msg) => {
+	const { chat } = msg;
+	if (chat.type !== 'private') return;
+
+	await User.findOneAndUpdate(
+		{ chatId: chat.id },
+		{
+			chatId: chat.id,
+			username: chat.username || '',
+			firstName: chat.first_name || '',
+		},
+		{ upsert: true, new: true }
+	);
 });
\ No newline at end of file
diff --git a/db.js b/db.js
new file mode 100644
index 0000000..35eac7d
--- /dev/null
+++ b/db.js
@@ -0,0 +1,13 @@
+const mongoose = require('mongoose');
+require('dotenv').config();
+
+mongoose.connect(process.env.MONGODB_URI, {
+	useNewUrlParser: true,
+	useUnifiedTopology: true,
+});
+
+const db = mongoose.connection;
+db.on('error', console.error.bind(console, 'MongoDB 連線錯誤:'));
+db.once('open', () => console.log('✅ 已連線到 MongoDB'));
+
+module.exports = mongoose;
diff --git a/models/User.js b/models/User.js
new file mode 100644
index 0000000..aced22f
--- /dev/null
+++ b/models/User.js
@@ -0,0 +1,9 @@
+const mongoose = require('../db');
+
+const userSchema = new mongoose.Schema({
+	chatId: { type: Number, required: true, unique: true },
+	username: { type: String },
+	firstName: { type: String },
+});
+
+module.exports = mongoose.model('User', userSchema);
diff --git a/package-lock.json b/package-lock.json
index fc1cacc..05ce555 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,9 +10,9 @@
 			"license": "ISC",
 			"dependencies": {
 				"dotenv": "^16.5.0",
+				"mongoose": "^8.13.2",
 				"node-telegram-bot-api": "^0.66.0"
-			},
-			"devDependencies": {}
+			}
 		},
 		"node_modules/@cypress/request": {
 			"version": "3.0.8",
@@ -73,6 +73,27 @@
 				"node": ">=6"
 			}
 		},
+		"node_modules/@mongodb-js/saslprep": {
+			"version": "1.2.2",
+			"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.2.tgz",
+			"integrity": "sha512-EB0O3SCSNRUFk66iRCpI+cXzIjdswfCs7F6nOC3RAGJ7xr5YhaicvsRwJ9eyzYvYRlCSDUO/c7g4yNulxKC1WA==",
+			"dependencies": {
+				"sparse-bitfield": "^3.0.3"
+			}
+		},
+		"node_modules/@types/webidl-conversions": {
+			"version": "7.0.3",
+			"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
+			"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
+		},
+		"node_modules/@types/whatwg-url": {
+			"version": "11.0.5",
+			"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
+			"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
+			"dependencies": {
+				"@types/webidl-conversions": "*"
+			}
+		},
 		"node_modules/ajv": {
 			"version": "6.12.6",
 			"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -218,6 +239,14 @@
 			"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
 			"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
 		},
+		"node_modules/bson": {
+			"version": "6.10.3",
+			"resolved": "https://registry.npmjs.org/bson/-/bson-6.10.3.tgz",
+			"integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==",
+			"engines": {
+				"node": ">=16.20.1"
+			}
+		},
 		"node_modules/call-bind": {
 			"version": "1.0.8",
 			"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -1222,6 +1251,14 @@
 				"verror": "1.10.0"
 			}
 		},
+		"node_modules/kareem": {
+			"version": "2.6.3",
+			"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
+			"integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
+			"engines": {
+				"node": ">=12.0.0"
+			}
+		},
 		"node_modules/lodash": {
 			"version": "4.17.21",
 			"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -1235,6 +1272,11 @@
 				"node": ">= 0.4"
 			}
 		},
+		"node_modules/memory-pager": {
+			"version": "1.5.0",
+			"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
+			"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
+		},
 		"node_modules/mime": {
 			"version": "1.6.0",
 			"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@@ -1265,6 +1307,116 @@
 				"node": ">= 0.6"
 			}
 		},
+		"node_modules/mongodb": {
+			"version": "6.15.0",
+			"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.15.0.tgz",
+			"integrity": "sha512-ifBhQ0rRzHDzqp9jAQP6OwHSH7dbYIQjD3SbJs9YYk9AikKEettW/9s/tbSFDTpXcRbF+u1aLrhHxDFaYtZpFQ==",
+			"dependencies": {
+				"@mongodb-js/saslprep": "^1.1.9",
+				"bson": "^6.10.3",
+				"mongodb-connection-string-url": "^3.0.0"
+			},
+			"engines": {
+				"node": ">=16.20.1"
+			},
+			"peerDependencies": {
+				"@aws-sdk/credential-providers": "^3.188.0",
+				"@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
+				"gcp-metadata": "^5.2.0",
+				"kerberos": "^2.0.1",
+				"mongodb-client-encryption": ">=6.0.0 <7",
+				"snappy": "^7.2.2",
+				"socks": "^2.7.1"
+			},
+			"peerDependenciesMeta": {
+				"@aws-sdk/credential-providers": {
+					"optional": true
+				},
+				"@mongodb-js/zstd": {
+					"optional": true
+				},
+				"gcp-metadata": {
+					"optional": true
+				},
+				"kerberos": {
+					"optional": true
+				},
+				"mongodb-client-encryption": {
+					"optional": true
+				},
+				"snappy": {
+					"optional": true
+				},
+				"socks": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/mongodb-connection-string-url": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
+			"integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
+			"dependencies": {
+				"@types/whatwg-url": "^11.0.2",
+				"whatwg-url": "^14.1.0 || ^13.0.0"
+			}
+		},
+		"node_modules/mongoose": {
+			"version": "8.13.2",
+			"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.13.2.tgz",
+			"integrity": "sha512-riCBqZmNkYBWjXpM3qWLDQw7QmTKsVZDPhLXFJqC87+OjocEVpvS3dA2BPPUiLAu+m0/QmEj5pSXKhH+/DgerQ==",
+			"dependencies": {
+				"bson": "^6.10.3",
+				"kareem": "2.6.3",
+				"mongodb": "~6.15.0",
+				"mpath": "0.9.0",
+				"mquery": "5.0.0",
+				"ms": "2.1.3",
+				"sift": "17.1.3"
+			},
+			"engines": {
+				"node": ">=16.20.1"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/mongoose"
+			}
+		},
+		"node_modules/mpath": {
+			"version": "0.9.0",
+			"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
+			"integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
+			"engines": {
+				"node": ">=4.0.0"
+			}
+		},
+		"node_modules/mquery": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
+			"integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
+			"dependencies": {
+				"debug": "4.x"
+			},
+			"engines": {
+				"node": ">=14.0.0"
+			}
+		},
+		"node_modules/mquery/node_modules/debug": {
+			"version": "4.4.0",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+			"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+			"dependencies": {
+				"ms": "^2.1.3"
+			},
+			"engines": {
+				"node": ">=6.0"
+			},
+			"peerDependenciesMeta": {
+				"supports-color": {
+					"optional": true
+				}
+			}
+		},
 		"node_modules/ms": {
 			"version": "2.1.3",
 			"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -1805,6 +1957,19 @@
 				"url": "https://github.com/sponsors/ljharb"
 			}
 		},
+		"node_modules/sift": {
+			"version": "17.1.3",
+			"resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
+			"integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ=="
+		},
+		"node_modules/sparse-bitfield": {
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+			"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
+			"dependencies": {
+				"memory-pager": "^1.0.2"
+			}
+		},
 		"node_modules/sshpk": {
 			"version": "1.18.0",
 			"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
@@ -1930,6 +2095,17 @@
 				"node": ">=16"
 			}
 		},
+		"node_modules/tr46": {
+			"version": "5.1.1",
+			"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+			"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+			"dependencies": {
+				"punycode": "^2.3.1"
+			},
+			"engines": {
+				"node": ">=18"
+			}
+		},
 		"node_modules/tunnel-agent": {
 			"version": "0.6.0",
 			"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -2090,6 +2266,26 @@
 			"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
 			"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
 		},
+		"node_modules/webidl-conversions": {
+			"version": "7.0.0",
+			"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+			"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/whatwg-url": {
+			"version": "14.2.0",
+			"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+			"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+			"dependencies": {
+				"tr46": "^5.1.0",
+				"webidl-conversions": "^7.0.0"
+			},
+			"engines": {
+				"node": ">=18"
+			}
+		},
 		"node_modules/which-boxed-primitive": {
 			"version": "1.1.1",
 			"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
diff --git a/package.json b/package.json
index 98b715d..46db50a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,7 @@
 {
 	"dependencies": {
 		"dotenv": "^16.5.0",
+		"mongoose": "^8.13.2",
 		"node-telegram-bot-api": "^0.66.0"
 	},
 	"name": "telegram-bot-api",