diff --git a/RollCall_App/acitvation_code.py b/RollCall_App/acitvation_code.py index dd6aab5..a2b88f4 100644 --- a/RollCall_App/acitvation_code.py +++ b/RollCall_App/acitvation_code.py @@ -1,23 +1,65 @@ # generate_activation_code.py -import hashlib +import uuid +import random # For date-based index generation -# --- Configuration (MUST MATCH THE ONE IN THE MAIN APP) --- -SECRET_KEY = "UgG2jVcqmYed%i4ZVRC%JN*&%6^LR!%y%dy4sE4c9zZ" # Keep this very secret -ACTIVATION_BASE_STRING = "yBSksSte52uorfNSSmxLSpLxoteRVd2i8enB73mFpXj" -CODE_LENGTH = 16 # How many characters of the hash to use +# --- Seed Date Configuration (MUST MATCH THE ONE IN THE MAIN APP) --- +SEED_YEAR = 2025 +SEED_MONTH = 5 +SEED_DAY = 18 +# --------------------------------------------------------------------- + +# --- New Configuration --- +# No other specific configuration needed for UUID-based generation itself. +# Key derivation logic is embedded in the function. # --------------------------------------------------------- def generate_code(): - """Generates the activation code.""" - data_to_hash = ACTIVATION_BASE_STRING + SECRET_KEY - hashed_data = hashlib.sha256(data_to_hash.encode('utf-8')).hexdigest() - raw_code = hashed_data[:CODE_LENGTH].upper() + """Generates a UUID and a corresponding activation key.""" + generated_uuid = uuid.uuid4() + uuid_str = str(generated_uuid) + + # New, more complex key derivation logic: + # 1. Remove hyphens from the UUID string. + # 2. Define a fixed set of indices. + # 3. Extract characters at these indices. + # 4. Concatenate and uppercase them to form the 8-character key. + # This logic MUST match the validation logic in the main application. + + s = uuid_str.replace('-', '') # Should be 32 characters long + + # Generate dynamic indices based on the fixed SEED_DATE + date_seed_value = SEED_YEAR * 10000 + SEED_MONTH * 100 + SEED_DAY + rng = random.Random(date_seed_value) + all_possible_indices = list(range(32)) + rng.shuffle(all_possible_indices) + dynamic_indices = sorted(all_possible_indices[:8]) # Take first 8 and sort for consistency + + if len(s) == 32: # Standard UUID without hyphens + try: + key_chars = [s[i] for i in dynamic_indices] + activation_key_raw = "".join(key_chars).upper() + except IndexError: + # This should ideally not happen if s is 32 chars and indices are from range(32) + activation_key_raw = "IDXERROR" + else: + # Fallback or error handling if s is not 32 chars + activation_key_raw = "LENERROR" - # Format the code e.g., XXXX-XXXX-XXXX-XXXX for 16 chars - formatted_code = "-".join(raw_code[i:i+4] for i in range(0, len(raw_code), 4)) - return formatted_code + # Optionally format the key for display (e.g., XXXX-XXXX) + # formatted_key = "-".join(activation_key_raw[i:i+4] for i in range(0, len(activation_key_raw), 4)) + # For simplicity, we'll return the raw 8-char key and let the main app decide on display formatting if needed. + + return uuid_str, activation_key_raw if __name__ == "__main__": - activation_code = generate_code() - print(f"Generated Activation Code: {activation_code}") - print("Keep this code safe and provide it to your users.") \ No newline at end of file + generated_uuid, activation_key = generate_code() + + print(f"Generated UUID: {generated_uuid}") + print(f"Generated Activation Key: {activation_key}") + # Example of how it might be formatted for easier user input: + if len(activation_key) == 8: + formatted_display_key = f"{activation_key[:4]}-{activation_key[4:]}" + print(f"Suggested Key Format for User: {formatted_display_key}") + + print("\nProvide both the UUID and the Activation Key to the user.") + print("The user will need to enter both into the application to activate.") diff --git a/RollCall_App/课堂随机点名.py b/RollCall_App/课堂随机点名.py index af61415..5f272f5 100644 --- a/RollCall_App/课堂随机点名.py +++ b/RollCall_App/课堂随机点名.py @@ -9,12 +9,11 @@ from datetime import date, timedelta # Added timedelta from pypinyin import pinyin, Style import json # Added for trial status import sys # Import sys for path joining -import hashlib # For activation +import random # For date-based index generation in ActivationManager -# --- Activation Configuration (MUST MATCH generate_activation_code.py) --- -SECRET_KEY = "UgG2jVcqmYed%i4ZVRC%JN*&%6^LR!%y%dy4sE4c9zZ" -ACTIVATION_BASE_STRING = "yBSksSte52uorfNSSmxLSpLxoteRVd2i8enB73mFpXj" -CODE_LENGTH = 16 +# --- Activation Configuration (UUID Based) --- +# Old SECRET_KEY, ACTIVATION_BASE_STRING, CODE_LENGTH are no longer used for UUID activation. +# New key length is effectively 8 characters, derived from UUID. APP_NAME_FOR_CONFIG = "RollCallApp" # Used for storing activation status # --------------------------------------------------------- @@ -22,9 +21,15 @@ APP_NAME_FOR_CONFIG = "RollCallApp" # Used for storing activation status TRIAL_DURATION_DAYS = 1 MAX_TRIAL_USES = 3 # Version for activation and trial files, ensures a fresh trial if activation logic changes -APP_CONFIG_VERSION_SUFFIX = "_v28" +APP_CONFIG_VERSION_SUFFIX = "_v30_uuid_date_idx" # << UPDATED FOR NEW ACTIVATION # --------------------------------------------------------- +# --- Seed Date Configuration (MUST MATCH THE ONE IN acitvation_code.py) --- +SEED_YEAR = 2025 +SEED_MONTH = 5 +SEED_DAY = 18 +# --------------------------------------------------------------------- + class ActivationManager: def __init__(self): self.config_dir = self._get_config_dir() @@ -51,48 +56,67 @@ class ActivationManager: path = os.path.join(os.path.expanduser('~/.config'), APP_NAME_FOR_CONFIG) return path - def _generate_expected_code(self): - """Generates the expected activation code based on embedded secrets.""" - data_to_hash = ACTIVATION_BASE_STRING + SECRET_KEY - hashed_data = hashlib.sha256(data_to_hash.encode('utf-8')).hexdigest() - raw_code = hashed_data[:CODE_LENGTH].upper() - formatted_code = "-".join(raw_code[i:i+4] for i in range(0, len(raw_code), 4)) - return formatted_code + def _generate_expected_key_from_uuid(self, uuid_str): + """ + Generates the expected activation key from a given UUID string. + This logic MUST match generate_activation_code.py. + """ + if not uuid_str: + return "" + + s = uuid_str.replace('-', '').lower() # Normalize to lowercase and remove hyphens + + if len(s) != 32: # Standard UUID without hyphens should be 32 chars + return "" # Invalid UUID format for key derivation + + # Generate dynamic indices based on the fixed SEED_DATE (must match acitvation_code.py) + date_seed_value = SEED_YEAR * 10000 + SEED_MONTH * 100 + SEED_DAY + rng = random.Random(date_seed_value) # random was imported at the top + all_possible_indices = list(range(32)) + rng.shuffle(all_possible_indices) + dynamic_indices = sorted(all_possible_indices[:8]) # Take first 8 and sort + + try: + key_chars = [s[i] for i in dynamic_indices] + expected_key = "".join(key_chars).upper() + return expected_key + except IndexError: + # This should not happen if len(s) == 32 and indices are correct + return "" def normalize_code(self, code_str): - """Normalizes user-entered code: uppercase, remove hyphens and spaces.""" + """Normalizes user-entered activation KEY: uppercase, remove hyphens and spaces.""" if not code_str: return "" return code_str.upper().replace("-", "").replace(" ", "") - def format_code_for_display(self, raw_code_str): - """Formats a raw code string (e.g., ABCDEFGH...) into XXXX-XXXX...""" - if not raw_code_str or len(raw_code_str) != CODE_LENGTH: - return raw_code_str # Or handle error - return "-".join(raw_code_str[i:i+4] for i in range(0, len(raw_code_str), 4)) + def format_code_for_display(self, raw_key_str): + """Formats an 8-character raw key string into XXXX-XXXX.""" + if not raw_key_str or len(raw_key_str) != 8: + return raw_key_str # Return as is if not 8 chars + return f"{raw_key_str[:4]}-{raw_key_str[4:]}" def is_activated(self): - """Checks if the application is already activated.""" - if os.path.exists(self.activation_file): - try: - with open(self.activation_file, 'r') as f: - stored_code_normalized = self.normalize_code(f.read().strip()) - expected_code_normalized = self.normalize_code(self._generate_expected_code()) - return stored_code_normalized == expected_code_normalized - except Exception: - return False # Problem reading file, assume not activated - return False + """ + Checks if the application is already activated by checking for the activation file. + The activation file, if present, contains the UUID used for activation. + """ + return os.path.exists(self.activation_file) - def activate(self, entered_code): - """Attempts to activate the application with the given code.""" - normalized_entered_code = self.normalize_code(entered_code) - expected_code = self._generate_expected_code() - normalized_expected_code = self.normalize_code(expected_code) + def activate(self, entered_uuid, entered_key): + """Attempts to activate the application with the given UUID and key.""" + if not entered_uuid or not entered_key: + return False - if normalized_entered_code == normalized_expected_code: + normalized_entered_uuid = entered_uuid.strip().lower() # UUIDs are case-insensitive in hex but usually lowercase + normalized_entered_key = self.normalize_code(entered_key) # Uppercase, no hyphens/spaces + + expected_key = self._generate_expected_key_from_uuid(normalized_entered_uuid) + + if normalized_entered_key == expected_key and expected_key != "": # Ensure expected_key is not empty try: with open(self.activation_file, 'w') as f: - f.write(expected_code) # Store the formatted expected code + f.write(normalized_entered_uuid) # Store the UUID # If activated, trial is no longer relevant, remove trial file if os.path.exists(self.trial_status_file): try: @@ -106,17 +130,25 @@ class ActivationManager: return False def prompt_for_activation(self, master): - """Shows a dialog to prompt for activation code.""" + """Shows dialogs to prompt for UUID and activation key.""" while True: - code = simpledialog.askstring("请激活", "------>请输入激活码以使用小程序<------", parent=master) - if code is None: # User cancelled - return False # Activation failed or cancelled - - if self.activate(code): + uuid_str = simpledialog.askstring("激活 - 步骤 1/2", + "请输入您的 UUID:", + parent=master) + if uuid_str is None: # User cancelled + return False + + key_str = simpledialog.askstring("激活 - 步骤 2/2", + "请输入您的激活密钥 (例如: ABCD-EFGH):", + parent=master) + if key_str is None: # User cancelled + return False + + if self.activate(uuid_str, key_str): messagebox.showinfo("激活成功", "软件已成功激活!感谢使用。", parent=master) return True else: - retry = messagebox.askretrycancel("激活失败", "激活码无效或输入错误,请重试。", parent=master) + retry = messagebox.askretrycancel("激活失败", "UUID 或激活密钥无效,请重试。", parent=master) if not retry: return False # User chose not to retry