commit 0d7f1523170f2d627d6bc9d598d6ea72292df63a Author: Wong Yiek Heng Date: Sat Oct 4 10:16:41 2025 +0800 First Commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cdcc516 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,55 @@ +# Git +.git +.gitignore + +# Python +__pycache__ +*.pyc +*.pyo +*.pyd +.Python +env +pip-log.txt +pip-delete-this-directory.txt +.tox +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.log +.git +.mypy_cache +.pytest_cache +.hypothesis + +# OS +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# IDE +.vscode +.idea +*.swp +*.swo +*~ + +# Docker +Dockerfile* +docker-compose*.yml +.dockerignore + +# Documentation +README.md +*.md + +# Other +.env +.env.local +.env.*.local diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e0e8c99 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +.DS_Store diff --git a/Dockerfile.api b/Dockerfile.api new file mode 100644 index 0000000..08d17be --- /dev/null +++ b/Dockerfile.api @@ -0,0 +1,20 @@ +FROM python:3.9-slim + +WORKDIR /app + +RUN apt-get update + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application files +COPY . . + +# Set environment variables +ENV PYTHONUNBUFFERED=1 + +# Expose port +EXPOSE 3000 + +# Run the API server +CMD ["python", "cm_api.py"] \ No newline at end of file diff --git a/Dockerfile.telegram b/Dockerfile.telegram new file mode 100644 index 0000000..98958d8 --- /dev/null +++ b/Dockerfile.telegram @@ -0,0 +1,17 @@ +FROM python:3.9-slim + +WORKDIR /app + +RUN apt-get update + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application files +COPY . . + +# Set environment variables +ENV PYTHONUNBUFFERED=1 + +# Run the telegram bot +CMD ["python", "cm_telegram.py"] diff --git a/Dockerfile.transfer b/Dockerfile.transfer new file mode 100644 index 0000000..66b0aec --- /dev/null +++ b/Dockerfile.transfer @@ -0,0 +1,17 @@ +FROM python:3.9-slim + +WORKDIR /app + +RUN apt-get update + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application files +COPY . . + +# Set environment variables +ENV PYTHONUNBUFFERED=1 + +# Run the API server +CMD ["python", "cm_transfer_credit.py"] \ No newline at end of file diff --git a/Dockerfile.web b/Dockerfile.web new file mode 100644 index 0000000..2ed3850 --- /dev/null +++ b/Dockerfile.web @@ -0,0 +1,20 @@ +FROM python:3.9-slim + +WORKDIR /app + +RUN apt-get update + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application files +COPY . . + +# Set environment variables +ENV PYTHONUNBUFFERED=1 + +# Expose port +EXPOSE 8000 + +# Run the web view +CMD ["python", "cm_web_view.py"] diff --git a/cm_api.py b/cm_api.py new file mode 100644 index 0000000..92d68a3 --- /dev/null +++ b/cm_api.py @@ -0,0 +1,191 @@ +import threading +from flask import Flask, jsonify, request +from flask_cors import CORS +from db import DB + + +class CM_API: + + def __init__(self): + self.app = Flask(__name__) + CORS(self.app) + self._register_routes() + + def _get_database_connection(self): + """Create a new database connection for use""" + try: + db = DB() + return db + except Exception as e: + print(f"Database connection failed: {e}") + return None + + def _close_database_connection(self, db): + """Close database connection if it exists""" + if db is not None: + try: + # Assuming DB class has a close method or similar cleanup + if hasattr(db, 'close'): + db.close() + elif hasattr(db, 'connection') and hasattr(db.connection, 'close'): + db.connection.close() + except Exception as e: + print(f"Error closing database connection: {e}") + + def _register_routes(self): + # Account routes + self.app.route('/acc/', methods=['GET'])(self.get_account) + self.app.route('/acc/', methods=['GET'])(self.get_account) + + # User routes + self.app.route('/user/', methods=['GET'])(self.get_user) + self.app.route('/user/', methods=['GET'])(self.get_user) + + # Update routes + self.app.route('/update-acc-data', methods=['POST'])(self.update_acc_data) + self.app.route('/update-user-data', methods=['POST'])(self.update_user_data) + + def _check_database_available(self): + db = self._get_database_connection() + if db is None: + return False, None, ("Database not available", 500) + return True, db, None + + def _handle_error(self, error, message="An error occurred"): + print(f"Error: {error}") + return message, 500 + + def get_account(self, username=None): + is_available, db, error_response = self._check_database_available() + if not is_available: + return error_response + + try: + if username: + query = "SELECT username, password, status, link FROM acc WHERE username = %s" + query_params = [username] + else: + query = "SELECT username, password, status, link FROM acc" + query_params = [] + + results = db.query(query, query_params) + return jsonify(results) + + except Exception as error: + return self._handle_error(error, "Not Found"), 404 + finally: + self._close_database_connection(db) + + def get_user(self, username=None): + is_available, db, error_response = self._check_database_available() + if not is_available: + return error_response + + try: + if username: + query = "SELECT f_username, f_password, t_username, t_password FROM user WHERE f_username = %s" + query_params = [username] + else: + query = "SELECT f_username, f_password, t_username, t_password, last_update_time FROM user" + query_params = [] + + results = db.query(query, query_params) + return jsonify(results) + + except Exception as error: + return self._handle_error(error, "Not Found"), 404 + finally: + self._close_database_connection(db) + + def update_acc_data(self): + is_available, db, error_response = self._check_database_available() + if not is_available: + return error_response + + try: + data = request.get_json() + username = data.get('username') + password = data.get('password') + status = data.get('status') + link = data.get('link') + + if not username: + return jsonify({"error": "Username is required"}) + + result = db.execute( + "UPDATE acc SET password = %s, status = %s, link = %s WHERE username = %s", + [password, status, link, username] + ) + + if result: + return jsonify("Data updated successfully") + else: + return jsonify("Error updating data") + + except Exception as error: + return self._handle_error(error, "Error updating data"), 500 + finally: + self._close_database_connection(db) + + def update_user_data(self): + is_available, db, error_response = self._check_database_available() + if not is_available: + return error_response + + try: + data = request.get_json() + f_username = data.get('f_username') + f_password = data.get('f_password') + t_username = data.get('t_username') + t_password = data.get('t_password') + + if not f_username: + return jsonify({"error": "f_username is required"}) + + result = db.execute( + "UPDATE user SET f_password = %s, t_password = %s, t_username = %s, last_update_time = CURRENT_TIMESTAMP WHERE f_username = %s", + [f_password, t_password, t_username, f_username] + ) + + if result: + return jsonify("Data updated successfully") + else: + return jsonify("Error updating data") + + except Exception as error: + return self._handle_error(error, "Error updating data") + finally: + self._close_database_connection(db) + + def run(self, port=3000, debug=True): + # Test database connection before starting server + test_db = self._get_database_connection() + if test_db is None: + print("Cannot start server: Database not available") + exit(1) + self._close_database_connection(test_db) + + print(f'CM Bot DB API Listening at Port : {port}') + self.app.run(host='0.0.0.0', port=port, debug=debug) + + def run_in_thread(self, port=3000, debug=False): + """Run the Flask app in a separate thread""" + # Test database connection before starting server + test_db = self._get_database_connection() + if test_db is None: + print("Cannot start server: Database not available") + return None + self._close_database_connection(test_db) + + def run_app(): + print(f'CM Bot DB API Listening at Port : {port}') + self.app.run(host='0.0.0.0', port=port, debug=debug, use_reloader=False) + + thread = threading.Thread(target=run_app, daemon=True) + thread.start() + return thread + + +if __name__ == '__main__': + api = CM_API() + api.run(port = 3000) \ No newline at end of file diff --git a/cm_bot.py b/cm_bot.py new file mode 100644 index 0000000..b0094b6 --- /dev/null +++ b/cm_bot.py @@ -0,0 +1,499 @@ +import requests, re +from bs4 import BeautifulSoup + +# with open('security_response.html', 'wb') as f: +# f.write(response.content) + +class CM_BOT: + def __init__(self): + self.session = requests.Session() + self.base_url = 'https://cm99.net' + self.is_logged_in = False + self._setup_headers() + + def _setup_headers(self): + """Set up default headers for requests.""" + + self.login_headers = { + 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', + 'accept-encoding': 'identity', + 'accept-language': 'en-GB,en;q=0.8', + 'cache-control': 'max-age=0', + 'content-type': 'application/x-www-form-urlencoded', + 'origin': self.base_url, + 'referer': f'{self.base_url}/cm/login', + 'sec-ch-ua': '"Not;A=Brand";v="99", "Brave";v="139", "Chromium";v="139"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + 'sec-fetch-dest': 'document', + 'sec-fetch-mode': 'navigate', + 'sec-fetch-site': 'same-origin', + 'sec-fetch-user': '?1', + 'sec-gpc': '1', + 'upgrade-insecure-requests': '1', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36' + } + + self.get_user_tree_headers = { + 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', + 'accept-encoding': 'identity', + 'accept-language': 'en-GB,en;q=0.8', + 'priority': 'u=0, i', + 'sec-ch-ua': '"Not;A=Brand";v="99", "Brave";v="139", "Chromium";v="139"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + 'sec-fetch-dest': 'document', + 'sec-fetch-mode': 'navigate', + 'sec-fetch-site': 'none', + 'sec-fetch-user': '?1', + 'sec-gpc': '1', + 'upgrade-insecure-requests': '1', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36' + } + + self.get_register_form_headers = { + 'accept': 'text/html, */*; q=0.01', + 'accept-encoding': 'identity', + 'accept-language': 'en-GB,en;q=0.8', + 'content-length': '0', + 'origin': self.base_url, + 'priority': 'u=1, i', + 'referer': f'{self.base_url}/cm/userMainAction', + 'sec-ch-ua': '"Not;A=Brand";v="99", "Brave";v="139", "Chromium";v="139"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + 'sec-gpc': '1', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36', + 'x-requested-with': 'XMLHttpRequest' + } + + self.get_user_credit_headers = { + 'accept': 'text/html, */*; q=0.01', + 'accept-encoding': 'identity', + 'accept-language': 'en-GB,en;q=0.7', + 'content-length': '0', + 'origin': self.base_url, + 'priority': 'u=1, i', + 'referer': f'{self.base_url}/cm/mainMenu', + 'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Brave";v="140"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + 'sec-gpc': '1', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36', + 'x-requested-with': 'XMLHttpRequest' + } + + # self.change_pass_headers = { + # 'accept': '*/*', + # 'accept-encoding': 'identity', + # 'accept-language': 'en-GB,en;q=0.8', + # 'content-length': '889', + # 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', + # 'origin': self.base_url, + # 'priority': 'u=1, i', + # 'referer': f'{self.base_url}/cm/userMainAction', + # 'sec-ch-ua': '"Not;A=Brand";v="99", "Brave";v="139", "Chromium";v="139"', + # 'sec-ch-ua-mobile': '?0', + # 'sec-ch-ua-platform': '"macOS"', + # 'sec-fetch-dest': 'empty', + # 'sec-fetch-mode': 'cors', + # 'sec-fetch-site': 'same-origin', + # 'sec-gpc': '1', + # 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36', + # 'x-requested-with': 'XMLHttpRequest' + # } + + self.register_form_headers = { + 'accept': '*/*', + 'accept-encoding': 'identity', + 'accept-language': 'en-GB,en;q=0.8', + 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'origin': self.base_url, + 'priority': 'u=1, i', + 'referer': f'{self.base_url}/cm/userMainAction', + 'sec-ch-ua': '"Not;A=Brand";v="99", "Brave";v="139", "Chromium";v="139"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + 'sec-gpc': '1', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36', + 'x-requested-with': 'XMLHttpRequest' + } + + self.set_security_pin_headers = { + 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', + 'accept-encoding': 'identity', + 'accept-language': 'en-GB,en;q=0.9', + 'cache-control': 'max-age=0', + 'content-length': '103', + 'content-type': 'application/x-www-form-urlencoded', + 'origin': self.base_url, + 'priority': 'u=0, i', + 'referer': f'{self.base_url}/cm/setSecurityPin', + 'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Brave";v="140"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + 'sec-fetch-dest': 'document', + 'sec-fetch-mode': 'navigate', + 'sec-fetch-site': 'same-origin', + 'sec-fetch-user': '?1', + 'sec-gpc': '1', + 'upgrade-insecure-requests': '1', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36' + } + + self.transfer_search_headers = { + 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', + 'accept-encoding': 'identity', + 'accept-language': 'en-GB,en;q=0.7', + 'cache-control': 'max-age=0', + 'content-length': '78', + 'content-type': 'application/x-www-form-urlencoded', + 'origin': self.base_url, + 'priority': 'u=0, i', + 'referer': f'{self.base_url}/cm/transfer', + 'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Brave";v="140"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + 'sec-fetch-dest': 'document', + 'sec-fetch-mode': 'navigate', + 'sec-fetch-site': 'same-origin', + 'sec-fetch-user': '?1', + 'sec-gpc': '1', + 'upgrade-insecure-requests': '1', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36' + } + + self.transfer_credit_headers = { + 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', + 'accept-encoding': 'identity', + 'accept-language': 'en-GB,en;q=0.7', + 'cache-control': 'max-age=0', + 'content-length': '152', + 'content-type': 'application/x-www-form-urlencoded', + 'origin': self.base_url, + 'priority': 'u=0, i', + 'referer': f'{self.base_url}/cm/searchTransferUser', + 'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Brave";v="140"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + 'sec-fetch-dest': 'document', + 'sec-fetch-mode': 'navigate', + 'sec-fetch-site': 'same-origin', + 'sec-fetch-user': '?1', + 'sec-gpc': '1', + 'upgrade-insecure-requests': '1', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36' + } + + def get_register_data(self, token: str, username: str, password: str): + return { + 'struts.token.name': 'token', + 'token': f'{token}', + 'userIsNew': 'true', + 'searchContent': '', + 'user.username': f'{username}', + 'user.companyId': '1', + 'user.userRoleId': '4', + 'user.password': f'{password}', + 'user.confirmPassword': f'{password}', + 'user.name': f'{username}', + 'user.mobileNumber': '', + 'user.email': '', + 'user.remarks': '', + 'user.accountStatus': 'A', + 'user.currencyMYR': 'true', + '__checkbox_user.currencyMYR': 'true', + 'user.allowBetHl': 'true', + '__checkbox_user.allowBetHl': 'true', + 'user.commission3d4d': '5.00', + 'user.commission5d6d': '5.00', + 'user.commissionHL': '19.00', + 'user.commissionHL6d': '19.00', + 'user.commissionNL': '19.00', + 'user.commissionNL6d': '19.00', + 'checkAllPrizePackages': 'on', + 'selectedPrizePackageList': ['6', '1', '5', '3'], + '__multiselect_selectedPrizePackageList': '', + 'checkAllPrizePackages5D6D': 'on', + 'selectedPrizePackage5D6DList': '2', + '__multiselect_selectedPrizePackage5D6DList': '' + } + + def get_security_pin_data(self, token: str, security_pin: str): + return { + 'struts.token.name': 'token', + 'token': token, + 'newPin': security_pin, + 'confirmNewPin': security_pin + } + + def get_transfer_search_data(self, token: str, username: str): + return { + 'struts.token.name': 'token', + 'token': token, + 'username': username + } + + def get_transfer_data(self, token: str, username: str, name: str, toUserId: str, amount: float, security_pin: str): + return { + 'struts.token.name': 'token', + 'token': token, + 'username': username, + 'name': name, + 'toUserId': toUserId, + 'amount': amount, + 'securityPin': security_pin + } + + # def get_change_pass_data(self, token: str, user_encrypted_id: str, username: str, password: str): + # return { + # 'struts.token.name': 'token', + # 'token': f'{token}', + # 'searchContent': '', + # 'userIsNew': 'false', + # 'user.encryptedId': f'{user_encrypted_id}', + # 'user.username': f'{username}', + # 'user.parentId': '31308', + # 'user.companyId': '1', + # 'user.userRoleId': '4', + # 'user.password': f'{password}', + # 'user.confirmPassword': f'{password}', + # 'user.name': f'{username}', + # 'user.mobileNumber': '', + # 'user.email': '', + # 'user.remarks': '', + # 'user.accountStatus': 'A', + # 'user.currencyMYR': 'true', + # '__checkbox_user.currencyMYR': 'true', + # 'user.allowBetHl': 'true', + # '__checkbox_user.allowBetHl': 'true', + # 'user.balance': '1', + # 'user.outstanding': '1', + # 'user.commission3d4d': '5.00', + # 'user.commission5d6d': '5.00', + # 'user.commissionHL': '19.00', + # 'user.commissionHL6d': '19.00', + # 'user.commissionNL': '19.00', + # 'user.commissionNL6d': '19.00', + # 'selectedPrizePackageList': ['6', '1', '5', '3'], + # '__multiselect_selectedPrizePackageList': '', + # 'selectedPrizePackage5D6DList': '2', + # '__multiselect_selectedPrizePackage5D6DList': '' + # } + + def login(self, username: str, password: str): + try: + print("Starting login process...") + login_page = self.session.get(f'{self.base_url}/cm/login') + print(f"Login page status: {login_page.status_code}") + + login_data = { + 'j_username': username, + 'j_password': password + } + + login_response = self.session.post( + f'{self.base_url}/cm/j_security_check', + data=login_data, + headers=self.login_headers, + allow_redirects=True + ) + + if login_response.status_code == 200 and 'login' not in login_response.url.lower(): + print("Login successful!") + self.is_logged_in = True + return True + else: + print("Login failed!") + self.is_logged_in = False + return False + + except requests.exceptions.RequestException as e: + print(f"Error during login: {e}") + self.is_logged_in = False + return False + + def get_max_user_in_pattern(self, prefix_pattern: str = '13c'): + response = self.session.get(f'{self.base_url}/cm/json/generateUserTree?id=0') + regex = f'\\[{prefix_pattern}\\d+]' + matches = re.findall(regex, response.text) + last_match_text = matches[-1] + number_part = last_match_text.replace(f"[{prefix_pattern}", "").replace("]", "") + last_match = int(number_part) + return last_match + + def get_register_form_token(self): + try: + response = self.session.post( + f'{self.base_url}/cm/loadUserAccount', + headers=self.get_register_form_headers + ) + soup = BeautifulSoup(response.content, 'html.parser') + return soup.find('input', {'name' : "token"})['value'] + except requests.exceptions.RequestException as e: + print(f"Error getting register form: {e}") + return None + + + def get_security_pin_form_token(self): + response = self.session.get(f'{self.base_url}/cm/setSecurityPin') + soup = BeautifulSoup(response.content, 'html.parser') + return soup.find('input', {'name' : "token"})['value'] + + def register_user(self, user_id, user_password): + try: + print("Creating user account...") + token = self.get_register_form_token() + print(f"Token: {token}") + + user_data = self.get_register_data(token, user_id, user_password) + print(user_data) + response = self.session.post( + f'{self.base_url}/cm/saveUserAccount', + data=user_data, + headers=self.register_form_headers + ) + + if re.search('User created successfully', response.text): + print(f"User account: {user_id} password: {user_password} creation completed!") + else: + print(f"User account: {user_id} creation FAIL!") + + except requests.exceptions.RequestException as e: + print(f"Error creating user account: {e}") + return None + + # def change_user_password(self, user_id: str, new_user_pass: str): + # try: + # print(f"Changing user: {user_id} password...") + # self.login(self.username, self.password) + # token = self.get_register_form_token() + # headers = self.change_pass_headers + + # user_data = self.get_change_pass_data(token, ) + # response = self.session.post( + # f'{self.base_url}/cm/saveUserAccount', + # data=user_data, + # headrs=headers + # ) + + # except requests.exceptions.RequestException as e: + # print(f"Error change user password: {e}") + # return None + + def get_register_link(self): + response = self.session.get(f"{self.base_url}/cm/showQrCode") + soup = BeautifulSoup(response.content, 'html.parser') + soup = soup.find('form', {'id': 'qrCodeForm'}) + return soup.find('a')['href'] + + def get_generate_username(self, max_username_index: int): + max_username_index += 1 + if max_username_index % 10 == 4: + max_username_index += 1 + return max_username_index + + def set_security_pin(self, security_pin: str): + token = self.get_security_pin_form_token() + security_data = self.get_security_pin_data(token, security_pin) + response = self.session.post( + f'{self.base_url}/cm/saveSecurityPin', + data=security_data, + headers=self.set_security_pin_headers + ) + + def transfer_credit(self, t_username: str, t_password: str, amount: float): + token = self.get_transfer_token() + transfer_search_data = self.get_transfer_search_data(token, t_username) + response = self.session.post( + f'{self.base_url}/cm/searchTransferUser', + data=transfer_search_data, + headers=self.transfer_search_headers + ) + soup = BeautifulSoup(response.content, 'html.parser') + name = soup.find('input', {'id': "name"})['value'] + token = soup.find('input', {'name': "token"})['value'] + toUserId = soup.find('input', {'id': "toUserId"})['value'] + transfer_data = self.get_transfer_data(token, t_username, name, toUserId, amount, t_password) + response = self.session.post( + f'{self.base_url}/cm/saveTransfer', + data=transfer_data, + headers=self.transfer_credit_headers + ) + # with open('transfer_credit.html', 'wb') as f: + # f.write(response.content) + return True if re.search(r'Successfully saved the record\.', response.text) else False + + def get_user_credit(self): + response = self.session.post( + f'{self.base_url}/cm/userProfile', + headers=self.get_user_credit_headers + ) + soup = BeautifulSoup(response.content, 'html.parser') + return float(soup.find('table', {'class': 'generalContent'}).find(text=re.compile('Credit Available')).parent.parent.find_all('td')[2].text) + + def get_transfer_token(self): + response = self.session.get(f'{self.base_url}/cm/transfer') + soup = BeautifulSoup(response.content, 'html.parser') + return soup.find('input', {'name' : "token"})['value'] + + def logout(self): + """Logout from the system.""" + self.session.close() + self.is_logged_in = False + print("Logged out successfully.") + + +def main(): + # user_id='testing0001' + # user_pass='Qwer1@34' + + # # prefix = '13c' + user_manager = CM_BOT() + + user_manager.login( + username = '13c4021', + password = 'vX34wUk' + ) + + user_manager.transfer_credit('m94', 'Sky533535', 0.01) + + # last_username = user_manager.get_max_user_in_pattern(prefix) + # user_id = f'{prefix}{user_manager.get_generate_username(last_username)}' + # user_pass = user_manager.get_random_password() + + # print(user_id) + # print(user_pass) + + # user_manager.register_user( + # user_id=user_id, + # user_password=user_pass + # ) + + # user_manager.logout() + + # user_manager = CM_BOT( + # username = user_id, + # password = user_pass + # ) + # user_manager.login(user_id, user_pass) + # print(user_manager.get_register_link()) + + # user_manager.login( + # username = user_id, + # password = user_pass + # ) + + # user_manager.set_security_pin(user_pass) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/cm_bot_hal.py b/cm_bot_hal.py new file mode 100644 index 0000000..26af044 --- /dev/null +++ b/cm_bot_hal.py @@ -0,0 +1,187 @@ +import requests, re +from bs4 import BeautifulSoup +from cm_bot import CM_BOT +from db import DB + +import secrets, string + +class CM_BOT_HAL: + def __init__(self): + self.db = DB() + self.prefix = '13c' + self.agent_id = 'cm13a3' + self.agent_password = 'Sky533535' + self.security_pin = 'Sky533535' + + def get_random_password(self): + length = 8 + lower_case = string.ascii_lowercase + upper_case = string.ascii_uppercase + digits = string.digits + all_characters = lower_case + upper_case + digits + + password_list = [ + secrets.choice(lower_case), + secrets.choice(upper_case), + secrets.choice(digits), + secrets.choice(digits), + secrets.choice(digits) + ] + + while len(password_list) < length: + password_list.append(secrets.choice(all_characters)) + + secrets.SystemRandom().shuffle(password_list) + + return "".join(password_list) + + def get_user_data_from_acc(self): + query = "SELECT username, password, link FROM acc WHERE status = %s ORDER BY username ASC LIMIT 1" + query_params = [''] + result = self.db.query(query, query_params) + return result[0] if len(result) else None + + def get_max_username(self, prefix: str): + query = "SELECT username FROM acc WHERE username LIKE %s ORDER BY username DESC LIMIT 1" + query_params = [f'{prefix}%'] + result = self.db.query(query, query_params) + return result[0]['username'] + + def get_next_username(self, prefix: str): + last_index = int(self.get_max_username(prefix).replace(prefix, '')) + next_index = last_index + 2 if last_index % 10 == 3 else last_index + 1 + return f'{prefix}{next_index}' + + def insert_user_to_table_acc(self, user): + query = "INSERT INTO acc (username, password, link) VALUES (%s, %s, %s)" + query_params = [user['username'], user['password'], user['link']] + return self.db.execute(query, query_params) + + def insert_user_to_table_user(self, user): + query = "INSERT INTO user (f_username, f_password, t_username, t_password) VALUES (%s, %s, %s, %s)" + query_params = [user['f_username'], user['f_password'], user['t_username'], user['t_password']] + return self.db.execute(query, query_params) + + def create_new_acc(self): + cm_bot = CM_BOT() + username = self.get_next_username(self.prefix) + password = self.get_random_password() + cm_bot.login( + username = self.agent_id, + password = self.agent_password + ) + cm_bot.register_user( + user_id = username, + user_password = password + ) + cm_bot.logout() + + cm_bot = CM_BOT() + cm_bot.login( + username = username, + password = password + ) + link = cm_bot.get_register_link() + cm_bot.logout() + + user = {'username': username, 'password': password, 'link': link} + self.insert_user_to_table_acc(user) + return user + + def update_user_status_to_wait(self, username: str): + query = "UPDATE acc SET status = 'wait' WHERE username = %s" + query_params = [username] + return self.db.execute(query, query_params) + + def update_user_status_to_done(self, username: str): + query = "UPDATE acc SET status = 'done' WHERE username = %s" + query_params = [username] + return self.db.execute(query, query_params) + + def get_user_api(self): + user = self.get_user_data_from_acc() + if user is None: + user = self.create_new_acc() + self.update_user_status_to_wait(user['username']) + return user + + def is_whatsapp_url(self, text): + url_pattern = re.compile( + r'^(https?://)?' # Optional http:// or https:// + r'((www\.)|([a-zA-Z0-9-]+\.))+' # www. or subdomain. + # r'[a-zA-Z]{2,6}' # Top-level domain (e.g., com, org, net) + r'(whatsapp.com)' + r'(/[a-zA-Z0-9-._~:/?#\[\]@!$&\'()*+,;=]*)?$' # Optional path, query, fragment + ) + return bool(url_pattern.search(text)) + + def get_whatsapp_link_username(self, whatsapp_link: str): + if self.is_whatsapp_url(whatsapp_link) == False: + return None + response = requests.get(whatsapp_link) + soup = BeautifulSoup(response.content, 'html.parser') + username = [x.replace('*', '').replace(' ', '') for x in soup.find('h3', {"class": "_9vd5 _9scr"}).text.split("/")] + return username + + def get_user_pass_from_acc(self, username: str): + query = "SELECT password FROM acc WHERE username = %s" + query_params = [username] + result = self.db.query(query, query_params) + return result[0]['password'] + + def set_security_pin_api(self, whatsapp_link: str): + t_username, f_username = self.get_whatsapp_link_username(whatsapp_link) + password = self.get_user_pass_from_acc(f_username) + cm_bot = CM_BOT() + cm_bot.login( + username = f_username, + password = password + ) + cm_bot.set_security_pin(self.security_pin) + cm_bot.logout() + + result = self.update_user_status_to_done(f_username) + if result == False: + raise Exception('Failed to update user status to done') + + result = self.insert_user_to_table_user( + { + 'f_username': f_username, + 'f_password': password, + 't_username': t_username, + 't_password': self.security_pin + } + ) + if result == False: + raise Exception('Failed to insert user to table user') + + def get_user_credit(self, f_username: str, f_password: str): + cm_bot = CM_BOT() + cm_bot.login( + username = f_username, + password = f_password + ) + return float(cm_bot.get_user_credit()) + + def transfer_credit_api(self, f_username: str, f_password: str, t_username: str, t_password: str): + cm_bot = CM_BOT() + cm_bot.login( + username = f_username, + password = f_password + ) + amount = cm_bot.get_user_credit() - 0.01 + if cm_bot.transfer_credit(t_username, t_password, amount) == True: + print(f'Successfully transfer amount: {amount} from {f_username} to {t_username}') + else: + print(f'Failed to transfer credit from {f_username} to {t_username}') + + +if __name__ == '__main__': + bot = CM_BOT_HAL() + # bot.transfer_credit_api('13c4070', 'zU2QoL', '4753kit', 'Sky533535') + # print(bot.get_next_username('13c')) + # print(bot.get_random_password()) + # bot.get_user_api() + # bot.insert_user_to_table_acc({'username': 'test0001', 'password': 'test0001', 'link': 'test0001'}) + # print(bot.get_whatsapp_link_username('https://chat.whatsapp.com/DZDWcicr6MTFrR4kBfnJXO')) + # print(bot.get_user_pass_from_acc('13c4151')) \ No newline at end of file diff --git a/cm_telegram.py b/cm_telegram.py new file mode 100644 index 0000000..72934ea --- /dev/null +++ b/cm_telegram.py @@ -0,0 +1,85 @@ +import logging + +from telegram import ForceReply, Update +from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters + +from cm_bot_hal import CM_BOT_HAL + +logging.basicConfig( + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO +) +logging.getLogger("httpx").setLevel(logging.WARNING) + +logger = logging.getLogger(__name__) + +async def menu_cmd_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + menu = [ + 'MENU', + '/1 - Get Acc', + '/2 - Set Security Pin' + ] + await update.message.reply_text('\n'.join(menu)) + +async def get_acc_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + await update.message.reply_text('Start Getting CM Account ...') + try: + bot = CM_BOT_HAL() + user = bot.get_user_api() + msg = [ + f'Username: {user["username"]}', + f'Password: {user["password"]}', + f'Link: {user["link"]}' + ] + await update.message.reply_text('\n'.join(msg)) + except Exception as e: + await update.message.reply_text(f'Error: {e}') + +async def set_security_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + if len(context.args) == 0 or len(context.args) > 1: + await update.message.reply_text('CMD is wrong, please check and retry ...') + return + bot = CM_BOT_HAL() + if bot.is_whatsapp_url(context.args[0]) == False: + await update.message.reply_text('Link Format Wrong, please check and retry ...') + return + await update.message.reply_text('Start Setting Security Pin ...') + try: + bot.set_security_pin_api(context.args[0]) + del bot + await update.message.reply_text('Security Pin Set Done!') + except Exception as e: + await update.message.reply_text(f'Error: {e}') + +async def insert_to_user_table_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + if len(context.args) == 0 or len(context.args) != 4: + await update.message.reply_text('CMD is wrong, please check and retry ...') + return + bot = CM_BOT_HAL() + f_username, f_password, t_username, t_password = context.args + bot.insert_user_to_table_user( + { + 'f_username': f_username, + 'f_password': f_password, + 't_username': t_username, + 't_password': t_password + } + ) + await update.message.reply_text(f'Done insert {f_username} into user table.') + + +def main() -> None: + """Start the bot.""" + # application = Application.builder().token("5327571437:AAFlowwnAysTEMx6LtYQNTevGCboKDZoYzY").build() + application = Application.builder().token("5315819168:AAH31xwNgPdnk123x97XalmTW6fQV5EUCFU").build() + application.add_handler(CommandHandler("menu", menu_cmd_handler)) + application.add_handler(CommandHandler("1", get_acc_handler)) + application.add_handler(CommandHandler("2", set_security_handler)) + application.add_handler(CommandHandler("3", insert_to_user_table_handler)) + # application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo)) + + # Start the Telegram bot + print("Starting Telegram bot...") + application.run_polling(allowed_updates=Update.ALL_TYPES) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/cm_transfer_credit.py b/cm_transfer_credit.py new file mode 100644 index 0000000..5d77ffd --- /dev/null +++ b/cm_transfer_credit.py @@ -0,0 +1,37 @@ +from cm_bot_hal import CM_BOT_HAL +import logging, time, requests, json +from concurrent.futures import ThreadPoolExecutor +from datetime import datetime +from tqdm import tqdm + +logging.basicConfig( + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO +) +logging.getLogger("httpx").setLevel(logging.WARNING) + +logger = logging.getLogger(__name__) +api_url = 'https://api.luckytown888.net' +max_threading = 1 + +def transfer(data: dict): + bot = CM_BOT_HAL() + logger.info(f'[Start] Transfer Credit from {data['f_username']} to {data['t_username']}') + bot.transfer_credit_api(data['f_username'], data['f_password'], data['t_username'], data['t_password']) + logger.info(f'[DONE] {data['f_username']} transfer done!') + del bot + time.sleep(0.5) + +while True: + weekday = int(datetime.now().strftime("%w")) + hour = int(datetime.now().strftime("%H")) + minutes = int(datetime.now().strftime("%M")) + if weekday == 3 and (hour >= 21 and hour < 22) and minutes >= 32: + response = requests.get(f'{api_url}/user') + items = json.loads(response.text) + total_items = len(items) if isinstance(items, list) else 0 + if total_items == 0: + logger.info("No items to process.") + else: + with ThreadPoolExecutor(max_workers=max_threading) as executor: + list(tqdm(executor.map(transfer, items), total=total_items, desc="Processing data")) + time.sleep(1) \ No newline at end of file diff --git a/cm_web_view.py b/cm_web_view.py new file mode 100644 index 0000000..7a1c1d6 --- /dev/null +++ b/cm_web_view.py @@ -0,0 +1,745 @@ +from flask import Flask, render_template_string, request, jsonify +from flask_cors import CORS +import requests +import json + +app = Flask(__name__) +CORS(app) + +# API base URL - use environment variable for Docker Compose +import os +API_BASE_URL = os.getenv('API_BASE_URL', 'http://localhost:3000') +print("API: ", API_BASE_URL) + +# Beautiful HTML template with modern styling +HTML_TEMPLATE = """ + + + + + + CM Bot Database Viewer + + + + +
+
+

