ts-game-decorators 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/redis.d.ts +7 -0
- package/dist/config/redis.js +126 -0
- package/dist/db/couchbaseClient.d.ts +4 -0
- package/dist/db/couchbaseClient.js +52 -0
- package/dist/db/lowdbClient.d.ts +5 -0
- package/dist/db/lowdbClient.js +8 -0
- package/dist/decorators/autoRouter.d.ts +7 -0
- package/dist/decorators/autoRouter.js +69 -0
- package/dist/decorators/initServer.d.ts +19 -0
- package/dist/decorators/initServer.js +87 -0
- package/dist/decorators/socketDecorators.d.ts +8 -0
- package/dist/decorators/socketDecorators.js +94 -0
- package/dist/middleware/auth.d.ts +4 -0
- package/dist/middleware/auth.js +61 -0
- package/dist/types/index.d.ts +22 -0
- package/dist/types/index.js +2 -0
- package/dist/types/map.d.ts +45 -0
- package/dist/types/map.js +12 -0
- package/dist/utils/collisionDetector.d.ts +35 -0
- package/dist/utils/collisionDetector.js +138 -0
- package/dist/utils/discordConnector.d.ts +1 -0
- package/dist/utils/discordConnector.js +89 -0
- package/dist/utils/teleConnector.d.ts +4 -0
- package/dist/utils/teleConnector.js +78 -0
- package/dist/utils/utils.d.ts +5 -0
- package/dist/utils/utils.js +39 -0
- package/package.json +32 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.closeRedisConnections = exports.getRedisClients = exports.getRedisAdapter = exports.createRedisAdapter = void 0;
|
|
7
|
+
const redis_1 = require("redis");
|
|
8
|
+
const redis_adapter_1 = require("@socket.io/redis-adapter");
|
|
9
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
10
|
+
dotenv_1.default.config();
|
|
11
|
+
const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379';
|
|
12
|
+
const REDIS_USERNAME = process.env.REDIS_USERNAME;
|
|
13
|
+
const REDIS_PASSWORD = process.env.REDIS_PASSWORD;
|
|
14
|
+
// Redis client instances for reuse
|
|
15
|
+
let pubClient = null;
|
|
16
|
+
let subClient = null;
|
|
17
|
+
let redisAdapter = null;
|
|
18
|
+
const createRedisAdapter = async () => {
|
|
19
|
+
try {
|
|
20
|
+
console.log('🔗 Connecting to Redis...');
|
|
21
|
+
// Build Redis client configuration
|
|
22
|
+
const redisConfig = {
|
|
23
|
+
url: REDIS_URL,
|
|
24
|
+
socket: {
|
|
25
|
+
reconnectStrategy: (retries) => {
|
|
26
|
+
if (retries > 10) {
|
|
27
|
+
console.error('❌ Redis: Too many retries, giving up');
|
|
28
|
+
return new Error('Too many retries');
|
|
29
|
+
}
|
|
30
|
+
console.log(`🔄 Redis: Retrying connection (${retries}/10)`);
|
|
31
|
+
return Math.min(retries * 100, 3000);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
// Only add authentication if credentials are provided
|
|
36
|
+
if (REDIS_USERNAME) {
|
|
37
|
+
redisConfig.username = REDIS_USERNAME;
|
|
38
|
+
}
|
|
39
|
+
if (REDIS_PASSWORD) {
|
|
40
|
+
redisConfig.password = REDIS_PASSWORD;
|
|
41
|
+
}
|
|
42
|
+
console.log(`🔗 Redis config: ${REDIS_URL} ${REDIS_USERNAME ? `(username: ${REDIS_USERNAME})` : ''} ${REDIS_PASSWORD ? '(password: ***)' : '(no auth)'}`);
|
|
43
|
+
// Create publisher client
|
|
44
|
+
pubClient = (0, redis_1.createClient)(redisConfig);
|
|
45
|
+
// Create subscriber client (duplicate of publisher)
|
|
46
|
+
subClient = pubClient.duplicate();
|
|
47
|
+
// Error handling for publisher
|
|
48
|
+
pubClient.on('error', (err) => {
|
|
49
|
+
console.error('❌ Redis Publisher Error:', err);
|
|
50
|
+
});
|
|
51
|
+
pubClient.on('connect', () => {
|
|
52
|
+
console.log('✅ Redis Publisher connected');
|
|
53
|
+
});
|
|
54
|
+
pubClient.on('reconnecting', () => {
|
|
55
|
+
console.log('🔄 Redis Publisher reconnecting...');
|
|
56
|
+
});
|
|
57
|
+
// Error handling for subscriber
|
|
58
|
+
subClient.on('error', (err) => {
|
|
59
|
+
console.error('❌ Redis Subscriber Error:', err);
|
|
60
|
+
});
|
|
61
|
+
subClient.on('connect', () => {
|
|
62
|
+
console.log('✅ Redis Subscriber connected');
|
|
63
|
+
});
|
|
64
|
+
subClient.on('reconnecting', () => {
|
|
65
|
+
console.log('🔄 Redis Subscriber reconnecting...');
|
|
66
|
+
});
|
|
67
|
+
// Connect both clients
|
|
68
|
+
await Promise.all([pubClient.connect(), subClient.connect()]);
|
|
69
|
+
// Create Socket.IO Redis adapter
|
|
70
|
+
redisAdapter = (0, redis_adapter_1.createAdapter)(pubClient, subClient, {
|
|
71
|
+
key: 'socket.io',
|
|
72
|
+
requestsTimeout: 5000
|
|
73
|
+
});
|
|
74
|
+
console.log('✅ Redis adapter created successfully');
|
|
75
|
+
return redisAdapter;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error('❌ Failed to create Redis adapter:', error);
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
exports.createRedisAdapter = createRedisAdapter;
|
|
83
|
+
// Function to get Redis adapter instance
|
|
84
|
+
const getRedisAdapter = () => {
|
|
85
|
+
return redisAdapter;
|
|
86
|
+
};
|
|
87
|
+
exports.getRedisAdapter = getRedisAdapter;
|
|
88
|
+
// Function to get Redis clients for direct operations
|
|
89
|
+
const getRedisClients = () => {
|
|
90
|
+
return { pubClient, subClient };
|
|
91
|
+
};
|
|
92
|
+
exports.getRedisClients = getRedisClients;
|
|
93
|
+
// Graceful shutdown for Redis connections
|
|
94
|
+
const closeRedisConnections = async () => {
|
|
95
|
+
try {
|
|
96
|
+
console.log('🛑 Closing Redis connections...');
|
|
97
|
+
if (pubClient && pubClient.isOpen) {
|
|
98
|
+
try {
|
|
99
|
+
await pubClient.quit();
|
|
100
|
+
console.log('✅ Redis Publisher connection closed');
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.error('❌ Error closing Redis Publisher connection:', error);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (pubClient) {
|
|
107
|
+
console.log('⚠️ Redis Publisher was already closed');
|
|
108
|
+
}
|
|
109
|
+
if (subClient && subClient.isOpen) {
|
|
110
|
+
try {
|
|
111
|
+
await subClient.quit();
|
|
112
|
+
console.log('✅ Redis Subscriber connection closed');
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error('❌ Error closing Redis Subscriber connection:', error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else if (subClient) {
|
|
119
|
+
console.log('⚠️ Redis Subscriber was already closed');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
console.error('❌ Error closing Redis connections:', error);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
exports.closeRedisConnections = closeRedisConnections;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.connectToCouchbase = connectToCouchbase;
|
|
7
|
+
exports.queryData = queryData;
|
|
8
|
+
exports.getCouchbaseUsersCollection = getCouchbaseUsersCollection;
|
|
9
|
+
const couchbase_1 = require("couchbase");
|
|
10
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
11
|
+
dotenv_1.default.config();
|
|
12
|
+
let cluster;
|
|
13
|
+
let bucket;
|
|
14
|
+
let dataCollection;
|
|
15
|
+
async function connectToCouchbase() {
|
|
16
|
+
try {
|
|
17
|
+
// Kết nối với Couchbase Cluster
|
|
18
|
+
cluster = await couchbase_1.Cluster.connect(`couchbase://${process.env.COUCHBASE_URL || '165.22.240.52'}`, {
|
|
19
|
+
username: process.env.COUCHBASE_USERNAME || 'nhat.huy.7996@gmail.com',
|
|
20
|
+
password: process.env.COUCHBASE_PASSWORD || 'oc3qKtHXELXL',
|
|
21
|
+
});
|
|
22
|
+
// Lấy bucket từ cluster
|
|
23
|
+
bucket = cluster.bucket(process.env.COUCHBASE_BUCKET || 'buildAnArmy');
|
|
24
|
+
//Get collection
|
|
25
|
+
dataCollection = bucket.collection('users');
|
|
26
|
+
console.log('✅ Connected to Couchbase');
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
console.error('❌ Failed to connect to Couchbase:', err);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/*
|
|
33
|
+
* query Data from couchbase
|
|
34
|
+
* @param query: string
|
|
35
|
+
* @returns array of rows
|
|
36
|
+
*/
|
|
37
|
+
async function queryData(query) {
|
|
38
|
+
try {
|
|
39
|
+
const result = await cluster.query(query);
|
|
40
|
+
return result.rows; // Return array of rows
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
console.error('Error executing query:', err);
|
|
44
|
+
throw new Error('Failed to fetch top users');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function getCouchbaseUsersCollection() {
|
|
48
|
+
if (!dataCollection) {
|
|
49
|
+
throw new Error('Couchbase is not connected. Call connectToCouchbase first.');
|
|
50
|
+
}
|
|
51
|
+
return dataCollection;
|
|
52
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.db = void 0;
|
|
4
|
+
const lowdb_1 = require("lowdb");
|
|
5
|
+
const node_1 = require("lowdb/node");
|
|
6
|
+
// File lưu dữ liệu
|
|
7
|
+
const adapter = new node_1.JSONFile("db.json");
|
|
8
|
+
exports.db = new lowdb_1.Low(adapter, { users: [] });
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { Express } from 'express';
|
|
3
|
+
export declare function RouterController(basePath?: string): ClassDecorator;
|
|
4
|
+
export declare function Get(path: string): MethodDecorator;
|
|
5
|
+
export declare function Post(path: string): MethodDecorator;
|
|
6
|
+
export declare function Authen(): MethodDecorator & ClassDecorator;
|
|
7
|
+
export declare function registerRoutes(app: Express, authMiddleware: any, ...controllers: any[]): void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RouterController = RouterController;
|
|
4
|
+
exports.Get = Get;
|
|
5
|
+
exports.Post = Post;
|
|
6
|
+
exports.Authen = Authen;
|
|
7
|
+
exports.registerRoutes = registerRoutes;
|
|
8
|
+
require("reflect-metadata");
|
|
9
|
+
const express_1 = require("express");
|
|
10
|
+
const ROUTER_META = Symbol('router');
|
|
11
|
+
const ROUTES_META = Symbol('routes');
|
|
12
|
+
const AUTHEN_META = Symbol('authen');
|
|
13
|
+
function RouterController(basePath = '') {
|
|
14
|
+
return (target) => {
|
|
15
|
+
Reflect.defineMetadata(ROUTER_META, basePath, target);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function Get(path) {
|
|
19
|
+
return (target, propertyKey) => {
|
|
20
|
+
const routes = Reflect.getMetadata(ROUTES_META, target.constructor) || [];
|
|
21
|
+
routes.push({ method: 'get', path, handler: propertyKey });
|
|
22
|
+
Reflect.defineMetadata(ROUTES_META, routes, target.constructor);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function Post(path) {
|
|
26
|
+
return (target, propertyKey) => {
|
|
27
|
+
const routes = Reflect.getMetadata(ROUTES_META, target.constructor) || [];
|
|
28
|
+
routes.push({ method: 'post', path, handler: propertyKey });
|
|
29
|
+
Reflect.defineMetadata(ROUTES_META, routes, target.constructor);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function Authen() {
|
|
33
|
+
return (target, propertyKey) => {
|
|
34
|
+
if (propertyKey) {
|
|
35
|
+
// Method
|
|
36
|
+
const authenMethods = Reflect.getMetadata(AUTHEN_META, target.constructor) || [];
|
|
37
|
+
authenMethods.push(propertyKey);
|
|
38
|
+
Reflect.defineMetadata(AUTHEN_META, authenMethods, target.constructor);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Class
|
|
42
|
+
Reflect.defineMetadata(AUTHEN_META, true, target);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function registerRoutes(app, authMiddleware, ...controllers) {
|
|
47
|
+
controllers.forEach(Controller => {
|
|
48
|
+
const basePath = Reflect.getMetadata(ROUTER_META, Controller) || '';
|
|
49
|
+
const routes = Reflect.getMetadata(ROUTES_META, Controller) || [];
|
|
50
|
+
const classAuthen = Reflect.getMetadata(AUTHEN_META, Controller);
|
|
51
|
+
const instance = new Controller();
|
|
52
|
+
const router = (0, express_1.Router)();
|
|
53
|
+
routes.forEach((route) => {
|
|
54
|
+
const handler = (req, res, next) => {
|
|
55
|
+
Promise.resolve(instance[route.handler](req, res, next)).catch(next);
|
|
56
|
+
};
|
|
57
|
+
const methodAuthenArr = Reflect.getMetadata(AUTHEN_META, Controller) || [];
|
|
58
|
+
const methodAuthen = Array.isArray(methodAuthenArr) && methodAuthenArr.includes(route.handler);
|
|
59
|
+
const routeMethod = route.method;
|
|
60
|
+
if (classAuthen || methodAuthen) {
|
|
61
|
+
router[routeMethod](route.path, authMiddleware, handler);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
router[routeMethod](route.path, handler);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
app.use(basePath, router);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Express } from 'express';
|
|
2
|
+
import { Server as HTTPServer } from 'http';
|
|
3
|
+
import { Server as SocketIOServer } from 'socket.io';
|
|
4
|
+
export interface InitOptions {
|
|
5
|
+
port: number;
|
|
6
|
+
apiControllers?: any[];
|
|
7
|
+
socketServices?: any[];
|
|
8
|
+
authAPIMiddleware?: any;
|
|
9
|
+
onReady?: (app: Express, io: SocketIOServer, httpServer: HTTPServer) => void;
|
|
10
|
+
publicPath?: string;
|
|
11
|
+
expressConfig?: (app: Express) => void;
|
|
12
|
+
socketConfig?: (io: SocketIOServer) => void;
|
|
13
|
+
createRedisAdapter?: () => Promise<any>;
|
|
14
|
+
}
|
|
15
|
+
export declare function initServer(options: InitOptions): Promise<{
|
|
16
|
+
app: import("express-serve-static-core").Express;
|
|
17
|
+
io: SocketIOServer<import("socket.io").DefaultEventsMap, import("socket.io").DefaultEventsMap, import("socket.io").DefaultEventsMap, any>;
|
|
18
|
+
httpServer: HTTPServer;
|
|
19
|
+
}>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.initServer = initServer;
|
|
7
|
+
const express_1 = __importDefault(require("express"));
|
|
8
|
+
const http_1 = require("http");
|
|
9
|
+
const socket_io_1 = require("socket.io");
|
|
10
|
+
const autoRouter_1 = require("./autoRouter");
|
|
11
|
+
const socketDecorators_1 = require("./socketDecorators");
|
|
12
|
+
async function initServer(options) {
|
|
13
|
+
const app = (0, express_1.default)();
|
|
14
|
+
const httpServer = (0, http_1.createServer)(app);
|
|
15
|
+
const io = new socket_io_1.Server(httpServer, {
|
|
16
|
+
cors: {
|
|
17
|
+
origin: '*',
|
|
18
|
+
methods: ['GET', 'POST']
|
|
19
|
+
},
|
|
20
|
+
transports: ['websocket', 'polling'],
|
|
21
|
+
allowUpgrades: true,
|
|
22
|
+
connectTimeout: 45000,
|
|
23
|
+
pingTimeout: 60000,
|
|
24
|
+
pingInterval: 25000,
|
|
25
|
+
cookie: {
|
|
26
|
+
name: 'io',
|
|
27
|
+
httpOnly: true,
|
|
28
|
+
secure: true,
|
|
29
|
+
sameSite: 'none',
|
|
30
|
+
maxAge: 24 * 60 * 60 * 1000
|
|
31
|
+
},
|
|
32
|
+
connectionStateRecovery: {
|
|
33
|
+
maxDisconnectionDuration: 2 * 60 * 1000,
|
|
34
|
+
skipMiddlewares: true,
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
// Redis adapter support
|
|
38
|
+
if (options.createRedisAdapter) {
|
|
39
|
+
try {
|
|
40
|
+
const redisAdapter = await options.createRedisAdapter();
|
|
41
|
+
if (redisAdapter) {
|
|
42
|
+
io.adapter(redisAdapter);
|
|
43
|
+
console.log('✅ Socket.IO Redis adapter configured');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
console.warn('⚠️ Redis adapter failed to initialize, using default adapter:', err);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Express config
|
|
51
|
+
app.use(express_1.default.json());
|
|
52
|
+
app.use((req, res, next) => {
|
|
53
|
+
res.header('Access-Control-Allow-Origin', '*');
|
|
54
|
+
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
|
|
55
|
+
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
56
|
+
res.header('Access-Control-Allow-Credentials', 'true');
|
|
57
|
+
next();
|
|
58
|
+
});
|
|
59
|
+
if (options.publicPath) {
|
|
60
|
+
app.use(express_1.default.static(options.publicPath));
|
|
61
|
+
}
|
|
62
|
+
if (options.expressConfig) {
|
|
63
|
+
options.expressConfig(app);
|
|
64
|
+
}
|
|
65
|
+
// Register API controllers
|
|
66
|
+
if (options.apiControllers && options.apiControllers.length > 0) {
|
|
67
|
+
(0, autoRouter_1.registerRoutes)(app, options.authAPIMiddleware, ...options.apiControllers);
|
|
68
|
+
}
|
|
69
|
+
// Register socket services
|
|
70
|
+
if (options.socketServices && options.socketServices.length > 0) {
|
|
71
|
+
(0, socketDecorators_1.registerSocketServices)(io, ...options.socketServices);
|
|
72
|
+
}
|
|
73
|
+
if (options.socketConfig) {
|
|
74
|
+
options.socketConfig(io);
|
|
75
|
+
}
|
|
76
|
+
// Error handling
|
|
77
|
+
app.use((err, req, res, next) => {
|
|
78
|
+
console.error('Error:', err);
|
|
79
|
+
res.status(500).json({ error: 'Something went wrong!', details: err.message });
|
|
80
|
+
});
|
|
81
|
+
httpServer.listen(options.port, () => {
|
|
82
|
+
console.log(`Server is running on http://localhost:${options.port}`);
|
|
83
|
+
if (options.onReady)
|
|
84
|
+
options.onReady(app, io, httpServer);
|
|
85
|
+
});
|
|
86
|
+
return { app, io, httpServer };
|
|
87
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { Server } from 'socket.io';
|
|
3
|
+
export declare function AuthenSocket(): MethodDecorator & ClassDecorator;
|
|
4
|
+
export declare function SocketService(): ClassDecorator;
|
|
5
|
+
export declare function OnEvent(event: string): MethodDecorator;
|
|
6
|
+
export declare function OnDisconnect(): MethodDecorator;
|
|
7
|
+
export declare function OnError(): MethodDecorator;
|
|
8
|
+
export declare function registerSocketServices(io: Server, ...serviceClasses: any[]): void;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuthenSocket = AuthenSocket;
|
|
4
|
+
exports.SocketService = SocketService;
|
|
5
|
+
exports.OnEvent = OnEvent;
|
|
6
|
+
exports.OnDisconnect = OnDisconnect;
|
|
7
|
+
exports.OnError = OnError;
|
|
8
|
+
exports.registerSocketServices = registerSocketServices;
|
|
9
|
+
require("reflect-metadata");
|
|
10
|
+
const SOCKET_SERVICE_META = Symbol('socket_service');
|
|
11
|
+
const SOCKET_EVENTS_META = Symbol('socket_events');
|
|
12
|
+
const SOCKET_AUTHEN_META = Symbol('socket_authen');
|
|
13
|
+
// Decorator cho xác thực socket
|
|
14
|
+
function AuthenSocket() {
|
|
15
|
+
return (target, propertyKey) => {
|
|
16
|
+
if (propertyKey) {
|
|
17
|
+
// Method
|
|
18
|
+
const authenMethods = Reflect.getMetadata(SOCKET_AUTHEN_META, target.constructor) || [];
|
|
19
|
+
authenMethods.push(propertyKey);
|
|
20
|
+
Reflect.defineMetadata(SOCKET_AUTHEN_META, authenMethods, target.constructor);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
// Class
|
|
24
|
+
Reflect.defineMetadata(SOCKET_AUTHEN_META, true, target);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function SocketService() {
|
|
29
|
+
return (target) => {
|
|
30
|
+
Reflect.defineMetadata(SOCKET_SERVICE_META, true, target);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function OnEvent(event) {
|
|
34
|
+
return (target, propertyKey) => {
|
|
35
|
+
const events = Reflect.getMetadata(SOCKET_EVENTS_META, target.constructor) || [];
|
|
36
|
+
events.push({ type: 'event', event, handler: propertyKey });
|
|
37
|
+
Reflect.defineMetadata(SOCKET_EVENTS_META, events, target.constructor);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function OnDisconnect() {
|
|
41
|
+
return (target, propertyKey) => {
|
|
42
|
+
const events = Reflect.getMetadata(SOCKET_EVENTS_META, target.constructor) || [];
|
|
43
|
+
events.push({ type: 'disconnect', handler: propertyKey });
|
|
44
|
+
Reflect.defineMetadata(SOCKET_EVENTS_META, events, target.constructor);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function OnError() {
|
|
48
|
+
return (target, propertyKey) => {
|
|
49
|
+
const events = Reflect.getMetadata(SOCKET_EVENTS_META, target.constructor) || [];
|
|
50
|
+
events.push({ type: 'error', handler: propertyKey });
|
|
51
|
+
Reflect.defineMetadata(SOCKET_EVENTS_META, events, target.constructor);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function registerSocketServices(io, ...serviceClasses) {
|
|
55
|
+
// Import middleware xác thực socket
|
|
56
|
+
const { authSocketToken } = require('../middleware/auth');
|
|
57
|
+
io.on('connection', (socket) => {
|
|
58
|
+
serviceClasses.forEach(ServiceClass => {
|
|
59
|
+
if (!Reflect.getMetadata(SOCKET_SERVICE_META, ServiceClass))
|
|
60
|
+
return;
|
|
61
|
+
const instance = new ServiceClass();
|
|
62
|
+
const events = Reflect.getMetadata(SOCKET_EVENTS_META, ServiceClass) || [];
|
|
63
|
+
const classAuthen = Reflect.getMetadata(SOCKET_AUTHEN_META, ServiceClass);
|
|
64
|
+
const methodAuthenArr = Reflect.getMetadata(SOCKET_AUTHEN_META, ServiceClass) || [];
|
|
65
|
+
events.forEach((evt) => {
|
|
66
|
+
const methodAuthen = Array.isArray(methodAuthenArr) && methodAuthenArr.includes(evt.handler);
|
|
67
|
+
const needAuth = classAuthen || methodAuthen;
|
|
68
|
+
const handler = (...args) => {
|
|
69
|
+
Promise.resolve(instance[evt.handler](socket, ...args)).catch(console.error);
|
|
70
|
+
};
|
|
71
|
+
if (evt.type === 'event') {
|
|
72
|
+
socket.on(evt.event, (...args) => {
|
|
73
|
+
if (needAuth) {
|
|
74
|
+
authSocketToken(socket, (err) => {
|
|
75
|
+
if (err)
|
|
76
|
+
return socket.emit('server:error', err.message);
|
|
77
|
+
handler(...args);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
handler(...args);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else if (evt.type === 'disconnect') {
|
|
86
|
+
socket.on('disconnect', (...args) => handler(...args));
|
|
87
|
+
}
|
|
88
|
+
else if (evt.type === 'error') {
|
|
89
|
+
socket.on('error', (...args) => handler(...args));
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Response, NextFunction } from 'express';
|
|
2
|
+
import { AuthenticatedRequest, AuthenticatedSocket } from '../types';
|
|
3
|
+
export declare const authAPIToken: (req: AuthenticatedRequest, res: Response, next: NextFunction) => void | Response<any, Record<string, any>>;
|
|
4
|
+
export declare const authSocketToken: (socket: AuthenticatedSocket, next: (err?: Error) => void) => void;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.authSocketToken = exports.authAPIToken = void 0;
|
|
4
|
+
const utils_1 = require("../utils/utils");
|
|
5
|
+
const authAPIToken = (req, res, next) => {
|
|
6
|
+
// Skip authentication for OPTIONS requests (CORS preflight)
|
|
7
|
+
if (req.method === 'OPTIONS') {
|
|
8
|
+
console.log('🔄 Skipping auth for OPTIONS request (CORS preflight)');
|
|
9
|
+
return next();
|
|
10
|
+
}
|
|
11
|
+
console.log('🔍 Request method:', req.method);
|
|
12
|
+
console.log('🔍 Request URL:', req.url);
|
|
13
|
+
const auth = req.headers.authorization; // "Bearer <token>"
|
|
14
|
+
const token = (auth === null || auth === void 0 ? void 0 : auth.startsWith("Bearer ")) ? auth.slice(7) : undefined;
|
|
15
|
+
console.log('🔑 API authentication token:', token);
|
|
16
|
+
if (!token) {
|
|
17
|
+
return res.status(401).json({
|
|
18
|
+
success: false,
|
|
19
|
+
message: 'No token provided'
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const decoded = utils_1.utils.tokenDecode(token);
|
|
24
|
+
req.userId = decoded.userId;
|
|
25
|
+
console.log('✅ API authenticated successfully for walletId:', decoded.walletId);
|
|
26
|
+
next();
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error(error);
|
|
30
|
+
return res.status(403).json({
|
|
31
|
+
success: false,
|
|
32
|
+
message: error
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
exports.authAPIToken = authAPIToken;
|
|
37
|
+
const authSocketToken = (socket, next) => {
|
|
38
|
+
console.log('🔐 Socket authentication middleware triggered for socket:', socket.id);
|
|
39
|
+
try {
|
|
40
|
+
var token = socket.handshake.auth.token;
|
|
41
|
+
console.log(socket.handshake);
|
|
42
|
+
console.log(`🔑 Socket authentication token: ${token ? 'Present' : 'Missing'}`);
|
|
43
|
+
// Temporarily allow connections without token for debugging
|
|
44
|
+
if (!token) {
|
|
45
|
+
console.log('⚠️ No token provided, but allowing connection for debugging');
|
|
46
|
+
token = socket.handshake.query.token;
|
|
47
|
+
}
|
|
48
|
+
const decoded = utils_1.utils.tokenDecode(token);
|
|
49
|
+
console.log(decoded);
|
|
50
|
+
socket.userId = decoded.userId;
|
|
51
|
+
next();
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error('❌ Socket authentication error:', error);
|
|
55
|
+
// Temporarily allow connection even with invalid token for debugging
|
|
56
|
+
console.log('⚠️ Invalid token, but allowing connection for debugging');
|
|
57
|
+
socket.userId = 'anonymous';
|
|
58
|
+
next();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
exports.authSocketToken = authSocketToken;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Socket } from 'socket.io';
|
|
2
|
+
import { Request } from 'express';
|
|
3
|
+
export interface ApiResponse {
|
|
4
|
+
success: boolean;
|
|
5
|
+
message: string;
|
|
6
|
+
data?: any;
|
|
7
|
+
}
|
|
8
|
+
export interface AuthenticatedSocket extends Socket {
|
|
9
|
+
userId?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface AuthenticatedRequest extends Request {
|
|
12
|
+
userId?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface Vector3 {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
z: number;
|
|
18
|
+
}
|
|
19
|
+
export interface Vector2 {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Vector3 } from ".";
|
|
2
|
+
/**
|
|
3
|
+
* Loại hình dạng của obstacle
|
|
4
|
+
*/
|
|
5
|
+
export declare enum ObstacleShape {
|
|
6
|
+
BOX = "box",
|
|
7
|
+
CYLINDER = "cylinder"
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Obstacle (vật cản) trên map
|
|
11
|
+
*/
|
|
12
|
+
export interface Obstacle {
|
|
13
|
+
id: string;
|
|
14
|
+
shape: ObstacleShape;
|
|
15
|
+
position: Vector3;
|
|
16
|
+
size: Vector3;
|
|
17
|
+
rotation?: Vector3;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Bounding box để kiểm tra collision
|
|
21
|
+
*/
|
|
22
|
+
export interface BoundingBox {
|
|
23
|
+
min: Vector3;
|
|
24
|
+
max: Vector3;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Map configuration
|
|
28
|
+
*/
|
|
29
|
+
export interface MapData {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
width: number;
|
|
33
|
+
length: number;
|
|
34
|
+
height?: number;
|
|
35
|
+
obstacles: Obstacle[];
|
|
36
|
+
spawnPoints?: Vector3[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Collision check result
|
|
40
|
+
*/
|
|
41
|
+
export interface CollisionResult {
|
|
42
|
+
hasCollision: boolean;
|
|
43
|
+
obstacle?: Obstacle;
|
|
44
|
+
penetrationDepth?: number;
|
|
45
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Map and Obstacle types
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ObstacleShape = void 0;
|
|
5
|
+
/**
|
|
6
|
+
* Loại hình dạng của obstacle
|
|
7
|
+
*/
|
|
8
|
+
var ObstacleShape;
|
|
9
|
+
(function (ObstacleShape) {
|
|
10
|
+
ObstacleShape["BOX"] = "box";
|
|
11
|
+
ObstacleShape["CYLINDER"] = "cylinder";
|
|
12
|
+
})(ObstacleShape || (exports.ObstacleShape = ObstacleShape = {}));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Obstacle, CollisionResult, MapData } from '../types/map';
|
|
2
|
+
import { Vector3 } from '../types';
|
|
3
|
+
export declare class CollisionDetector {
|
|
4
|
+
private static mapsDirectory;
|
|
5
|
+
static loadMap(nameMap: string): MapData;
|
|
6
|
+
/**
|
|
7
|
+
* Kiểm tra va chạm giữa player và tất cả obstacles trên map
|
|
8
|
+
* @param playerPosition Vị trí player muốn di chuyển đến
|
|
9
|
+
* @param playerRadius Bán kính collision của player (dạng cylinder/capsule)
|
|
10
|
+
* @param playerHeight Chiều cao của player
|
|
11
|
+
* @param obstacles Danh sách obstacles trên map
|
|
12
|
+
* @returns CollisionResult chứa thông tin về va chạm
|
|
13
|
+
*/
|
|
14
|
+
static checkCollision(playerPosition: Vector3, playerRadius: number, playerHeight: number, obstacles: Obstacle[]): CollisionResult;
|
|
15
|
+
/**
|
|
16
|
+
* Kiểm tra va chạm giữa player (cylinder) và box obstacle
|
|
17
|
+
*/
|
|
18
|
+
private static checkPlayerVsBox;
|
|
19
|
+
/**
|
|
20
|
+
* Kiểm tra va chạm giữa player (cylinder) và cylinder obstacle
|
|
21
|
+
*/
|
|
22
|
+
private static checkPlayerVsCylinder;
|
|
23
|
+
/**
|
|
24
|
+
* Tính bounding box của obstacle
|
|
25
|
+
*/
|
|
26
|
+
private static getObstacleBoundingBox;
|
|
27
|
+
/**
|
|
28
|
+
* Kiểm tra xem position có nằm trong giới hạn map không
|
|
29
|
+
*/
|
|
30
|
+
static isInMapBounds(position: Vector3, mapWidth: number, mapLength: number): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Clamp position vào trong giới hạn map
|
|
33
|
+
*/
|
|
34
|
+
static clampToMapBounds(position: Vector3, mapWidth: number, mapLength: number): Vector3;
|
|
35
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CollisionDetector = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Collision Detection Utility
|
|
9
|
+
* Xử lý việc kiểm tra va chạm giữa player và obstacles
|
|
10
|
+
*/
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const map_1 = require("../types/map");
|
|
14
|
+
class CollisionDetector {
|
|
15
|
+
static loadMap(nameMap) {
|
|
16
|
+
const filePath = path_1.default.join(this.mapsDirectory, `${nameMap}.json`);
|
|
17
|
+
console.log(`[MapLoader] Loading map from: ${filePath}`);
|
|
18
|
+
const jsonData = fs_1.default.readFileSync(filePath, 'utf8');
|
|
19
|
+
const mapData = JSON.parse(jsonData);
|
|
20
|
+
console.log(`[MapLoader] Loaded map: ${mapData.name} (ID: ${mapData.id})`);
|
|
21
|
+
return mapData;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Kiểm tra va chạm giữa player và tất cả obstacles trên map
|
|
25
|
+
* @param playerPosition Vị trí player muốn di chuyển đến
|
|
26
|
+
* @param playerRadius Bán kính collision của player (dạng cylinder/capsule)
|
|
27
|
+
* @param playerHeight Chiều cao của player
|
|
28
|
+
* @param obstacles Danh sách obstacles trên map
|
|
29
|
+
* @returns CollisionResult chứa thông tin về va chạm
|
|
30
|
+
*/
|
|
31
|
+
static checkCollision(playerPosition, playerRadius, playerHeight, obstacles) {
|
|
32
|
+
for (const obstacle of obstacles) {
|
|
33
|
+
let hasCollision = false;
|
|
34
|
+
if (obstacle.shape === map_1.ObstacleShape.BOX) {
|
|
35
|
+
hasCollision = this.checkPlayerVsBox(playerPosition, playerRadius, playerHeight, obstacle);
|
|
36
|
+
}
|
|
37
|
+
else if (obstacle.shape === map_1.ObstacleShape.CYLINDER) {
|
|
38
|
+
hasCollision = this.checkPlayerVsCylinder(playerPosition, playerRadius, playerHeight, obstacle);
|
|
39
|
+
}
|
|
40
|
+
if (hasCollision) {
|
|
41
|
+
return {
|
|
42
|
+
hasCollision: true,
|
|
43
|
+
obstacle: obstacle
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { hasCollision: false };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Kiểm tra va chạm giữa player (cylinder) và box obstacle
|
|
51
|
+
*/
|
|
52
|
+
static checkPlayerVsBox(playerPos, playerRadius, playerHeight, obstacle) {
|
|
53
|
+
// Tính bounding box của obstacle
|
|
54
|
+
const obstacleBox = this.getObstacleBoundingBox(obstacle);
|
|
55
|
+
// Kiểm tra overlap trên trục Y (chiều cao)
|
|
56
|
+
const playerBottom = playerPos.y;
|
|
57
|
+
const playerTop = playerPos.y + playerHeight;
|
|
58
|
+
if (playerTop < obstacleBox.min.y || playerBottom > obstacleBox.max.y) {
|
|
59
|
+
return false; // Không va chạm theo chiều cao
|
|
60
|
+
}
|
|
61
|
+
// Kiểm tra va chạm 2D trên mặt phẳng XZ
|
|
62
|
+
// Tìm điểm gần nhất trên box với player position
|
|
63
|
+
const closestX = Math.max(obstacleBox.min.x, Math.min(playerPos.x, obstacleBox.max.x));
|
|
64
|
+
const closestZ = Math.max(obstacleBox.min.z, Math.min(playerPos.z, obstacleBox.max.z));
|
|
65
|
+
// Tính khoảng cách từ player đến điểm gần nhất
|
|
66
|
+
const distanceX = playerPos.x - closestX;
|
|
67
|
+
const distanceZ = playerPos.z - closestZ;
|
|
68
|
+
const distanceSquared = distanceX * distanceX + distanceZ * distanceZ;
|
|
69
|
+
// Va chạm nếu khoảng cách nhỏ hơn bán kính player
|
|
70
|
+
return distanceSquared < (playerRadius * playerRadius);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Kiểm tra va chạm giữa player (cylinder) và cylinder obstacle
|
|
74
|
+
*/
|
|
75
|
+
static checkPlayerVsCylinder(playerPos, playerRadius, playerHeight, obstacle) {
|
|
76
|
+
const obstacleRadius = obstacle.size.x; // Bán kính cylinder là size.x
|
|
77
|
+
const obstacleHeight = obstacle.size.y;
|
|
78
|
+
// Kiểm tra overlap trên trục Y
|
|
79
|
+
const playerBottom = playerPos.y;
|
|
80
|
+
const playerTop = playerPos.y + playerHeight;
|
|
81
|
+
const obstacleBottom = obstacle.position.y - obstacleHeight / 2;
|
|
82
|
+
const obstacleTop = obstacle.position.y + obstacleHeight / 2;
|
|
83
|
+
if (playerTop < obstacleBottom || playerBottom > obstacleTop) {
|
|
84
|
+
return false; // Không va chạm theo chiều cao
|
|
85
|
+
}
|
|
86
|
+
// Kiểm tra va chạm 2D giữa 2 circles trên mặt phẳng XZ
|
|
87
|
+
const distanceX = playerPos.x - obstacle.position.x;
|
|
88
|
+
const distanceZ = playerPos.z - obstacle.position.z;
|
|
89
|
+
const distanceSquared = distanceX * distanceX + distanceZ * distanceZ;
|
|
90
|
+
const minDistance = playerRadius + obstacleRadius;
|
|
91
|
+
return distanceSquared < (minDistance * minDistance);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Tính bounding box của obstacle
|
|
95
|
+
*/
|
|
96
|
+
static getObstacleBoundingBox(obstacle) {
|
|
97
|
+
const halfSize = {
|
|
98
|
+
x: obstacle.size.x / 2,
|
|
99
|
+
y: obstacle.size.y / 2,
|
|
100
|
+
z: obstacle.size.z / 2
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
min: {
|
|
104
|
+
x: obstacle.position.x - halfSize.x,
|
|
105
|
+
y: obstacle.position.y - halfSize.y,
|
|
106
|
+
z: obstacle.position.z - halfSize.z
|
|
107
|
+
},
|
|
108
|
+
max: {
|
|
109
|
+
x: obstacle.position.x + halfSize.x,
|
|
110
|
+
y: obstacle.position.y + halfSize.y,
|
|
111
|
+
z: obstacle.position.z + halfSize.z
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Kiểm tra xem position có nằm trong giới hạn map không
|
|
117
|
+
*/
|
|
118
|
+
static isInMapBounds(position, mapWidth, mapLength) {
|
|
119
|
+
const halfWidth = mapWidth / 2;
|
|
120
|
+
const halfLength = mapLength / 2;
|
|
121
|
+
return position.x >= -halfWidth && position.x <= halfWidth &&
|
|
122
|
+
position.z >= -halfLength && position.z <= halfLength;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Clamp position vào trong giới hạn map
|
|
126
|
+
*/
|
|
127
|
+
static clampToMapBounds(position, mapWidth, mapLength) {
|
|
128
|
+
const halfWidth = mapWidth / 2;
|
|
129
|
+
const halfLength = mapLength / 2;
|
|
130
|
+
return {
|
|
131
|
+
x: Math.max(-halfWidth, Math.min(halfWidth, position.x)),
|
|
132
|
+
y: position.y,
|
|
133
|
+
z: Math.max(-halfLength, Math.min(halfLength, position.z))
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
exports.CollisionDetector = CollisionDetector;
|
|
138
|
+
CollisionDetector.mapsDirectory = path_1.default.join(__dirname, '../../maps');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sendDiscordNotification(message: string): Promise<void>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.sendDiscordNotification = sendDiscordNotification;
|
|
7
|
+
const discord_js_1 = require("discord.js");
|
|
8
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
9
|
+
dotenv_1.default.config();
|
|
10
|
+
const token = process.env.DISCORD_TOKEN;
|
|
11
|
+
const channelId = process.env.CHANNEL_ID;
|
|
12
|
+
if (!token || !channelId) {
|
|
13
|
+
console.error("Vui lòng cung cấp DISCORD_TOKEN và CHANNEL_ID trong .env");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const client = new discord_js_1.Client({
|
|
17
|
+
intents: [discord_js_1.GatewayIntentBits.Guilds, discord_js_1.GatewayIntentBits.GuildMessages],
|
|
18
|
+
});
|
|
19
|
+
client.once(discord_js_1.Events.ClientReady, async () => {
|
|
20
|
+
var _a;
|
|
21
|
+
console.log(`Bot đã đăng nhập với tên: ${(_a = client.user) === null || _a === void 0 ? void 0 : _a.tag}`);
|
|
22
|
+
const channel = client.channels.cache.get(channelId);
|
|
23
|
+
if (!channel) {
|
|
24
|
+
console.error("Không tìm thấy kênh!");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// channel.send("Bot đã hoạt động! 🚀");
|
|
28
|
+
});
|
|
29
|
+
client.on(discord_js_1.Events.MessageCreate, async (message) => {
|
|
30
|
+
// Bỏ qua tin nhắn của chính bot
|
|
31
|
+
if (message.author.bot)
|
|
32
|
+
return;
|
|
33
|
+
// Kiểm tra bot có bị tag hay không
|
|
34
|
+
if (message.mentions.has(client.user)) {
|
|
35
|
+
console.log(`📩 Được tag bởi ${message.author.tag}: ${message.content}`);
|
|
36
|
+
if (message.content.toLowerCase().includes("game"))
|
|
37
|
+
await message.reply(`Chào ${message.author.username}! Game đây nhá!!\n
|
|
38
|
+
🤖 [Shadow Javelin](https://t.me/gamedevtoi_bot/shadow_javelin)\n
|
|
39
|
+
🍉 [Garden DHH](https://t.me/gamedevtoi_bot/gardenDHH)`);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
// Xử lý lỗi Discord client
|
|
43
|
+
client.on(discord_js_1.Events.Error, (error) => {
|
|
44
|
+
console.error('Discord client error:', error);
|
|
45
|
+
});
|
|
46
|
+
// Đăng nhập với xử lý lỗi
|
|
47
|
+
client.login(token).catch((error) => {
|
|
48
|
+
console.error('❌ Discord login failed:', error.message);
|
|
49
|
+
console.log('⚠️ Discord bot không khả dụng');
|
|
50
|
+
});
|
|
51
|
+
function Connect() {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
// Nếu đã ready thì resolve ngay
|
|
54
|
+
if (client.isReady()) {
|
|
55
|
+
resolve(client);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// Timeout sau 5 giây nếu không kết nối được
|
|
59
|
+
const timeout = setTimeout(() => {
|
|
60
|
+
clearInterval(check);
|
|
61
|
+
reject(new Error('Discord connection timeout'));
|
|
62
|
+
}, 5000);
|
|
63
|
+
const check = setInterval(() => {
|
|
64
|
+
if (client.isReady()) {
|
|
65
|
+
clearTimeout(timeout);
|
|
66
|
+
clearInterval(check);
|
|
67
|
+
resolve(client);
|
|
68
|
+
}
|
|
69
|
+
}, 100);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// Hàm gửi thông báo
|
|
73
|
+
async function sendDiscordNotification(message) {
|
|
74
|
+
try {
|
|
75
|
+
await Connect();
|
|
76
|
+
console.log(`Gửi tin đến server!`);
|
|
77
|
+
const channel = client.channels.cache.get(channelId);
|
|
78
|
+
if (channel) {
|
|
79
|
+
await channel.send(message);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.error("Không thể gửi tin nhắn, kênh không tồn tại!");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.error("❌ Không thể gửi Discord notification:", error instanceof Error ? error.message : error);
|
|
87
|
+
// Server vẫn tiếp tục chạy, chỉ log lỗi
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import TelegramBot from "node-telegram-bot-api";
|
|
2
|
+
export declare const botTele: TelegramBot;
|
|
3
|
+
export declare function sendMessage(message: string, userId?: string, thread_id?: string): Promise<void>;
|
|
4
|
+
export declare function sendMessageNoti(message: string): Promise<void>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.botTele = void 0;
|
|
7
|
+
exports.sendMessage = sendMessage;
|
|
8
|
+
exports.sendMessageNoti = sendMessageNoti;
|
|
9
|
+
const node_telegram_bot_api_1 = __importDefault(require("node-telegram-bot-api"));
|
|
10
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
11
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
12
|
+
dotenv_1.default.config();
|
|
13
|
+
const token = process.env.TELEGRAM_BOT_TOKEN;
|
|
14
|
+
const gameUrl = "https://t.me/gamedevtoi_bot/shadow_javelin";
|
|
15
|
+
if (!token) {
|
|
16
|
+
throw new Error("Missing TELEGRAM_BOT_TOKEN in .env file");
|
|
17
|
+
}
|
|
18
|
+
exports.botTele = new node_telegram_bot_api_1.default(token, { polling: true });
|
|
19
|
+
exports.botTele.on("inline_query", async (query) => {
|
|
20
|
+
const results = [
|
|
21
|
+
{
|
|
22
|
+
type: "article",
|
|
23
|
+
id: "1",
|
|
24
|
+
title: "PlayGame",
|
|
25
|
+
input_message_content: {
|
|
26
|
+
message_text: gameUrl,
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
];
|
|
30
|
+
exports.botTele.answerInlineQuery(query.id, results);
|
|
31
|
+
});
|
|
32
|
+
exports.botTele.onText(/\/games/, (msg) => {
|
|
33
|
+
exports.botTele.sendMessage(msg.chat.id, "Chào bạn! Đây là những game hiện có 🎮", {
|
|
34
|
+
reply_markup: {
|
|
35
|
+
keyboard: [[{ text: "🎮 Shadow Javelin" }, { text: "🍉 Garden DHH" }]],
|
|
36
|
+
resize_keyboard: true,
|
|
37
|
+
one_time_keyboard: false,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
exports.botTele.on("message", (msg) => {
|
|
42
|
+
var _a;
|
|
43
|
+
if (msg.text === "🎮 Shadow Javelin") {
|
|
44
|
+
exports.botTele.sendMessage(msg.chat.id, `🚀 Nhấn vào đây để chơi: [Shadow Javelin](${gameUrl})`, {
|
|
45
|
+
parse_mode: "Markdown",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (msg.text === "🍉 Garden DHH") {
|
|
49
|
+
exports.botTele.sendMessage(msg.chat.id, `🚀 Nhấn vào đây để chơi: [Garden DHH](https://t.me/gamedevtoi_bot/gardenDHH)`, {
|
|
50
|
+
parse_mode: "Markdown",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if ((_a = msg.text) === null || _a === void 0 ? void 0 : _a.startsWith("/noti")) {
|
|
54
|
+
//sendNotiAllUser(msg.text.replace("/noti", ""));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
async function sendMessage(message, userId = "", thread_id = "") {
|
|
58
|
+
try {
|
|
59
|
+
const url = `https://api.telegram.org/bot${token}/sendMessage`;
|
|
60
|
+
const response = await (0, node_fetch_1.default)(url, {
|
|
61
|
+
method: "POST",
|
|
62
|
+
headers: { "Content-Type": "application/json" },
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
chat_id: userId,
|
|
65
|
+
message_thread_id: thread_id,
|
|
66
|
+
text: message,
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
const data = await response.json();
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
console.error(`Send message to tele fail!`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function sendMessageNoti(message) {
|
|
76
|
+
sendMessage(message, process.env.TELEGRAM_CHATID || "", process.env.TELEGRAM_THREAD_ID || "");
|
|
77
|
+
}
|
|
78
|
+
console.log("🤖 Bot is running...");
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.utils = void 0;
|
|
7
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
8
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
9
|
+
dotenv_1.default.config();
|
|
10
|
+
exports.utils = {
|
|
11
|
+
getMinutesPassed: function (startAt) {
|
|
12
|
+
const startDate = new Date(startAt);
|
|
13
|
+
const currentDate = new Date();
|
|
14
|
+
const diffInMilliseconds = currentDate.getTime() - startDate.getTime();
|
|
15
|
+
return Math.floor(diffInMilliseconds / (1000 * 60)); // Chuyển đổi từ milliseconds sang phút
|
|
16
|
+
},
|
|
17
|
+
tokenDecode(token) {
|
|
18
|
+
const secret = process.env.JWT_SECRET || 'your-secret-key';
|
|
19
|
+
console.log('🔍 JWT Secret status:', process.env.JWT_SECRET ? 'Using environment JWT_SECRET' : 'Using default secret');
|
|
20
|
+
try {
|
|
21
|
+
const decoded = jsonwebtoken_1.default.verify(token, secret);
|
|
22
|
+
return decoded;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
console.error('❌ JWT verification failed:', error);
|
|
26
|
+
console.error('🔍 Token preview:', token.substring(0, 50) + '...');
|
|
27
|
+
console.error('🔍 Secret being used:', secret.substring(0, 10) + '...');
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
tokenEncode(data) {
|
|
32
|
+
const secret = process.env.JWT_SECRET || 'your-secret-key';
|
|
33
|
+
console.log('🔍 Creating JWT with secret:', process.env.JWT_SECRET ? 'Using environment JWT_SECRET' : 'Using default secret');
|
|
34
|
+
const token = jsonwebtoken_1.default.sign(data, secret, { expiresIn: '7d' });
|
|
35
|
+
console.log('✅ JWT token created successfully');
|
|
36
|
+
console.log('🔍 Token:', token);
|
|
37
|
+
return token;
|
|
38
|
+
}
|
|
39
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ts-game-decorators",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Express & Socket.IO decorators for auto routing and event handling. using for backend game development.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc -p tsconfig.json",
|
|
12
|
+
"prepare": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"express",
|
|
16
|
+
"socket.io",
|
|
17
|
+
"decorators",
|
|
18
|
+
"typescript",
|
|
19
|
+
"router",
|
|
20
|
+
"auto-router"
|
|
21
|
+
],
|
|
22
|
+
"author": "gamedevtoi",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"express": "^4.18.2",
|
|
26
|
+
"socket.io": "^4.7.4",
|
|
27
|
+
"reflect-metadata": "^0.1.13"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"typescript": "^5.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|