CM Bot Database Viewer

+

Real-time view of accounts and users data

+
+ +
+ + +
+ +
+ + + + +
+
+ +

Loading accounts...

+
+
+ + +
+
+ + + + +""" + +@app.route('/') +def index(): + return render_template_string(HTML_TEMPLATE) + +@app.route('/api/acc/') +def proxy_acc(): + try: + response = requests.get(f"{API_BASE_URL}/acc/") + return jsonify(response.json()) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route('/api/user/') +def proxy_user(): + try: + response = requests.get(f"{API_BASE_URL}/user/") + return jsonify(response.json()) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route('/api/update-acc-data', methods=['POST']) +def proxy_update_acc(): + try: + data = request.get_json() + response = requests.post(f"{API_BASE_URL}/update-acc-data", json=data) + return jsonify(response.json()), response.status_code + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route('/api/update-user-data', methods=['POST']) +def proxy_update_user(): + try: + data = request.get_json() + response = requests.post(f"{API_BASE_URL}/update-user-data", json=data) + return jsonify(response.json()), response.status_code + except Exception as e: + return jsonify({"error": str(e)}), 500 + +if __name__ == '__main__': + print("Starting CM Web View...") + print("Web interface will be available at: http://localhost:8000") + print("Make sure the API server is running on port 3000") + app.run(host='0.0.0.0', port=8000, debug=True) \ No newline at end of file diff --git a/db.py b/db.py new file mode 100644 index 0000000..191a1aa --- /dev/null +++ b/db.py @@ -0,0 +1,99 @@ +import mysql.connector +from mysql.connector import Error + +class DB: + def __init__(self): + self.config = { + 'host': '192.168.0.210', + 'user': 'rex_cm', + 'password': 'hengserver', + 'database': 'rex_cm', + 'port': 3306 + } + self.init_database() + + def get_connection(self): + """Get MySQL database connection.""" + try: + connection = mysql.connector.connect(**self.config) + return connection + except Error as e: + print(f"Error connecting to MySQL: {e}") + return None + + def init_database(self): + """Initialize the database connection.""" + connection = self.get_connection() + if connection is None: + raise Exception("Failed to connect to database") + + try: + cursor = connection.cursor() + # Test connection by checking if required tables exist + cursor.execute("SHOW TABLES LIKE 'acc'") + if not cursor.fetchone(): + raise Exception("Table 'acc' does not exist") + + cursor.execute("SHOW TABLES LIKE 'user'") + if not cursor.fetchone(): + raise Exception("Table 'user' does not exist") + + print("Database connection verified - required tables exist") + + except Error as e: + print(f"Error verifying database: {e}") + raise Exception(f"Database verification failed: {e}") + finally: + if connection.is_connected(): + cursor.close() + connection.close() + + def query(self, query, params=None): + """Execute a query and return results.""" + connection = self.get_connection() + if connection is None: + return [] + + try: + cursor = connection.cursor(dictionary=True) + + if params: + cursor.execute(query, params) + else: + cursor.execute(query) + + results = cursor.fetchall() + return results + + except Error as e: + print(f"Error executing query: {e}") + return [] + finally: + if connection.is_connected(): + cursor.close() + connection.close() + + def execute(self, query, params=None): + """Execute a query that modifies data (INSERT, UPDATE, DELETE) and return success status.""" + connection = self.get_connection() + if connection is None: + return False + + try: + cursor = connection.cursor() + + if params: + cursor.execute(query, params) + else: + cursor.execute(query) + + connection.commit() + return True + + except Error as e: + print(f"Error executing query: {e}") + return False + finally: + if connection.is_connected(): + cursor.close() + connection.close() \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..fda6368 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,71 @@ +services: + # Telegram Bot Service + telegram-bot: + build: + context: . + dockerfile: Dockerfile.telegram + container_name: cm-telegram-bot + restart: unless-stopped + environment: + - PYTHONUNBUFFERED=1 + volumes: + - .:/app + networks: + - cm-network + depends_on: + - api-server + + # API Server Service + api-server: + build: + context: . + dockerfile: Dockerfile.api + container_name: cm-api-server + restart: unless-stopped + ports: + - "3000:3000" + environment: + - PYTHONUNBUFFERED=1 + volumes: + - .:/app + networks: + - cm-network + + # Web View Service + web-view: + build: + context: . + dockerfile: Dockerfile.web + container_name: cm-web-view + restart: unless-stopped + ports: + - "8001:8000" + environment: + - PYTHONUNBUFFERED=1 + - API_BASE_URL=http://api-server:3000 + volumes: + - .:/app + networks: + - cm-network + depends_on: + - api-server + + transfer-bot: + build: + context: . + dockerfile: Dockerfile.transfer + container_name: cm-transfer-bot + restart: unless-stopped + environment: + - PYTHONUNBUFFERED=1 + volumes: + - .:/app + networks: + - cm-network + depends_on: + - api-server + + +networks: + cm-network: + driver: bridge diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bbc8992 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +Flask==2.3.3 +mysql-connector-python==8.1.0 +flask-cors==4.0.0 +python-telegram-bot==22.4 +requests==2.32.5 +beautifulsoup4==4.13.5 +tqdm==4.67.1 \ No newline at end of file diff --git a/transfer_credit.html b/transfer_credit.html new file mode 100644 index 0000000..ac8a8ea --- /dev/null +++ b/transfer_credit.html @@ -0,0 +1,542 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Transfer | + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Information + Successfully saved the record.
+ +
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+ + + + + +
 Transfer
+
+
+ + + + + + + + + + + +
Username : + + + + +
+
+
+ + +
+ + + + +
+ + +
+ + + diff --git a/transfer_search.html b/transfer_search.html new file mode 100644 index 0000000..02f8314 --- /dev/null +++ b/transfer_search.html @@ -0,0 +1,559 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Transfer | + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+ + + + + +
 Transfer
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Username : + + +
Name : + + +
Amount : + +
Security Pin : + +
+ +
+
+
+ + +
+ + + + +
+ + +
+ + +