From 5f5791cf8ae9d3985e7002f8e69778f3b253c7c7 Mon Sep 17 00:00:00 2001 From: Chaitanya6Nli Date: Mon, 7 Jul 2025 19:20:27 +0530 Subject: [PATCH 001/121] Feat: Add CLI password manager with generation and encryption --- .../cli/password_manager_cli.py | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 Secure_Password_Manager/cli/password_manager_cli.py diff --git a/Secure_Password_Manager/cli/password_manager_cli.py b/Secure_Password_Manager/cli/password_manager_cli.py new file mode 100644 index 0000000..e03070f --- /dev/null +++ b/Secure_Password_Manager/cli/password_manager_cli.py @@ -0,0 +1,114 @@ +import random, datetime, base64, argparse, os +from cryptography.fernet import Fernet + +# πŸ”‘ File where secret key will be stored +KEY_FILE = "secret.key" + +# ------------------------------------------------------ +# STEP 1: Generate a new encryption key (only once)) +def generate_key(): + key = Fernet.generate_key() + with open(KEY_FILE, "wb") as f: + f.write(key) + +# STEP 2: Load the ecryption key +def load_key(): + if not os.path.exists(KEY_FILE): + print("No Key found. Creating a new one...") + generate_key() + with open(KEY_FILE, "rb") as f: + return f.read() + +# STEP 3: Generate password based on user settings +def generate_password(length, use_lower, use_upper, use_digits, use_specials): + lowercase = 'abcdefghijklmnopqrstuvwxyz' + uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + digits = '0123456789' + specials = '!@#$%^&*()' + + # Build character set based on user input + character_set = '' + + if use_lower: + character_set = character_set + lowercase + if use_upper: + character_set = character_set + uppercase + if use_digits: + character_set = character_set + digits + if use_specials: + character_set = character_set + specials + + if not character_set: + return "Error: No character sets selected. Cannot generate password." + + password = '' + for i in range(length): + password = password + random.choice(character_set) + return password + +# STEP 4: Check password strength +def check_strength(length, use_lower, use_upper, use_digits, use_specials): + score = 0 + + # Add points for character variety + score = score + use_lower + use_upper + use_digits + use_specials + + if length >= 12: + score = score + 1 + + if score <= 2: + return "Weak" + elif score == 3 or score == 4: + return "Medium" + else: + return "Strong" + +# STEP 5: Command-line interface using argparse +def main(): + parser = argparse.ArgumentParser(description="πŸ” Password Generator Tool") + + parser.add_argument('--length', type=int, required=True, help='Password length (e.g., 8, 12, 16)') + parser.add_argument('--label', type=str, required=True, help='Purpose or label for the password (e.g, Google, Gmail)') + parser.add_argument('--lower', action='store_true', help='Include lowercase letters') + parser.add_argument('--upper', action='store_true', help='Include uppercase letters') + parser.add_argument('--digits', action='store_true', help='Include digits') + parser.add_argument('--specials', action='store_true', help='Include special characters') + + args = parser.parse_args() + + # Validate length + if args.length <= 0: + print("❌ Password length must be positive. Try again.") + return + + # Generate and evaluate password + password = generate_password(args.length, args.lower, args.upper, args.digits, args.specials) + if password.startswith("Error"): + print(password) + return + + print(f"βœ… Your generated password is: {password}") + strength = check_strength(args.length, args.lower, args.upper, args.digits, args.specials) + print(f"πŸ’ͺ Password Strenght: {strength}") + + # STEP 6: Encrypt password using Fernet + key = load_key() + fernet = Fernet(key) + encrypted_password = fernet.encrypt(password.encode()).decode() # This variable already holds the Fernet encrypted string. + + # STEP 7: Save encrypted password to file + with open("saved_passwords.txt", "a") as file: + timestamp = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S") + # The 'encoded_password' (base64 of original password) was being saved previously. + # We should save the 'encrypted_password' (Fernet encrypted string) instead. + file.write(f"\n[{timestamp}]\n") + file.write(f"Label: {args.label}\n") + file.write(f"Encrypted Password: {encrypted_password}\n") # Save the Fernet encrypted password and use "Encrypted Password:" label + file.write(f"Included - Lowercase: {args.lower}, Uppercase: {args.upper}, Digits: {args.digits}, Special Characters: {args.specials}\n") + file.write("-" * 40 + "\n") + + print("πŸ”’ Password encrypted and saved to 'saved_passwords.txt") + + +if __name__ == '__main__': + main() \ No newline at end of file From 64629627ef77863ed4326238fb1cb82d527b5886 Mon Sep 17 00:00:00 2001 From: Chaitanya6Nli Date: Mon, 7 Jul 2025 19:21:12 +0530 Subject: [PATCH 002/121] Feat: Add view functionality for CLI version --- .../cli/view_passwords_cli.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 Secure_Password_Manager/cli/view_passwords_cli.py diff --git a/Secure_Password_Manager/cli/view_passwords_cli.py b/Secure_Password_Manager/cli/view_passwords_cli.py new file mode 100644 index 0000000..06eefa5 --- /dev/null +++ b/Secure_Password_Manager/cli/view_passwords_cli.py @@ -0,0 +1,48 @@ +import base64 + +def view_passwords(filename="saved_passwords.txt"): + try: + with open(filename, "r") as file: + lines = file.readlines() + + # Temperory variable to store each password block info + timestamp = label = encoded_password = "" + included_options = "" + + print("\nπŸ” Saved Passwords:\n") + + for line in lines: + line = line.strip() + + if line.startswith("["): # Timestamp line + timestamp = line.strip("[]") + + elif line.startswith("Label:"): + label = line.split("Label:")[1].strip() + + elif line.startswith("Encoded Password:"): + encoded_password = line.split("Encoded Password:")[1].strip() + try: + decoded_password = base64.b64decode(encoded_password.encode()).decode() + except Exception as e: + decoded_password = f"[Error decoding password: {e}]" + + elif line.startswith("Included"): + included_options = line.split("Included -")[1].strip() + + elif line.startswith("-" * 10): # Block ends here + print(f"πŸ“… Date/Time: {timestamp}") + print(f"🏷️ Label: {label}") + print(f"πŸ”“ Decoded Password: {decoded_password}") + print(f"πŸ”§ Options Included: {included_options}") + print("-" * 40) + + except FileNotFoundError: + print("❌ saved_passwords.txt not found.") + + except Exception as e: + print(f"❌ An error occured: {e}") + +# Run the function +if __name__ == '__main__': + view_passwords() \ No newline at end of file From c1f0eec3204920cc868ed90db8589121407f2e37 Mon Sep 17 00:00:00 2001 From: Chaitanya6Nli Date: Mon, 7 Jul 2025 19:21:46 +0530 Subject: [PATCH 003/121] Feat: Add decryption support to CLI version --- .../cli/decrypt_passwords_cli.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Secure_Password_Manager/cli/decrypt_passwords_cli.py diff --git a/Secure_Password_Manager/cli/decrypt_passwords_cli.py b/Secure_Password_Manager/cli/decrypt_passwords_cli.py new file mode 100644 index 0000000..8792c7e --- /dev/null +++ b/Secure_Password_Manager/cli/decrypt_passwords_cli.py @@ -0,0 +1,63 @@ +from cryptography import fernet +import os + +KEY_FILE = "secret.key" +PASSWORD_FILE = "saved_passwords.txt" + +# STEP 1: Load the encryption key +def load_key(): + if not os.path.exists(KEY_FILE): + print("❌ Encryption key not found. Cannot decrypt passwords.") + return None + with open(KEY_FILE, "rb") as f: + return f.read() + +# STEP 2: Decrypt the passwords +def decrypt_password(encrypted_text, fernet_cipher_suite): # Renamed parameter for clarity + try: + return fernet_cipher_suite.decrypt(encrypted_text.encode()).decode() + except Exception: + return "[Decryption Failed]" + +# STEP 3: Read and decrypt all entries +def view_passwords(): + if not os.path.exists(PASSWORD_FILE): + print("❌ No saved passwords found") + return + + key = load_key() + if not key: + return + + # Fernet = fernet(key) # Original line causing TypeError + active_fernet_cipher = fernet.Fernet(key) # Correctly instantiate Fernet class from the module + + print("πŸ” Saved Encrypted Passwords\n" + "=" * 40) + with open(PASSWORD_FILE, "r") as file: + lines = file.readlines() + + current_block = {} + + for line in lines: + line = line.strip() + + if line.startswith("[") and "]" in line: + current_block["timestamp"] = line.strip("[]") + elif line.startswith("Label:"): + current_block["label"] = line.split("Label:")[1].strip() + elif line.startswith("Encrypted Password:"): + current_block["encrypted"] = line.split("Encrypted Password:")[1].strip() + elif line.startswith("Included -"): + current_block["options"] = line.split("Included -")[1].strip() + elif line.startswith("-" * 10): + # print everything together + print(f"\nπŸ“… Date/Time: {current_block.get('timestamp', '[Unknown]')}") + print(f"🏷️ Label: {current_block.get('label', '[None]')}") + print(f"πŸ”“ Password: {decrypt_password(current_block.get('encrypted', ''), active_fernet_cipher)}") # Pass the Fernet instance + print(f"βš™οΈ Options: {current_block.get('options', '[Not specified]')}") + print("-" * 40) + current_block = {} # Reset for next block + +# STEP 4: Entry point +if __name__ == '__main__': + view_passwords() \ No newline at end of file From f50f6124301bff327c2cdf26711cef2d26bdca8f Mon Sep 17 00:00:00 2001 From: Chaitanya6Nli Date: Mon, 7 Jul 2025 19:22:33 +0530 Subject: [PATCH 004/121] Feat: Add GUI version using Tkinter for password management --- .../gui/password_manager_gui.py | 258 ++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 Secure_Password_Manager/gui/password_manager_gui.py diff --git a/Secure_Password_Manager/gui/password_manager_gui.py b/Secure_Password_Manager/gui/password_manager_gui.py new file mode 100644 index 0000000..492e7dc --- /dev/null +++ b/Secure_Password_Manager/gui/password_manager_gui.py @@ -0,0 +1,258 @@ +# Import necessary libraries +import os, random, datetime +from tkinter import * +from tkinter import messagebox +from cryptography.fernet import Fernet + +# ------------------------ +# FUNCTION DEFINITIONS +# ------------------------ + +# Generate password based on user options +def generate_password(length, use_lower, use_upper, use_digits, use_specials): + lowercase = 'abcdefghijklmnopqrstuvwxyz' + uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + digits = '0123456789' + specials = '!@#$%^&*()' + + character_set = '' + if use_lower: character_set += lowercase + if use_upper: character_set += uppercase + if use_digits: character_set += digits + if use_specials: character_set += specials + + if not character_set: + return "Error: No character sets selected. Cannot generate password." + + password = ''.join(random.choice(character_set) for _ in range(length)) + return password + +# Check password strength +def check_strength(length, use_lower, use_upper, use_digits, use_specials): + score = use_lower + use_upper + use_digits + use_specials + if length >= 12: + score += 1 + + if score <= 2: + return "Weak" + elif score in (3, 4): + return "Medium" + else: + return "Strong" + +# Load or create encryption key +def load_key(): + key_path = "secret.key" + + if not os.path.exists(key_path): + key = Fernet.generate_key() + with open(key_path, "wb") as key_file: + key_file.write(key) + print("πŸ” Key generated.") + return key + else: + with open(key_path, "rb") as key_file: + key = key_file.read() + + # βœ… Verify it's a valid Fernet key + try: + Fernet(key) # This will raise ValueError if invalid + print("πŸ”‘ Valid key loaded.") + return key + except ValueError: + print("❌ Invalid key detected. Regenerating...") + key = Fernet.generate_key() + with open(key_path, "wb") as key_file: + key_file.write(key) + print("πŸ” New key generated.") + return key + +# Encrypt the password +def encrypt_password(password, key): + fernet = Fernet(key) + return fernet.encrypt(password.encode()).decode() + +# Button click logic +def on_generate_click(): + length_input = password_length_entry.get().strip() + label = purpose_entry.get().strip() or "Unnamed" + lower = use_lower.get() + upper = use_upper.get() + digits = use_digits.get() + specials = use_specials.get() + + # Handle default or invalid input safely + if length_input == "": + length = 12 + elif length_input.isdigit(): + length = int(length_input) + if length <= 0: + messagebox.showerror("Invalid Input", "Password length must be a positive number.") + return + else: + messagebox.showerror("Invalid Input", "Enter a valid number for length.") + return + + # Generate the password + password = generate_password(length, lower, upper, digits, specials) + if password.startswith("Error"): + messagebox.showwarning("Oops", password) + return + + strength = check_strength(length, lower, upper, digits, specials) + + # Show result popup + messagebox.showinfo( + "Password Generated", + f"πŸ” Label: {label}\n\nPassword: {password}\n\nπŸ’ͺ Strength: {strength}" + ) + + # Save to file (encrypted) + key = load_key() + encrypted = encrypt_password(password, key) + + with open("saved_passwords.txt", "a") as file: + timestamp = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S") + file.write(f"\n[{timestamp}]\n") + file.write(f"Label: {label}\n") + file.write(f"Encrypted Password: {encrypted}\n") + file.write(f"Strength: {strength}\n") + file.write("-" * 40 + "\n") + + messagebox.showinfo("βœ… Saved", "Password saved securely to file.") + +def on_view_passwords_click(): + try: + key = load_key() + fernet = Fernet(key) + + # Check if saved passwords file exists + if not os.path.exists("saved_passwords.txt"): + messagebox.showinfo("No Data", "No saved passwords found.") + return + + # Read the file + with open("saved_passwords.txt", "r") as file: + lines = file.readlines() + + display_data = [] + current = {} + + # Parse each line + for line in lines: + line = line.strip() + if line.startswith('['): # timestamp line + current["Time"] = line + elif line.startswith("Label:"): + current["Label"] = line.replace("Label: ", "") + elif line.startswith("Encrypted Password:"): + encrypted = line.replace("Encrypted Password: ", "") + try: + decrypted = fernet.decrypt(encrypted.encode()).decode() + except Exception: + decrypted = "❌ Could not decrypt" + current["Password"] = decrypted + elif line.startswith("Strength:"): + current["Strength"] = line.replace("Strength: ", "") + elif line.startswith("-"): + # end of entry + display_data.append(current) + current = {} + + # If nothing parsed + if not display_data: + messagebox.showinfo("Empty", "No valid entried found.") + return + + # -------------------- + # New GUI Window to Display Saved Passwords + # -------------------- + top = Toplevel(window) + top.title("Saved Passwords") + top.geometry("800x500") + top.config(bg="dark slate gray") + + # Create scrollbar + scrollbar = Scrollbar(top) + scrollbar.pack(side=RIGHT, fill=Y) + + # Create text area and bind it to scrollbar + text = Text(top, wrap=WORD, yscrollcommand=scrollbar.set, font="Consolas 12", bg="black", fg="light green") + text.pack(expand=True, fill=BOTH) + + for entry in display_data: + text.insert(END, f"{entry['Time']}\n") + text.insert(END, f"πŸ”– Label: {entry['Label']}\n") + text.insert(END, f"πŸ” Password: {entry['Password']}\n") + text.insert(END, f"πŸ’ͺ Strength: {entry['Strength']}\n") + text.insert(END, "-" * 40 + "\n\n") + + text.config(state=DISABLED) + scrollbar.config(command=text.yview) + + except Exception as e: + messagebox.showerror("Error", f"Something went wrong:\n{str(e)}") + +# ------------------------ +# GUI LAYOUT +# ------------------------ + +# Create main window +window = Tk() +window.title("Password Book") +icon = PhotoImage(file="icons8-password-book-24.png") +window.iconphoto(False, icon) +window.geometry('900x700') +window.configure(bg='dark slate gray') +window.resizable(False, False) + +# Create main frame +frame = Frame(bg='dark slate gray') + +# Welcome text +welcome_label = Label(frame, text="Welcome to your Password Manager!", bg='dark slate gray', fg="azure", font="fixedsys 30 bold") +welcome_label.grid(row=0, column=0, columnspan=2, sticky="news", pady=40) + +# Label entry +purpose_label = Label(frame, text="Enter a label or purpose for this password:", bg='dark slate gray', fg="ivory2", font=("Arial", 16)) +purpose_label.grid(row=1, column=0) + +purpose_entry = Entry(frame, font=("Arial", 16)) +purpose_entry.grid(row=1, column=1, pady=20) + +# Length entry +password_length_label = Label(frame, text="Enter desired password length:", bg='dark slate gray', fg="ivory2", font=("Arial", 16)) +password_length_label.grid(row=2, column=0) + +password_length_entry = Entry(frame, font=("Arial", 16)) +password_length_entry.grid(row=2, column=1, pady=20) + +# Optional placeholder label (recommended) +default_note = Label(frame, text="*Default is 12 if left empty", bg='dark slate gray', fg="gray80", font=("Arial", 10)) +default_note.grid(row=3, column=1, sticky='w') + +# Character set checkboxes +use_lower = BooleanVar() +use_upper = BooleanVar() +use_digits = BooleanVar() +use_specials = BooleanVar() + +Checkbutton(frame, text="Include lowercase letters", variable=use_lower, width=20, bg="dark slate gray", font=('Helvetica', 12, 'bold'), fg="ivory2", selectcolor="blue").grid(row=4, column=0, columnspan=2, pady=(30, 20)) +Checkbutton(frame, text="Include Uppercase letters", variable=use_upper, width=20, bg="dark slate gray", font=('Helvetica', 12, 'bold'), fg="ivory2", selectcolor="blue").grid(row=5, column=0, columnspan=2, pady=(0, 20)) +Checkbutton(frame, text="Include digits", variable=use_digits, width=20, bg="dark slate gray", font=('Helvetica', 12, 'bold'), fg="ivory2", selectcolor="blue").grid(row=6, column=0, columnspan=2, pady=(0, 20)) +Checkbutton(frame, text="Include special characters", variable=use_specials, width=20, bg="dark slate gray", font=('Helvetica', 12, 'bold'), fg="ivory2", selectcolor="blue").grid(row=7, column=0, columnspan=2, pady=(0, 30)) + +# Generate button +generate_btn = Button(frame, text="Generate Password", bg="slate gray", fg="ivory2", font="Arial 16 bold", command=on_generate_click) +generate_btn.grid(row=8, column=0, columnspan=2, pady=30) + +# View passwords button +view_btn = Button(frame, text="View Saved Passwords", bg="slate gray", fg="ivory2", font="Arial 16 bold") +view_btn.grid(row=9, column=0, columnspan=2) +view_btn.config(command=on_view_passwords_click) + +# Pack the frame +frame.pack() + +# Start the app +window.mainloop() From 02c5c14b2653906c701ad3ae230feffc1ef145ca Mon Sep 17 00:00:00 2001 From: Chaitanya6Nli Date: Mon, 7 Jul 2025 19:23:15 +0530 Subject: [PATCH 005/121] Chore: Add icon for GUI window --- .../icons8-password-book-24.png | Bin 0 -> 356 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Secure_Password_Manager/icons8-password-book-24.png diff --git a/Secure_Password_Manager/icons8-password-book-24.png b/Secure_Password_Manager/icons8-password-book-24.png new file mode 100644 index 0000000000000000000000000000000000000000..508276eae26cfedeb4433ed3c54fd6bf0af3672f GIT binary patch literal 356 zcmV-q0h|7bP)IJdnYid{f+;<3| z?b*T9b2M5LE5KKS#Hhjm4?y#rfF{s7Cm<>Mh$aT}aUOs(sx6=u@Bv(gTr_t?kHA*# zYAcVX>7uzKnyD^p$oSK8(VRiC>asf-uZyTb1T0gEKgbJ+fVYh9s^$Siz`HVB0KdQp zxJ~U*pke@YmH8`hk!2bh^8@pjqRsd5EX94wGT#A?C(fy>Ex!f;0000 literal 0 HcmV?d00001 From e6d0d48d56422b7b2c0d65a2f6bca11bcc599325 Mon Sep 17 00:00:00 2001 From: Chaitanya6Nli Date: Mon, 7 Jul 2025 19:24:15 +0530 Subject: [PATCH 006/121] Docs: Add README with usage and project overview --- Secure_Password_Manager/README | 114 +++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 Secure_Password_Manager/README diff --git a/Secure_Password_Manager/README b/Secure_Password_Manager/README new file mode 100644 index 0000000..6ac955f --- /dev/null +++ b/Secure_Password_Manager/README @@ -0,0 +1,114 @@ +# πŸ” Secure Password Manager (CLI + GUI) + +This is a simple and secure Password Manager built with Python. It includes both a **Tkinter-based GUI (Graphical User Interface)** and a **CLI (Command Line Interface)** version. + +--- + +## πŸ“Œ Features + +* βœ… Generate strong, random passwords +* βœ… Choose what characters to include (lowercase, uppercase, digits, special characters) +* βœ… Real-time password strength feedback +* βœ… Save passwords with labels (e.g., Gmail, Netflix) +* βœ… Encode/Encrypt passwords for security +* βœ… View saved passwords with automatic decryption +* βœ… GUI built using Python's `tkinter` library +* βœ… CLI version with command-line options using `argparse` + +--- + +## πŸ’‘ How It Works + +### Tkinter GUI + +The graphical interface is built using `tkinter`, Python’s standard GUI toolkit. It provides a clean interface for generating, encrypting, and viewing passwords. + +### Generate Password + +1. Choose the password length. +2. Select character types using checkboxes. +3. Hit the **Generate Password** button. +4. The password is displayed with its strength (Weak, Medium, or Strong). +5. The password is then **encrypted** and saved to a file. + +### View Saved Passwords + +1. Click the **View Saved Passwords** button in the GUI. +2. If the encryption key exists, the program decrypts and displays all saved passwords in a pop-up window. +3. If the key is missing or corrupted, an error message is shown. + +--- + +## πŸ› οΈ Technologies Used + +| Component | Tool/Library | +|----------|--------------| +| Programming Language | Python | +| GUI Library | Tkinter | +| Encryption | cryptography (Fernet) | +| Encoding (CLI version) | base64 | +| CLI Parser | argparse | +| Version Control | Git | + +--- + +## πŸ—ƒοΈ Project Structure + +```bash +Password_Manager/ +β”œβ”€β”€ password_manager_gui.py # Tkinter-based GUI application +β”œβ”€β”€ password_manager_cli.py # CLI-based password generator +β”œβ”€β”€ view_passwords_cli.py # View decoded Base64 passwords (CLI) +β”œβ”€β”€ decrypt_passwords_cli.py # Decrypt encrypted passwords (CLI) +β”œβ”€β”€ saved_passwords.txt # File where encrypted passwords are saved +β”œβ”€β”€ secret.key # File containing Fernet encryption key +β”œβ”€β”€ README.md +└── .gitignore +``` + +--- + +## ⚠️ Security Note + +This project uses **Fernet encryption** to protect your saved passwords. + +Files like `secret.key` and `saved_passwords.txt` are excluded from version control using `.gitignore`. + +> Do not share your `secret.key` with anyone. If you lose it, saved passwords cannot be decrypted. + +--- + +## 🧠 Ideal For + +- Python beginners learning `tkinter` +- Students building secure Python projects +- Learning encryption basics with `cryptography` +- Practicing Git and GitHub workflow with versioned features + +--- + +## βš™οΈ Setup Instructions + +1. Clone the repo + `git clone https://github.com/Your-Username/Secure_Password_Manager.git` + +2. Install dependencies + `pip install cryptography` + +3. Run GUI + `python password_manager_gui.py` + +4. Or use CLI + `python password_manager_cli.py --length 12 --label Gmail --lower --upper --digits --specials` + +--- + +## ⭐ GitHub Ready + +This project was built using proper Git practices: + +* Every feature added on a new branch +* Meaningful commit messages +* Clean merge history + +--- From 044b1678c96203e89fe49b7542c3fb2b1edd8a27 Mon Sep 17 00:00:00 2001 From: Chaitanya6Nli Date: Mon, 7 Jul 2025 19:24:59 +0530 Subject: [PATCH 007/121] Chore: Add .gitignore to exclude unnecessary files --- Secure_Password_Manager/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Secure_Password_Manager/.gitignore diff --git a/Secure_Password_Manager/.gitignore b/Secure_Password_Manager/.gitignore new file mode 100644 index 0000000..17334ee --- /dev/null +++ b/Secure_Password_Manager/.gitignore @@ -0,0 +1,2 @@ +saved_passwords.txt +secret.key \ No newline at end of file From 3edbde2cedc48a2177d6bcde064ea8336e044224 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 16 Jul 2025 03:38:11 +0000 Subject: [PATCH 008/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 348a45a..b6bf157 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -178,5 +178,14 @@ "25" ], "demo-path": "Digital_Clock" + }, + "Secure_Password_Manager": { + "contributor-name": [ + "Chaitanya6Nli" + ], + "pull-request-number": [ + "29" + ], + "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Secure_Password_Manager" } } \ No newline at end of file diff --git a/index.md b/index.md index 1729424..448bcce 100644 --- a/index.md +++ b/index.md @@ -29,6 +29,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | 3d_pose_estimation | [mahipalimkar](https://github.com/mahipalimkar "goto mahipalimkar profile") | [#23](https://github.com/Grow-with-Open-Source/Python-Projects/pull/23 "visit pr \#23") | [/Grow-with-Open-Source/Python-Projects/3d_pose_estimation/](3d_pose_estimation "view the result of 3d_pose_estimation") | | Coin-Poison | [niharikah005](https://github.com/niharikah005 "goto niharikah005 profile") | [#24](https://github.com/Grow-with-Open-Source/Python-Projects/pull/24 "visit pr \#24") | [/Grow-with-Open-Source/Python-Projects/Coin-Poison/](Coin-Poison "view the result of Coin-Poison") | | Digital_Clock | [DarkSlayer102](https://github.com/DarkSlayer102 "goto DarkSlayer102 profile") | [#25](https://github.com/Grow-with-Open-Source/Python-Projects/pull/25 "visit pr \#25") | [/Grow-with-Open-Source/Python-Projects/Digital_Clock/](Digital_Clock "view the result of Digital_Clock") | +| Secure_Password_Manager | [Chaitanya6Nli](https://github.com/Chaitanya6Nli "goto Chaitanya6Nli profile") | [#29](https://github.com/Grow-with-Open-Source/Python-Projects/pull/29 "visit pr \#29") | [/Grow-with-Open-Source/Python-Projects/Secure_Password_Manager/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Secure_Password_Manager "view the result of Secure_Password_Manager") | From e444960a47776cc105881defbbcf588153ea17bd Mon Sep 17 00:00:00 2001 From: Shamith Nakka Date: Wed, 16 Jul 2025 09:14:33 +0530 Subject: [PATCH 009/121] Update deploy-gh-pages.yml --- .github/workflows/deploy-gh-pages.yml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-gh-pages.yml b/.github/workflows/deploy-gh-pages.yml index accd813..345d37e 100644 --- a/.github/workflows/deploy-gh-pages.yml +++ b/.github/workflows/deploy-gh-pages.yml @@ -34,15 +34,21 @@ jobs: fi env: REPO_NAME: ${{ github.repository }} + - name: Setup Ruby + uses: ruby/setup-ruby@4a9ddd6f338a97768b8006bf671dfbad383215f4 + with: + ruby-version: '3.1' + bundler-cache: true + cache-version: 0 - name: Setup Pages - uses: actions/configure-pages@v3 + id: pages + uses: actions/configure-pages@v5 - name: Build with Jekyll - uses: actions/jekyll-build-pages@v1 - with: - source: ./ - destination: ./_site + run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" + env: + JEKYLL_ENV: production - name: Upload artifact - uses: actions/upload-pages-artifact@v2 + uses: actions/upload-pages-artifact@v3 # Deployment job deploy: @@ -54,4 +60,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v2 \ No newline at end of file + uses: actions/deploy-pages@v4 From 0cd6634bab6557e6341fdfc47935076744668cf7 Mon Sep 17 00:00:00 2001 From: Shamith Nakka Date: Wed, 16 Jul 2025 09:26:10 +0530 Subject: [PATCH 010/121] Update deploy-gh-pages.yml --- .github/workflows/deploy-gh-pages.yml | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/.github/workflows/deploy-gh-pages.yml b/.github/workflows/deploy-gh-pages.yml index 345d37e..6c69261 100644 --- a/.github/workflows/deploy-gh-pages.yml +++ b/.github/workflows/deploy-gh-pages.yml @@ -34,21 +34,12 @@ jobs: fi env: REPO_NAME: ${{ github.repository }} - - name: Setup Ruby - uses: ruby/setup-ruby@4a9ddd6f338a97768b8006bf671dfbad383215f4 - with: - ruby-version: '3.1' - bundler-cache: true - cache-version: 0 - name: Setup Pages - id: pages uses: actions/configure-pages@v5 - name: Build with Jekyll - run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" - env: - JEKYLL_ENV: production + uses: actions/jekyll-build-pages@v1.0.13 - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v3.0.1 # Deployment job deploy: @@ -60,4 +51,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v4.0.5 From 36f5f0e0e5a457e25defe318e60d63b421567c75 Mon Sep 17 00:00:00 2001 From: veerababu1729 <84015665+veerababu1729@users.noreply.github.com> Date: Tue, 22 Jul 2025 19:35:51 +0530 Subject: [PATCH 011/121] Create app.py This is created by me as part one of the company assesment as first round in their hiring finding best youtube videos using youtube api and Gemini llm --- Youtube_video_frinder_using_GeminiLLM/app.py | 180 +++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 Youtube_video_frinder_using_GeminiLLM/app.py diff --git a/Youtube_video_frinder_using_GeminiLLM/app.py b/Youtube_video_frinder_using_GeminiLLM/app.py new file mode 100644 index 0000000..58aeda4 --- /dev/null +++ b/Youtube_video_frinder_using_GeminiLLM/app.py @@ -0,0 +1,180 @@ +import os +import datetime +from googleapiclient.discovery import build +import google.generativeai as genai + +# β€”β€”β€” CONFIG β€”β€”β€” +# Initialize clients with environment variables +yt = build("youtube", "v3", developerKey=os.environ["YT_API_KEY"]) + +# Configure the Google Generative AI client +genai.configure(api_key=os.environ["GEMINI_API_KEY"]) + +# Initialize the Gemini model +model = genai.GenerativeModel('gemini-1.5-flash-latest') + + +def search_videos(query, max_filtered_results=20): + """ + Search for YouTube videos matching a query, filtering by recency and duration. + + This function keeps searching until it finds enough videos that meet the criteria + or exhausts the search results. + """ + # Calculate publishedAfter timestamp (14 days ago) + fourteen_days_ago = (datetime.datetime.utcnow() + - datetime.timedelta(days=14)).isoformat("T") + "Z" + + filtered_videos = [] + next_page_token = None + page_count = 0 + max_pages = 5 # Limit the number of pages to search to avoid excessive API calls + + # Continue searching until we have enough filtered videos or run out of results + while len(filtered_videos) < max_filtered_results and page_count < max_pages: + # Step 1: Search for videos matching the query + search_response = yt.search().list( + q=query, + part="id,snippet", + type="video", + order="relevance", + publishedAfter=fourteen_days_ago, + maxResults=50, # Maximum allowed by the API + pageToken=next_page_token + ).execute() + + page_count += 1 + + # Step 2: Collect video IDs from this page + video_ids = [item["id"]["videoId"] for item in search_response.get("items", [])] + + # Break if no more videos found + if not video_ids: + break + + # Step 3: Get details for the fetched videos + details = yt.videos().list( + part="contentDetails,snippet", + id=",".join(video_ids) + ).execute() + + # Step 4: Filter by duration (4–20 minutes) + for item in details.get("items", []): + try: + # Parse duration (ISO 8601 format, e.g. "PT5M30S") + dur = item["contentDetails"]["duration"].replace("PT","") + + # Skip videos with hours or without minutes + if "H" in dur or "M" not in dur: + continue + + # Split minutes and seconds + parts = dur.split("M") + mins = int(parts[0]) + secs = parts[1].replace("S","") if len(parts) > 1 else "0" + seconds = int(secs) if secs else 0 + + total_seconds = mins * 60 + seconds + + # Filter by duration (4 to 20 minutes inclusive) + if 4 * 60 <= total_seconds <= 20 * 60: + filtered_videos.append({ + "id": item["id"], + "title": item["snippet"]["title"], + "duration": total_seconds, + "publishedAt": item["snippet"]["publishedAt"] + }) + + # If we've found enough videos, we can stop + if len(filtered_videos) >= max_filtered_results: + break + except Exception as e: + print(f"Could not parse duration for video {item.get('id', 'N/A')}: {e}") + continue + + # Check if there are more pages of results + next_page_token = search_response.get("nextPageToken") + if not next_page_token: + break + + print(f"Found {len(filtered_videos)} qualifying videos so far. Searching next page...") + + print(f"Search completed. Found {len(filtered_videos)} videos meeting criteria.") + return filtered_videos + + +def score_title(title, query): + """Score a video title's relevance to the query using Gemini AI.""" + prompt = ( + f"Query: {query}\n" + f"Title: {title}\n" + "Rate relevance & quality 1–10 (just give the number)." + ) + try: + response = model.generate_content(prompt) + score_text = response.text.strip() + # Try to extract just the number if there's additional text + import re + match = re.search(r'\b([0-9]|10)(\.[0-9]+)?\b', score_text) + if match: + score = float(match.group(0)) + else: + score = float(score_text) + return score + except ValueError: + print(f"Model returned non-numeric score for '{title}': '{score_text}'") + return 5.0 # Default middle score instead of 0 + except Exception as e: + print(f"Error scoring title '{title}': {e}") + if 'response' in locals() and hasattr(response, 'text'): + print(f"API response text: {response.text}") + return 5.0 # Default middle score + + +def pick_best(query, num_results=20): + """ + Find and score the best YouTube videos for a query. + + Args: + query: Search query string + num_results: Number of top videos to return + """ + # Get more videos than we need to ensure we have enough after scoring + vids = search_videos(query, max_filtered_results=max(30, num_results * 1.5)) + + if not vids: + print("No suitable videos found after applying filters.") + return + + # Score each video + print(f"Scoring {len(vids)} videos...") + for i, v in enumerate(vids): + v["score"] = score_title(v["title"], query) + print(f" Scored video {i+1}/{len(vids)}: '{v['title']}' - Score: {v['score']:.2f}") + + # Sort by score in descending order + vids.sort(key=lambda x: x.get("score", 0.0), reverse=True) + + # Print the top num_results + result_count = min(num_results, len(vids)) + print(f"\n--- Top {result_count} Relevant Videos ---") + + for i, video in enumerate(vids[:num_results]): + print(f"\n{i+1}.") + print(f" β€’ Title: {video.get('title', 'N/A')}") + print(f" β€’ URL: https://youtu.be/{video.get('id', 'N/A')}") + print(f" β€’ Score: {video.get('score', 0.0):.2f}") + duration_sec = video.get('duration', 0) + print(f" β€’ Duration: {duration_sec // 60}m{duration_sec % 60:02d}s") + print(f" β€’ Published: {video.get('publishedAt', 'N/A')}") + + +# β€”β€” RUN IT! β€”β€” +if __name__ == "__main__": + # Check if API keys are set + if "YT_API_KEY" not in os.environ or "GEMINI_API_KEY" not in os.environ: + print("Error: YouTube and/or Gemini API keys not set in environment variables.") + else: + user_query = input("Enter your search (voice-to-text or text): ") + # Call pick_best with the desired number of results + pick_best(user_query, num_results=20) From a6901bf1106348f2ff106440721a90760257533d Mon Sep 17 00:00:00 2001 From: veerababu1729 <84015665+veerababu1729@users.noreply.github.com> Date: Tue, 22 Jul 2025 19:55:09 +0530 Subject: [PATCH 012/121] Delete Youtube_video_frinder_using_GeminiLLM directory --- Youtube_video_frinder_using_GeminiLLM/app.py | 180 ------------------- 1 file changed, 180 deletions(-) delete mode 100644 Youtube_video_frinder_using_GeminiLLM/app.py diff --git a/Youtube_video_frinder_using_GeminiLLM/app.py b/Youtube_video_frinder_using_GeminiLLM/app.py deleted file mode 100644 index 58aeda4..0000000 --- a/Youtube_video_frinder_using_GeminiLLM/app.py +++ /dev/null @@ -1,180 +0,0 @@ -import os -import datetime -from googleapiclient.discovery import build -import google.generativeai as genai - -# β€”β€”β€” CONFIG β€”β€”β€” -# Initialize clients with environment variables -yt = build("youtube", "v3", developerKey=os.environ["YT_API_KEY"]) - -# Configure the Google Generative AI client -genai.configure(api_key=os.environ["GEMINI_API_KEY"]) - -# Initialize the Gemini model -model = genai.GenerativeModel('gemini-1.5-flash-latest') - - -def search_videos(query, max_filtered_results=20): - """ - Search for YouTube videos matching a query, filtering by recency and duration. - - This function keeps searching until it finds enough videos that meet the criteria - or exhausts the search results. - """ - # Calculate publishedAfter timestamp (14 days ago) - fourteen_days_ago = (datetime.datetime.utcnow() - - datetime.timedelta(days=14)).isoformat("T") + "Z" - - filtered_videos = [] - next_page_token = None - page_count = 0 - max_pages = 5 # Limit the number of pages to search to avoid excessive API calls - - # Continue searching until we have enough filtered videos or run out of results - while len(filtered_videos) < max_filtered_results and page_count < max_pages: - # Step 1: Search for videos matching the query - search_response = yt.search().list( - q=query, - part="id,snippet", - type="video", - order="relevance", - publishedAfter=fourteen_days_ago, - maxResults=50, # Maximum allowed by the API - pageToken=next_page_token - ).execute() - - page_count += 1 - - # Step 2: Collect video IDs from this page - video_ids = [item["id"]["videoId"] for item in search_response.get("items", [])] - - # Break if no more videos found - if not video_ids: - break - - # Step 3: Get details for the fetched videos - details = yt.videos().list( - part="contentDetails,snippet", - id=",".join(video_ids) - ).execute() - - # Step 4: Filter by duration (4–20 minutes) - for item in details.get("items", []): - try: - # Parse duration (ISO 8601 format, e.g. "PT5M30S") - dur = item["contentDetails"]["duration"].replace("PT","") - - # Skip videos with hours or without minutes - if "H" in dur or "M" not in dur: - continue - - # Split minutes and seconds - parts = dur.split("M") - mins = int(parts[0]) - secs = parts[1].replace("S","") if len(parts) > 1 else "0" - seconds = int(secs) if secs else 0 - - total_seconds = mins * 60 + seconds - - # Filter by duration (4 to 20 minutes inclusive) - if 4 * 60 <= total_seconds <= 20 * 60: - filtered_videos.append({ - "id": item["id"], - "title": item["snippet"]["title"], - "duration": total_seconds, - "publishedAt": item["snippet"]["publishedAt"] - }) - - # If we've found enough videos, we can stop - if len(filtered_videos) >= max_filtered_results: - break - except Exception as e: - print(f"Could not parse duration for video {item.get('id', 'N/A')}: {e}") - continue - - # Check if there are more pages of results - next_page_token = search_response.get("nextPageToken") - if not next_page_token: - break - - print(f"Found {len(filtered_videos)} qualifying videos so far. Searching next page...") - - print(f"Search completed. Found {len(filtered_videos)} videos meeting criteria.") - return filtered_videos - - -def score_title(title, query): - """Score a video title's relevance to the query using Gemini AI.""" - prompt = ( - f"Query: {query}\n" - f"Title: {title}\n" - "Rate relevance & quality 1–10 (just give the number)." - ) - try: - response = model.generate_content(prompt) - score_text = response.text.strip() - # Try to extract just the number if there's additional text - import re - match = re.search(r'\b([0-9]|10)(\.[0-9]+)?\b', score_text) - if match: - score = float(match.group(0)) - else: - score = float(score_text) - return score - except ValueError: - print(f"Model returned non-numeric score for '{title}': '{score_text}'") - return 5.0 # Default middle score instead of 0 - except Exception as e: - print(f"Error scoring title '{title}': {e}") - if 'response' in locals() and hasattr(response, 'text'): - print(f"API response text: {response.text}") - return 5.0 # Default middle score - - -def pick_best(query, num_results=20): - """ - Find and score the best YouTube videos for a query. - - Args: - query: Search query string - num_results: Number of top videos to return - """ - # Get more videos than we need to ensure we have enough after scoring - vids = search_videos(query, max_filtered_results=max(30, num_results * 1.5)) - - if not vids: - print("No suitable videos found after applying filters.") - return - - # Score each video - print(f"Scoring {len(vids)} videos...") - for i, v in enumerate(vids): - v["score"] = score_title(v["title"], query) - print(f" Scored video {i+1}/{len(vids)}: '{v['title']}' - Score: {v['score']:.2f}") - - # Sort by score in descending order - vids.sort(key=lambda x: x.get("score", 0.0), reverse=True) - - # Print the top num_results - result_count = min(num_results, len(vids)) - print(f"\n--- Top {result_count} Relevant Videos ---") - - for i, video in enumerate(vids[:num_results]): - print(f"\n{i+1}.") - print(f" β€’ Title: {video.get('title', 'N/A')}") - print(f" β€’ URL: https://youtu.be/{video.get('id', 'N/A')}") - print(f" β€’ Score: {video.get('score', 0.0):.2f}") - duration_sec = video.get('duration', 0) - print(f" β€’ Duration: {duration_sec // 60}m{duration_sec % 60:02d}s") - print(f" β€’ Published: {video.get('publishedAt', 'N/A')}") - - -# β€”β€” RUN IT! β€”β€” -if __name__ == "__main__": - # Check if API keys are set - if "YT_API_KEY" not in os.environ or "GEMINI_API_KEY" not in os.environ: - print("Error: YouTube and/or Gemini API keys not set in environment variables.") - else: - user_query = input("Enter your search (voice-to-text or text): ") - # Call pick_best with the desired number of results - pick_best(user_query, num_results=20) From afb481bc3bf636bf79b1ae1f559b6853742bd6a0 Mon Sep 17 00:00:00 2001 From: veerababu1729 <84015665+veerababu1729@users.noreply.github.com> Date: Tue, 22 Jul 2025 19:59:05 +0530 Subject: [PATCH 013/121] Create app.py Developed by me as a company hiring assesment project to findbest youtube videos using yt api and gemini llm at backend --- Youtube_video_finder_using_geminillm/app.py | 180 ++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 Youtube_video_finder_using_geminillm/app.py diff --git a/Youtube_video_finder_using_geminillm/app.py b/Youtube_video_finder_using_geminillm/app.py new file mode 100644 index 0000000..58aeda4 --- /dev/null +++ b/Youtube_video_finder_using_geminillm/app.py @@ -0,0 +1,180 @@ +import os +import datetime +from googleapiclient.discovery import build +import google.generativeai as genai + +# β€”β€”β€” CONFIG β€”β€”β€” +# Initialize clients with environment variables +yt = build("youtube", "v3", developerKey=os.environ["YT_API_KEY"]) + +# Configure the Google Generative AI client +genai.configure(api_key=os.environ["GEMINI_API_KEY"]) + +# Initialize the Gemini model +model = genai.GenerativeModel('gemini-1.5-flash-latest') + + +def search_videos(query, max_filtered_results=20): + """ + Search for YouTube videos matching a query, filtering by recency and duration. + + This function keeps searching until it finds enough videos that meet the criteria + or exhausts the search results. + """ + # Calculate publishedAfter timestamp (14 days ago) + fourteen_days_ago = (datetime.datetime.utcnow() + - datetime.timedelta(days=14)).isoformat("T") + "Z" + + filtered_videos = [] + next_page_token = None + page_count = 0 + max_pages = 5 # Limit the number of pages to search to avoid excessive API calls + + # Continue searching until we have enough filtered videos or run out of results + while len(filtered_videos) < max_filtered_results and page_count < max_pages: + # Step 1: Search for videos matching the query + search_response = yt.search().list( + q=query, + part="id,snippet", + type="video", + order="relevance", + publishedAfter=fourteen_days_ago, + maxResults=50, # Maximum allowed by the API + pageToken=next_page_token + ).execute() + + page_count += 1 + + # Step 2: Collect video IDs from this page + video_ids = [item["id"]["videoId"] for item in search_response.get("items", [])] + + # Break if no more videos found + if not video_ids: + break + + # Step 3: Get details for the fetched videos + details = yt.videos().list( + part="contentDetails,snippet", + id=",".join(video_ids) + ).execute() + + # Step 4: Filter by duration (4–20 minutes) + for item in details.get("items", []): + try: + # Parse duration (ISO 8601 format, e.g. "PT5M30S") + dur = item["contentDetails"]["duration"].replace("PT","") + + # Skip videos with hours or without minutes + if "H" in dur or "M" not in dur: + continue + + # Split minutes and seconds + parts = dur.split("M") + mins = int(parts[0]) + secs = parts[1].replace("S","") if len(parts) > 1 else "0" + seconds = int(secs) if secs else 0 + + total_seconds = mins * 60 + seconds + + # Filter by duration (4 to 20 minutes inclusive) + if 4 * 60 <= total_seconds <= 20 * 60: + filtered_videos.append({ + "id": item["id"], + "title": item["snippet"]["title"], + "duration": total_seconds, + "publishedAt": item["snippet"]["publishedAt"] + }) + + # If we've found enough videos, we can stop + if len(filtered_videos) >= max_filtered_results: + break + except Exception as e: + print(f"Could not parse duration for video {item.get('id', 'N/A')}: {e}") + continue + + # Check if there are more pages of results + next_page_token = search_response.get("nextPageToken") + if not next_page_token: + break + + print(f"Found {len(filtered_videos)} qualifying videos so far. Searching next page...") + + print(f"Search completed. Found {len(filtered_videos)} videos meeting criteria.") + return filtered_videos + + +def score_title(title, query): + """Score a video title's relevance to the query using Gemini AI.""" + prompt = ( + f"Query: {query}\n" + f"Title: {title}\n" + "Rate relevance & quality 1–10 (just give the number)." + ) + try: + response = model.generate_content(prompt) + score_text = response.text.strip() + # Try to extract just the number if there's additional text + import re + match = re.search(r'\b([0-9]|10)(\.[0-9]+)?\b', score_text) + if match: + score = float(match.group(0)) + else: + score = float(score_text) + return score + except ValueError: + print(f"Model returned non-numeric score for '{title}': '{score_text}'") + return 5.0 # Default middle score instead of 0 + except Exception as e: + print(f"Error scoring title '{title}': {e}") + if 'response' in locals() and hasattr(response, 'text'): + print(f"API response text: {response.text}") + return 5.0 # Default middle score + + +def pick_best(query, num_results=20): + """ + Find and score the best YouTube videos for a query. + + Args: + query: Search query string + num_results: Number of top videos to return + """ + # Get more videos than we need to ensure we have enough after scoring + vids = search_videos(query, max_filtered_results=max(30, num_results * 1.5)) + + if not vids: + print("No suitable videos found after applying filters.") + return + + # Score each video + print(f"Scoring {len(vids)} videos...") + for i, v in enumerate(vids): + v["score"] = score_title(v["title"], query) + print(f" Scored video {i+1}/{len(vids)}: '{v['title']}' - Score: {v['score']:.2f}") + + # Sort by score in descending order + vids.sort(key=lambda x: x.get("score", 0.0), reverse=True) + + # Print the top num_results + result_count = min(num_results, len(vids)) + print(f"\n--- Top {result_count} Relevant Videos ---") + + for i, video in enumerate(vids[:num_results]): + print(f"\n{i+1}.") + print(f" β€’ Title: {video.get('title', 'N/A')}") + print(f" β€’ URL: https://youtu.be/{video.get('id', 'N/A')}") + print(f" β€’ Score: {video.get('score', 0.0):.2f}") + duration_sec = video.get('duration', 0) + print(f" β€’ Duration: {duration_sec // 60}m{duration_sec % 60:02d}s") + print(f" β€’ Published: {video.get('publishedAt', 'N/A')}") + + +# β€”β€” RUN IT! β€”β€” +if __name__ == "__main__": + # Check if API keys are set + if "YT_API_KEY" not in os.environ or "GEMINI_API_KEY" not in os.environ: + print("Error: YouTube and/or Gemini API keys not set in environment variables.") + else: + user_query = input("Enter your search (voice-to-text or text): ") + # Call pick_best with the desired number of results + pick_best(user_query, num_results=20) From 658ae5b50d8fafac6640be8ff68cc1ae351f5ac9 Mon Sep 17 00:00:00 2001 From: veerababu1729 <84015665+veerababu1729@users.noreply.github.com> Date: Tue, 22 Jul 2025 21:57:12 +0530 Subject: [PATCH 014/121] Add files via upload --- .../README.md | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 Youtube_video_finder_using_geminillm/README.md diff --git a/Youtube_video_finder_using_geminillm/README.md b/Youtube_video_finder_using_geminillm/README.md new file mode 100644 index 0000000..fe00150 --- /dev/null +++ b/Youtube_video_finder_using_geminillm/README.md @@ -0,0 +1,65 @@ +"This is a Open source project initiated by me -you can fork, edit, and make a pull request" + +````markdown +# YouTube Relevance Finder with Gemini AI + +This Python script searches YouTube for recent videos based on a user query and ranks them by relevance using Google's Gemini AI model. It filters results by duration and recency, scores video titles for relevance, and returns the top-ranked videos. + +## πŸ” Features + +- Searches YouTube for videos from the past 14 days +- Filters videos by duration (4–20 minutes) +- Uses Gemini AI to score title relevance to a query +- Prints the top relevant video links with scores and metadata + +## πŸ› οΈ Setup + +1. **Clone the repository**: + ```bash + git clone https://github.com/yourusername/your-repo-name.git + cd your-repo-name +```` + +2. **Install dependencies**: + + ```bash + pip install google-api-python-client google-generativeai + ``` + +3. **Set up environment variables**: + Create a `.env` file or export in terminal: + + ```bash + export YT_API_KEY=your_youtube_api_key + export GEMINI_API_KEY=your_gemini_api_key + ``` + +## πŸš€ Usage + +Run the script: + +```bash +python your_script_name.py +``` + +You'll be prompted to enter a search query. The script will then display a list of the top relevant YouTube videos based on that query. + +## πŸ“„ Example Output + +``` +1. + β€’ Title: Learn Python in 10 Minutes + β€’ URL: https://youtu.be/xyz123 + β€’ Score: 9.2 + β€’ Duration: 10m30s + β€’ Published: 2025-05-01T12:34:56Z +``` + +## πŸ“Œ Notes + +* Make sure you have valid API keys for both YouTube Data API v3 and Google Gemini. +* The script currently uses the `gemini-1.5-flash-latest` model. + +## πŸ“ƒ License + +Open source – feel free to use and modify From 5a4692562c7f865509e439758570d57a4b972d33 Mon Sep 17 00:00:00 2001 From: veerababu1729 <84015665+veerababu1729@users.noreply.github.com> Date: Tue, 22 Jul 2025 22:00:16 +0530 Subject: [PATCH 015/121] updated readme with proper, correct explanation and workflow "Mmentioned about Youtube API" --- Youtube_video_finder_using_geminillm/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Youtube_video_finder_using_geminillm/README.md b/Youtube_video_finder_using_geminillm/README.md index fe00150..874dcd1 100644 --- a/Youtube_video_finder_using_geminillm/README.md +++ b/Youtube_video_finder_using_geminillm/README.md @@ -3,11 +3,11 @@ ````markdown # YouTube Relevance Finder with Gemini AI -This Python script searches YouTube for recent videos based on a user query and ranks them by relevance using Google's Gemini AI model. It filters results by duration and recency, scores video titles for relevance, and returns the top-ranked videos. +This Python script searches YouTube for recent videos based on a user query and ranks them by relevance using Google's Gemini AI model and Youtube API. It filters results by duration and recency, scores video titles for relevance, and returns the top-ranked videos. ## πŸ” Features -- Searches YouTube for videos from the past 14 days +- Searches YouTube for videos from the past 14 days using Youtube API which is publicly available. - Filters videos by duration (4–20 minutes) - Uses Gemini AI to score title relevance to a query - Prints the top relevant video links with scores and metadata From f991a1f119aed926d1e33bff76e2e47a31a242c5 Mon Sep 17 00:00:00 2001 From: veerababu1729 <84015665+veerababu1729@users.noreply.github.com> Date: Thu, 7 Aug 2025 08:44:07 +0530 Subject: [PATCH 016/121] Update Youtube_video_finder_using_geminillm/README.md Co-authored-by: Shamith Nakka --- Youtube_video_finder_using_geminillm/README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/Youtube_video_finder_using_geminillm/README.md b/Youtube_video_finder_using_geminillm/README.md index 874dcd1..614ec61 100644 --- a/Youtube_video_finder_using_geminillm/README.md +++ b/Youtube_video_finder_using_geminillm/README.md @@ -1,6 +1,3 @@ -"This is a Open source project initiated by me -you can fork, edit, and make a pull request" - -````markdown # YouTube Relevance Finder with Gemini AI This Python script searches YouTube for recent videos based on a user query and ranks them by relevance using Google's Gemini AI model and Youtube API. It filters results by duration and recency, scores video titles for relevance, and returns the top-ranked videos. From 8aa32e49468518c5d9e0f5a7ae85409035dee4fc Mon Sep 17 00:00:00 2001 From: veerababu1729 <84015665+veerababu1729@users.noreply.github.com> Date: Thu, 7 Aug 2025 08:44:25 +0530 Subject: [PATCH 017/121] Update Youtube_video_finder_using_geminillm/README.md Co-authored-by: Shamith Nakka --- Youtube_video_finder_using_geminillm/README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/Youtube_video_finder_using_geminillm/README.md b/Youtube_video_finder_using_geminillm/README.md index 614ec61..5837c16 100644 --- a/Youtube_video_finder_using_geminillm/README.md +++ b/Youtube_video_finder_using_geminillm/README.md @@ -57,6 +57,3 @@ You'll be prompted to enter a search query. The script will then display a list * Make sure you have valid API keys for both YouTube Data API v3 and Google Gemini. * The script currently uses the `gemini-1.5-flash-latest` model. -## πŸ“ƒ License - -Open source – feel free to use and modify From 6bb5880b1d187a4a1b9503e81478601935ef8acc Mon Sep 17 00:00:00 2001 From: veerababu1729 <84015665+veerababu1729@users.noreply.github.com> Date: Thu, 7 Aug 2025 08:46:20 +0530 Subject: [PATCH 018/121] Update Youtube_video_finder_using_geminillm/app.py Co-authored-by: Shamith Nakka --- Youtube_video_finder_using_geminillm/app.py | 450 +++++++++++++------- 1 file changed, 294 insertions(+), 156 deletions(-) diff --git a/Youtube_video_finder_using_geminillm/app.py b/Youtube_video_finder_using_geminillm/app.py index 58aeda4..f08df6f 100644 --- a/Youtube_video_finder_using_geminillm/app.py +++ b/Youtube_video_finder_using_geminillm/app.py @@ -1,180 +1,318 @@ +#!/usr/bin/env python3 + import os -import datetime +import re +from typing import Dict +from datetime import datetime, timedelta, timezone from googleapiclient.discovery import build import google.generativeai as genai -# β€”β€”β€” CONFIG β€”β€”β€” -# Initialize clients with environment variables -yt = build("youtube", "v3", developerKey=os.environ["YT_API_KEY"]) - -# Configure the Google Generative AI client -genai.configure(api_key=os.environ["GEMINI_API_KEY"]) -# Initialize the Gemini model -model = genai.GenerativeModel('gemini-1.5-flash-latest') +# β€”β€”β€” ENV variables β€”β€”β€” +YT_API_KEY = os.environ.get('YT_API_KEY') +GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY') + +# β€”β€”β€” CONSTANTS β€”β€”β€” +SERVICE_TYPE = 'youtube' +SERVICE_VERSION = 'v3' +MODEL_NAME = 'gemini-1.5-flash-latest' + +DEFAULT_MAX_API_CALLS = 5 +DEFAULT_MAX_RESULTS_PER_PAGE = 50 +DEFAULT_MAX_RESULTS = 20 +DEFAULT_MIN_VIDEO_DURATION_MINUTES = 10 +DEFAULT_MAX_VIDEO_DURATION_MINUTES = 60 +DEFAULT_NO_OF_PREV_DAYS = 14 + +DEFAULT_MAX_RESULTS = 5 +REGEX_PATTERN = r'\b(10|[1-9](\.\d+)?|0?\.\d+)\b' +DEFAULT_SCORE = 5.0 + + +class TimeUtils: + @staticmethod + def get_timestamp_n_days_from_now(days: int) -> str: + """ + Get the timestamp for a date n days ago in ISO 8601 format. + """ + date_before_n_days = datetime.now(timezone.utc) - timedelta(days=days) + formatted_date = date_before_n_days \ + .isoformat('T') \ + .replace('+00:00', 'Z') + return formatted_date + + @staticmethod + def is_duration_in_mins(duration: str) -> bool: + """ + Check if the duration is in minutes. + """ + return 'H' in duration or 'M' not in duration + + @staticmethod + def derive_total_seconds_from_duration(duration: str) -> int: + """ + Derive total seconds from duration (ISO 8601 format, e.g. "PT5M30S"). + """ + parts = duration.split('M') + mins = int(parts[0]) + secs = parts[1].replace('S', '') if len(parts) > 1 else '0' + seconds = int(secs) if secs else 0 + total_seconds = mins * 60 + seconds + return total_seconds + + @staticmethod + def is_video_duration_in_range( + total_seconds: int, + *, + min_duration: int = DEFAULT_MIN_VIDEO_DURATION_MINUTES, + max_duration: int = DEFAULT_MAX_VIDEO_DURATION_MINUTES) -> bool: + """ + Check if the video duration is within the specified range in minutes. + """ + return min_duration * 60 <= total_seconds <= max_duration * 60 + + +class VideoDetailsExtractor: + """ + A class to encapsulate YouTube video extraction logic. + This class can be extended or modified for more complex behaviors. + """ + __platform_conn = build( + serviceName=SERVICE_TYPE, + version=SERVICE_VERSION, + developerKey=YT_API_KEY + ) -def search_videos(query, max_filtered_results=20): - """ - Search for YouTube videos matching a query, filtering by recency and duration. + def __init__( + self, + query: str, + *, + no_prev_days: int = DEFAULT_NO_OF_PREV_DAYS, + max_pages: int = DEFAULT_MAX_API_CALLS, + max_results: int = DEFAULT_MAX_RESULTS) -> None: + """ + Initialize the VideoDetailsExtractor. + """ + self.__filtered_videos = [] + self.__next_page_token = None + self.__page_count = 0 + self.__max_pages = max_pages + + self.query = query + self.__targeted_date = TimeUtils \ + .get_timestamp_n_days_from_now(no_prev_days) + self.__search_response = self.get_new_search_response() + self.__max_results = max_results + + self.scan_videos() + + def get_new_search_response(self) -> dict: + """ + Fetch a new search response for the given query. + """ + search_config = { + "q": self.query, + "part": "id,snippet", + "type": "video", + "order": "relevance", + "publishedAfter": self.__targeted_date, + "maxResults": DEFAULT_MAX_RESULTS_PER_PAGE, + "pageToken": self.__next_page_token + } + + new_search_response = VideoDetailsExtractor.__platform_conn \ + .search() \ + .list(**search_config) \ + .execute() + + self.__page_count += 1 + + return new_search_response + + def get_video_ids_from_search_response(self) -> list: + """ + Extract video IDs from the search response. + """ + items_list = self.__search_response.get('items', []) + return [item['id']['videoId'] for item in items_list] + + def filter_videos(self) -> None: + """ + Filter videos based on duration and recency. + This method processes the search response to filter videos that meet the criteria. + """ + video_ids = self.get_video_ids_from_search_response() - This function keeps searching until it finds enough videos that meet the criteria - or exhausts the search results. - """ - # Calculate publishedAfter timestamp (14 days ago) - fourteen_days_ago = (datetime.datetime.utcnow() - - datetime.timedelta(days=14)).isoformat("T") + "Z" - - filtered_videos = [] - next_page_token = None - page_count = 0 - max_pages = 5 # Limit the number of pages to search to avoid excessive API calls - - # Continue searching until we have enough filtered videos or run out of results - while len(filtered_videos) < max_filtered_results and page_count < max_pages: - # Step 1: Search for videos matching the query - search_response = yt.search().list( - q=query, - part="id,snippet", - type="video", - order="relevance", - publishedAfter=fourteen_days_ago, - maxResults=50, # Maximum allowed by the API - pageToken=next_page_token - ).execute() - - page_count += 1 - - # Step 2: Collect video IDs from this page - video_ids = [item["id"]["videoId"] for item in search_response.get("items", [])] - - # Break if no more videos found if not video_ids: - break + print("No video IDs found in the search response.") + return + + details_config = { + "part": "contentDetails,snippet", + "id": ",".join(video_ids) + } - # Step 3: Get details for the fetched videos - details = yt.videos().list( - part="contentDetails,snippet", - id=",".join(video_ids) - ).execute() + details = VideoDetailsExtractor.__platform_conn \ + .videos() \ + .list(**details_config) \ + .execute() - # Step 4: Filter by duration (4–20 minutes) - for item in details.get("items", []): + for item in details.get('items', []): try: - # Parse duration (ISO 8601 format, e.g. "PT5M30S") - dur = item["contentDetails"]["duration"].replace("PT","") + duration = item['contentDetails']['duration'].replace('PT', '') - # Skip videos with hours or without minutes - if "H" in dur or "M" not in dur: + if TimeUtils.is_duration_in_mins(duration): continue - # Split minutes and seconds - parts = dur.split("M") - mins = int(parts[0]) - secs = parts[1].replace("S","") if len(parts) > 1 else "0" - seconds = int(secs) if secs else 0 - - total_seconds = mins * 60 + seconds - - # Filter by duration (4 to 20 minutes inclusive) - if 4 * 60 <= total_seconds <= 20 * 60: - filtered_videos.append({ - "id": item["id"], - "title": item["snippet"]["title"], - "duration": total_seconds, - "publishedAt": item["snippet"]["publishedAt"] - }) - - # If we've found enough videos, we can stop - if len(filtered_videos) >= max_filtered_results: + total_seconds = TimeUtils \ + .derive_total_seconds_from_duration(duration) + + if TimeUtils.is_video_duration_in_range(total_seconds): + video_details = { + 'id': item['id'], + 'title': item['snippet']['title'], + 'duration': total_seconds, + 'publishedAt': item['snippet']['publishedAt'] + } + self.__filtered_videos.append(video_details) + + if len(self.__filtered_videos) >= DEFAULT_MAX_RESULTS: break + except Exception as e: - print(f"Could not parse duration for video {item.get('id', 'N/A')}: {e}") + print(f"Error processing video {item.get('id', 'N/A')}: {e}") continue - # Check if there are more pages of results - next_page_token = search_response.get("nextPageToken") - if not next_page_token: - break + print( + f"Filtered {len(self.__filtered_videos)} videos based on criteria.") + + def has_filtered_videos_reached_limit(self) -> bool: + """ + Check if the maximum number of filtered videos has been reached. + """ + return len(self.__filtered_videos) < self.__max_results + + def has_page_token_reached_limit(self) -> bool: + """ + Check if the maximum number of API calls has been reached. + """ + return self.__page_count >= self.__max_pages + + def update_next_page_token(self) -> None: + """ + Update the next page token based on the search response. + """ + self.__next_page_token = \ + self.__search_response.get('nextPageToken', None) + + def scan_videos(self) -> None: + """ + Scan for videos that meet the specified criteria. + This method keeps searching until it finds enough videos that meet the criteria + or exhausts the search results. + """ + while self.has_filtered_videos_reached_limit() and self.has_page_token_reached_limit(): + self.__search_response = self.get_new_search_response() + self.filter_videos() + self.update_next_page_token() + if not self.__next_page_token: + break + + def get_video_details(self) -> list: + """ + Fetch video details for a list of filtered video based that were previously computed. + """ + if not self.__filtered_videos: + print('No suitable videos found after applying filters.') + return self.__filtered_videos + + +class GenModel: + """ + A class to encapsulate the Gemini model for scoring video titles. + This class can be extended or modified for more complex behaviors. + """ + _model = None + + @classmethod + def _initialize_model(cls): + """ + Initialize the Gemini model if it hasn't been initialized yet. + """ + if cls._model is None: + genai.configure(api_key=GEMINI_API_KEY) + cls._model = genai.GenerativeModel(MODEL_NAME) + + @staticmethod + def get_prompt_for_title(title: str, query: str) -> str: + """ + Generate a prompt for the Gemini model to score the title based on the query. + """ + return ( + f"Query: {query}\n" + f"Title: {title}\n" + "Rate relevance & quality 1–10 (just give the number)." + ) + + @classmethod + def get_score_for_title(cls, title: str, query: str) -> float: + """ + Get the score for a video title based on the query using the Gemini model. + If the model is not initialized, it will initialize it first. + If the score cannot be parsed, it returns a default score. + """ + cls._initialize_model() + prompt = cls.get_prompt_for_title(title, query) + try: + response = cls._model.generate_content(prompt) + score_text = response.text.strip() + match = re.search(REGEX_PATTERN, score_text) + return float(match.group()) if match else DEFAULT_SCORE + except (ValueError, AttributeError) as e: + print(f"[Error] Failed to parse score for '{title}': {e}") + return DEFAULT_SCORE + except Exception as e: + print(f"[Error] API call failed for '{title}': {e}") + return DEFAULT_SCORE + + +class VideoProcessor: + """ + A class to process video details and rank them based on a scoring model. + This class can be extended or modified for more complex behaviors. + """ + def __init__(self, scorer=GenModel): + """ + Initialize the VideoProcessor with a scoring model. + """ + self.scorer = scorer - print(f"Found {len(filtered_videos)} qualifying videos so far. Searching next page...") + def find_and_rank_videos(self, query: str, num_results: int = DEFAULT_MAX_RESULTS): + """ + Find and rank videos based on the query. + This method uses the VideoDetailsExtractor to find videos and the scoring model to rank them. + """ + videos = VideoDetailsExtractor(query).get_video_details() + if not videos: + return [] - print(f"Search completed. Found {len(filtered_videos)} videos meeting criteria.") - return filtered_videos + for video in videos: + video['score'] = self.scorer.get_score_for_title( + video['title'], query) + return sorted(videos, key=lambda v: v['score'], reverse=True)[:num_results] -def score_title(title, query): - """Score a video title's relevance to the query using Gemini AI.""" - prompt = ( - f"Query: {query}\n" - f"Title: {title}\n" - "Rate relevance & quality 1–10 (just give the number)." - ) - try: - response = model.generate_content(prompt) - score_text = response.text.strip() - # Try to extract just the number if there's additional text - import re - match = re.search(r'\b([0-9]|10)(\.[0-9]+)?\b', score_text) - if match: - score = float(match.group(0)) - else: - score = float(score_text) - return score - except ValueError: - print(f"Model returned non-numeric score for '{title}': '{score_text}'") - return 5.0 # Default middle score instead of 0 - except Exception as e: - print(f"Error scoring title '{title}': {e}") - if 'response' in locals() and hasattr(response, 'text'): - print(f"API response text: {response.text}") - return 5.0 # Default middle score - - -def pick_best(query, num_results=20): - """ - Find and score the best YouTube videos for a query. - Args: - query: Search query string - num_results: Number of top videos to return - """ - # Get more videos than we need to ensure we have enough after scoring - vids = search_videos(query, max_filtered_results=max(30, num_results * 1.5)) - - if not vids: - print("No suitable videos found after applying filters.") - return - - # Score each video - print(f"Scoring {len(vids)} videos...") - for i, v in enumerate(vids): - v["score"] = score_title(v["title"], query) - print(f" Scored video {i+1}/{len(vids)}: '{v['title']}' - Score: {v['score']:.2f}") - - # Sort by score in descending order - vids.sort(key=lambda x: x.get("score", 0.0), reverse=True) - - # Print the top num_results - result_count = min(num_results, len(vids)) - print(f"\n--- Top {result_count} Relevant Videos ---") - - for i, video in enumerate(vids[:num_results]): - print(f"\n{i+1}.") - print(f" β€’ Title: {video.get('title', 'N/A')}") - print(f" β€’ URL: https://youtu.be/{video.get('id', 'N/A')}") - print(f" β€’ Score: {video.get('score', 0.0):.2f}") - duration_sec = video.get('duration', 0) - print(f" β€’ Duration: {duration_sec // 60}m{duration_sec % 60:02d}s") - print(f" β€’ Published: {video.get('publishedAt', 'N/A')}") - - -# β€”β€” RUN IT! β€”β€” -if __name__ == "__main__": - # Check if API keys are set - if "YT_API_KEY" not in os.environ or "GEMINI_API_KEY" not in os.environ: - print("Error: YouTube and/or Gemini API keys not set in environment variables.") - else: - user_query = input("Enter your search (voice-to-text or text): ") - # Call pick_best with the desired number of results - pick_best(user_query, num_results=20) +if __name__ == '__main__': + required_env_vars = ['YT_API_KEY', 'GEMINI_API_KEY'] + + if any([env_var not in os.environ for env_var in required_env_vars]): + raise KeyError( + "Error: YouTube and/or Gemini API keys not set in environment variables.") + + user_query = input("Enter your search: ") + + video_processor = VideoProcessor() + pick_best = video_processor.find_and_rank_videos(user_query) + From 3072b8586f482dc6fa62cf41e000a4ce47750955 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 7 Aug 2025 04:19:43 +0000 Subject: [PATCH 019/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index b6bf157..9e580cf 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -187,5 +187,14 @@ "29" ], "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Secure_Password_Manager" + }, + "Youtube_video_finder_using_geminillm": { + "contributor-name": [ + "veerababu1729" + ], + "pull-request-number": [ + "32" + ], + "demo-path": "Youtube_video_finder_using_geminillm" } } \ No newline at end of file diff --git a/index.md b/index.md index 448bcce..ce4903f 100644 --- a/index.md +++ b/index.md @@ -30,6 +30,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Coin-Poison | [niharikah005](https://github.com/niharikah005 "goto niharikah005 profile") | [#24](https://github.com/Grow-with-Open-Source/Python-Projects/pull/24 "visit pr \#24") | [/Grow-with-Open-Source/Python-Projects/Coin-Poison/](Coin-Poison "view the result of Coin-Poison") | | Digital_Clock | [DarkSlayer102](https://github.com/DarkSlayer102 "goto DarkSlayer102 profile") | [#25](https://github.com/Grow-with-Open-Source/Python-Projects/pull/25 "visit pr \#25") | [/Grow-with-Open-Source/Python-Projects/Digital_Clock/](Digital_Clock "view the result of Digital_Clock") | | Secure_Password_Manager | [Chaitanya6Nli](https://github.com/Chaitanya6Nli "goto Chaitanya6Nli profile") | [#29](https://github.com/Grow-with-Open-Source/Python-Projects/pull/29 "visit pr \#29") | [/Grow-with-Open-Source/Python-Projects/Secure_Password_Manager/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Secure_Password_Manager "view the result of Secure_Password_Manager") | +| Youtube_video_finder_using_geminillm | [veerababu1729](https://github.com/veerababu1729 "goto veerababu1729 profile") | [#32](https://github.com/Grow-with-Open-Source/Python-Projects/pull/32 "visit pr \#32") | [/Grow-with-Open-Source/Python-Projects/Youtube_video_finder_using_geminillm/](Youtube_video_finder_using_geminillm "view the result of Youtube_video_finder_using_geminillm") | From bbc45ab40710c18b8cb516b0632aee31e9c02624 Mon Sep 17 00:00:00 2001 From: Mudassir Junejo Date: Sun, 19 Oct 2025 21:11:22 +0500 Subject: [PATCH 020/121] update --- Gesture Volume Control/README.md | 11 +++++ Gesture Volume Control/volume_control.py | 62 ++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 Gesture Volume Control/README.md create mode 100644 Gesture Volume Control/volume_control.py diff --git a/Gesture Volume Control/README.md b/Gesture Volume Control/README.md new file mode 100644 index 0000000..c83274a --- /dev/null +++ b/Gesture Volume Control/README.md @@ -0,0 +1,11 @@ +# 🎚️ Gesture Volume Control + +Control your system volume with hand gestures using your webcam! +This project uses **OpenCV**, **MediaPipe**, and **pycaw** to detect your hand in real time and map the distance between your thumb and index finger to system volume. + +## βš™οΈ Requirements +```bash +pip install opencv-python mediapipe pycaw comtypes +``` + +Check out my github: [10mudassir007](https://github.com/10mudassir007) \ No newline at end of file diff --git a/Gesture Volume Control/volume_control.py b/Gesture Volume Control/volume_control.py new file mode 100644 index 0000000..e4cd91d --- /dev/null +++ b/Gesture Volume Control/volume_control.py @@ -0,0 +1,62 @@ +import cv2 +import mediapipe as mp +from ctypes import cast, POINTER +from comtypes import CLSCTX_ALL +from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume +import math + +# Mediapipe hands +mp_hands = mp.solutions.hands +hands = mp_hands.Hands(max_num_hands=1) +mp_draw = mp.solutions.drawing_utils + +# Audio setup +devices = AudioUtilities.GetSpeakers() +interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None) +volume = cast(interface, POINTER(IAudioEndpointVolume)) +vol_range = volume.GetVolumeRange() # minVol, maxVol, step + +# Phone camera +cap = cv2.VideoCapture(0) + +while True: + ret, frame = cap.read() + if not ret: + break + frame = cv2.convertScaleAbs(frame, alpha=1.2, beta=30) + frame = cv2.flip(frame, 1) + rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + results = hands.process(rgb) + + # Resize to 360x360 + frame = cv2.resize(frame, (640, 480)) + + if results.multi_hand_landmarks: + for handLms in results.multi_hand_landmarks: + mp_draw.draw_landmarks(frame, handLms, mp_hands.HAND_CONNECTIONS) + + # Thumb and index finger tips + thumb_tip = handLms.landmark[mp_hands.HandLandmark.THUMB_TIP] + index_tip = handLms.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP] + + h, w, _ = frame.shape + x1, y1 = int(thumb_tip.x * w), int(thumb_tip.y * h) + x2, y2 = int(index_tip.x * w), int(index_tip.y * h) + + cv2.circle(frame, (x1, y1), 8, (255, 0, 0), cv2.FILLED) + cv2.circle(frame, (x2, y2), 8, (255, 0, 0), cv2.FILLED) + cv2.line(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) + + # Distance between fingers β†’ volume + length = math.hypot(x2 - x1, y2 - y1) + min_vol, max_vol, _ = vol_range + vol = (length / 200) * (max_vol - min_vol) + min_vol + vol = max(min(vol, max_vol), min_vol) + volume.SetMasterVolumeLevel(vol, None) + + cv2.imshow("Hand Volume Control", frame) + if cv2.waitKey(1) & 0xFF == ord("q"): + break + +cap.release() +cv2.destroyAllWindows() From 0e658059074d4b1dbee3d233e0ef2b4c7c4a6ed8 Mon Sep 17 00:00:00 2001 From: Shamith Nakka Date: Fri, 24 Oct 2025 08:42:54 +0530 Subject: [PATCH 021/121] Fixing Linting Issue Use of Python version 3.8 limits the new feature introduced during and after 3.10. This fix should resolve the linting issue caused due to older versions. --- .github/workflows/python-linter.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-linter.yml b/.github/workflows/python-linter.yml index c843b03..3d0c94f 100644 --- a/.github/workflows/python-linter.yml +++ b/.github/workflows/python-linter.yml @@ -14,9 +14,9 @@ jobs: uses: actions/checkout@v4.1.0 - name: Set up Python - uses: actions/setup-python@v4.7.1 + uses: actions/setup-python@v6.0.0 with: - python-version: 3.8 + python-version: '3.12' - name: Install dependencies run: | From 93f5bb79ae37dfa2176012bb8c5e181a6613a657 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 24 Oct 2025 03:17:25 +0000 Subject: [PATCH 022/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 9e580cf..442e33e 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -196,5 +196,14 @@ "32" ], "demo-path": "Youtube_video_finder_using_geminillm" + }, + "{workflows}": { + "contributor-name": [ + "iamwatchdogs" + ], + "pull-request-number": [ + "36" + ], + "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github/workflows" } } \ No newline at end of file diff --git a/index.md b/index.md index ce4903f..af36b88 100644 --- a/index.md +++ b/index.md @@ -31,6 +31,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Digital_Clock | [DarkSlayer102](https://github.com/DarkSlayer102 "goto DarkSlayer102 profile") | [#25](https://github.com/Grow-with-Open-Source/Python-Projects/pull/25 "visit pr \#25") | [/Grow-with-Open-Source/Python-Projects/Digital_Clock/](Digital_Clock "view the result of Digital_Clock") | | Secure_Password_Manager | [Chaitanya6Nli](https://github.com/Chaitanya6Nli "goto Chaitanya6Nli profile") | [#29](https://github.com/Grow-with-Open-Source/Python-Projects/pull/29 "visit pr \#29") | [/Grow-with-Open-Source/Python-Projects/Secure_Password_Manager/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Secure_Password_Manager "view the result of Secure_Password_Manager") | | Youtube_video_finder_using_geminillm | [veerababu1729](https://github.com/veerababu1729 "goto veerababu1729 profile") | [#32](https://github.com/Grow-with-Open-Source/Python-Projects/pull/32 "visit pr \#32") | [/Grow-with-Open-Source/Python-Projects/Youtube_video_finder_using_geminillm/](Youtube_video_finder_using_geminillm "view the result of Youtube_video_finder_using_geminillm") | +| {workflows} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#36](https://github.com/Grow-with-Open-Source/Python-Projects/pull/36 "visit pr \#36") | [/Grow-with-Open-Source/Python-Projects/.github/workflows](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github/workflows "view the result of {workflows}") | From 73c39f410c8d2dc0790b1f2c85319ba54f56cbc1 Mon Sep 17 00:00:00 2001 From: iamwatchdogs Date: Fri, 24 Oct 2025 07:43:40 +0530 Subject: [PATCH 023/121] Added temp-cleaner.py script --- Temp-Cleaner/temp-cleaner.py | 314 +++++++++++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100755 Temp-Cleaner/temp-cleaner.py diff --git a/Temp-Cleaner/temp-cleaner.py b/Temp-Cleaner/temp-cleaner.py new file mode 100755 index 0000000..9015146 --- /dev/null +++ b/Temp-Cleaner/temp-cleaner.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python + +import os +import sys +import logging +import argparse +from datetime import datetime +import platform + + +class ProgressBar: + def __init__(self, total, *, prefix='Progress:', suffix='Completed', width=50, fill='=', empty=' '): + self.total = total + self.prefix = prefix + self.suffix = suffix + self.width = width + self.fill = fill + self.empty = empty + self.current = 0 + + def calculate_progress(self): + try: + progress = min(float(self.current) / self.total, 1.0) + except ZeroDivisionError: + progress = 1.0 + + filled = int(self.width * progress) + bar = f'{self.fill * filled}{self.empty * (self.width - filled)}' + percent = int(progress * 100) + output_str = f'\r{self.prefix} [{bar}] {percent}% ({self.current}/{self.total}) {self.suffix}' + + if self.current >= self.total: + output_str += '\n' + return output_str + + def print(self): + sys.stdout.write(self.calculate_progress()) + sys.stdout.flush() + + def update(self, count=1): + self.current = min(self.current + count, self.total) + self.print() + + def finish(self): + self.current = self.total + self.print() + + +class Logger: + __levels = { + 'DEBUG': logging.DEBUG, + 'INFO': logging.INFO, + 'WARNING': logging.WARNING, + 'ERROR': logging.ERROR, + 'CRITICAL': logging.CRITICAL + } + __default_log_filename = f"temp_cleaner_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log" + __default_log_format = "%(asctime)s [%(levelname)s]: %(message)s" + + def __init__(self): + self.logger = logging.getLogger("TempCleaner") + self.logger.setLevel(logging.INFO) + self.handlers = [] + self.logger.propagate = False # Prevent duplicate logging + + def add_handler(self, handler): + handler.setFormatter(logging.Formatter(self.__default_log_format)) + self.handlers.append(handler) + self.logger.addHandler(handler) + + def clear_handlers(self): + for handler in self.handlers: + handler.flush() + self.logger.removeHandler(handler) + handler.close() + self.handlers.clear() + + def __del__(self): + self.clear_handlers() + + def __file_handler_logic(self, log_filename): + log_file = log_filename if log_filename else self.__default_log_filename + try: + file_handler = logging.FileHandler(log_file) + self.add_handler(file_handler) + except (PermissionError, OSError) as e: + self.logger.error( + f"Failed to create log file '{log_file}': {e}") + # Attempt to create in current directory as fallback + if log_filename: + fallback_file = os.path.basename(log_file) + try: + file_handler = logging.FileHandler(fallback_file) + self.add_handler(file_handler) + self.logger.warning( + f"Using fallback log file: {fallback_file}") + except Exception as e: + self.logger.error( + f"Failed to create fallback log file: {e}") + + def configure(self, log_level='INFO', save_log=False, log_filename=None, silent=False): + # Clean up existing handlers + self.clear_handlers() + + try: + # Set log level + level = self.__levels.get(log_level.upper(), logging.INFO) + self.logger.setLevel(level) + + # Silent mode: use NullHandler + if silent: + self.add_handler(logging.NullHandler()) + return self + + # Add console handler by default unless silent + self.add_handler(logging.StreamHandler()) + + # Handle file logging + if save_log or log_filename: + self.__file_handler_logic(log_filename) + + except Exception as e: + self.logger.error(f"Failed to configure logger: {e}") + + finally: + # Ensure we have at least a NullHandler + if not self.handlers: + self.add_handler(logging.NullHandler()) + + return self + + def get_logger(self): + return self.logger + + +class ArgsParser: + DEFAULT_CONFIG = { + '--log-level': { + 'type': str, + 'default': 'INFO', + 'help': 'Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)' + }, + '--save-log': { + 'action': 'store_true', + 'help': 'Save log to a file' + }, + '--log-filename': { + 'type': str, + 'help': 'Specify log filename' + }, + '--silent': { + 'action': 'store_true', + 'help': 'Show only the progress without logging details' + } + } + + def __init__(self): + description="Temp Files Cleaner that cleans temporary files from system temp directories." + self.parser = argparse.ArgumentParser(description=description) + for arg, params in self.DEFAULT_CONFIG.items(): + self.parser.add_argument(arg, **params) + + def parse(self): + return self.parser.parse_args() + + +def does_path_exist(path): + return path is not None and os.path.exists(path) + +def get_temp_directories(): + temp_dirs = [ + os.environ.get('TEMP'), + os.environ.get('TMP') + ] + + match platform.system(): + case 'Windows': # Windows + temp_dirs.extend([ + os.path.join('C:', 'Windows', 'Temp'), + os.path.join('C:', 'Windows', 'Prefetch'), + os.path.join('C:', 'Users', os.getlogin(), 'AppData', 'Local', 'Temp') + ]) + case 'Darwin': # macOS + temp_dirs.extend([ + os.path.join(os.path.expanduser('~'), 'Library', 'Caches'), + '/private/var/folders', + '/private/var/tmp', + '/private/tmp' + ]) + case 'Linux': # Any Linux Distro + temp_dirs.extend([ + '/tmp', + '/var/tmp', + os.path.join(os.path.expanduser('~'), '.cache') + ]) + + return list(filter(does_path_exist, temp_dirs)) + + +def delete_directory_contents(path, logger=None, progress_bar=None): + deleted, skipped = 0, 0 + + if logger is None: + logger = logging.getLogger(__name__) + + if not does_path_exist(path): + logger.warning(f"Directory not found: {path}") + return deleted, skipped + + if os.path.isdir(path): + try: + # Safely get directory contents + items = os.listdir(path) + except PermissionError as e: + logger.warning(f"Permission denied accessing directory: {path} | Reason: {e}") + skipped += 1 + if progress_bar: + progress_bar.update() + return deleted, skipped + except OSError as e: + logger.error(f"OS error accessing directory: {path} | Reason: {e}") + skipped += 1 + if progress_bar: + progress_bar.update() + return deleted, skipped + + # Process directory contents + for item in items: + try: + item_path = os.path.join(path, item) + recursive_deleted, recursive_skipped = delete_directory_contents( + item_path, + logger, + progress_bar + ) + deleted += recursive_deleted + skipped += recursive_skipped + except OSError as e: + logger.error(f"Error processing path: {item_path} | Reason: {e}") + skipped += 1 + if progress_bar: + progress_bar.update() + else: + try: + os.remove(path) + logger.info(f"Deleted file: {path}") + deleted += 1 + except PermissionError as e: + logger.warning(f"File in use: {path} | Reason: {e}") + skipped += 1 + except OSError as e: + logger.error(f"OS error deleting file: {path} | Reason: {e}") + skipped += 1 + + if progress_bar: + progress_bar.update() + + return deleted, skipped + + +def total_file_counter(directory): + total_files = 0 + try: + for root, dirs, files in os.walk(directory): + total_files += len(files) + len(dirs) + return total_files + except (FileNotFoundError, PermissionError, OSError): + return 0 + + +def execute_temp_cleaning(directory_list, cmd_args, logger_instance, progress_bar): + logger = logger_instance.configure(**vars(cmd_args)).get_logger() + + if progress_bar: + progress_bar.print() + + logger.info("---- Starting safe cleaning of temp directories ----") + + for temp_dir in directory_list: + logger.info(f"Cleaning directory: {temp_dir}") + deleted, skipped = delete_directory_contents( + temp_dir, + logger, + progress_bar + ) + logger.info( + f"Summary for {temp_dir}: Deleted: {deleted}, Skipped: {skipped}") + + if progress_bar: + progress_bar.finish() + + logger.info("---- Cleaning completed. Check log for details. ----") + + +def main(): + args_parser = ArgsParser() + logger_instance = Logger() + + directory_list = get_temp_directories() + cmd_args = args_parser.parse() + + total_temp_files_count = sum(map(total_file_counter, directory_list)) + progress_bar = ProgressBar(total_temp_files_count) + + execute_temp_cleaning( + directory_list, + cmd_args, + logger_instance, + progress_bar + ) + + +if __name__ == "__main__": + main() From 467af5cc0b7b457646332b1a67507c67cb22a0a7 Mon Sep 17 00:00:00 2001 From: iamwatchdogs Date: Fri, 24 Oct 2025 08:23:25 +0530 Subject: [PATCH 024/121] Added README.md --- Temp-Cleaner/README.md | 93 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 Temp-Cleaner/README.md diff --git a/Temp-Cleaner/README.md b/Temp-Cleaner/README.md new file mode 100644 index 0000000..536dcb2 --- /dev/null +++ b/Temp-Cleaner/README.md @@ -0,0 +1,93 @@ +# Temp-Cleaner + +Temp-Cleaner is a Python script that automates the manual process of cleaning temporary files from system temp directories. This script is compatable with major operating systems including Linux, Windows, and macOS. + +This script include a good logging system that can be used to track the progress of the script. The script also comprises a command line interface that can be used to control the script from the command line to manipulate the logging mechanism. + +## Requirements + +The only requirement for running this script in your local system is Python 3.10 or above. No external dependencies are required. + +## Usage + +### For *unix-based systems +To use this script, run the following command: + +```bash +curl -s https://raw.githubusercontent.com/Grow-with-Open-Source/Python-Projects/main/Temp-Cleaner/temp-cleaner.py | python +``` + +or + +```bash +wget -qO- https://raw.githubusercontent.com/Grow-with-Open-Source/Python-Projects/main/Temp-Cleaner/temp-cleaner.py | python +``` + +or you can download the file from GitHub and then run the script by giving permission to execute the file as shown below: + +```bash +# Downloading the script +wget https://raw.githubusercontent.com/Grow-with-Open-Source/Python-Projects/main/Temp-Cleaner/temp-cleaner.py + +# --- OR --- +# curl -o script.py https://raw.githubusercontent.com/Grow-with-Open-Source/Python-Projects/main/Temp-Cleaner/temp-cleaner.py +# ---------- + +# Giving permission to execute the file +chmod +x temp-cleaner.py + +# Running the script +./temp-cleaner.py +``` + +### For Windows + +Usually, powershell in Window 10 or later version consist of `curl` binary, so you can do that same thing as shown above. But in case it doesn't work, you can use the following command: + +```powershell +Invoke-WebRequest https://raw.githubusercontent.com/Grow-with-Open-Source/Python-Projects/main/Temp-Cleaner/temp-cleaner.py -OutFile "$env:TEMP\temp_script.py" +python "$env:TEMP\temp_script.py" +``` + +If you want to save the script for later usage, then it's best recommended to download the script in a desired location and run the script using python interpreter. + +### Command Line Arguments + +If you execute the script without any arguments or options, then it will be equivalent to the following command: + +```bash +./temp-cleaner.py --log-level INFO +``` + +Which mean, the logs will be displayed only to the console and is not stored anywhere. But you can change the logging behavior by using command line arguments/options as shown below: + +```bash +usage: temp-cleaner.py [-h] [--log-level LOG_LEVEL] [--save-log] [--log-filename LOG_FILENAME] [--silent] + +Temp Files Cleaner that cleans temporary files from system temp directories. + +options: + -h, --help show this help message and exit + --log-level LOG_LEVEL Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) + --save-log Save log to a file + --log-filename LOG_FILENAME Specify log filename + --silent Show only the progress without logging details +``` + +Here are more details on the options: + +- `--log-level`: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) sets the logging level for the script. **Default is `INFO`**. +- `--save-log`: Save log to a file with file name as `temp_cleaner_YYYYMMDD_HHMMSS.log`. **Default is `False`**. +- `--log-filename`: If you want to save the log file with a custom name, then you can use this option. **Default is `None`**. +- `--silent`: Displays only the progress without logging details. **Default is `False`**. + +> [!IMPORTANT] +> If you're using `--silent` option, then the log file will not be saved even if you specified `--save-log` or `--log-filename` option and all the logging level will be ignored. + +## Contributing + +Please make sure you have used it this script before you start contributing, and then please go through the [Contributing Guidelines](https://github.com/Grow-with-Open-Source/Python-Projects/blob/main/CONTRIBUTING.md) to make your contribution. + +## License + +This project is released under the [Apache License 2.0](https://github.com/Grow-with-Open-Source/Python-Projects/blob/main/LICENSE). \ No newline at end of file From 06c01adbc965782dc7d207c5852dfcaaa4ab33cf Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 24 Oct 2025 03:57:01 +0000 Subject: [PATCH 025/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 442e33e..39a0d90 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -205,5 +205,14 @@ "36" ], "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github/workflows" + }, + "Temp-Cleaner": { + "contributor-name": [ + "iamwatchdogs" + ], + "pull-request-number": [ + "35" + ], + "demo-path": "Temp-Cleaner" } } \ No newline at end of file diff --git a/index.md b/index.md index af36b88..9bb2fad 100644 --- a/index.md +++ b/index.md @@ -32,6 +32,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Secure_Password_Manager | [Chaitanya6Nli](https://github.com/Chaitanya6Nli "goto Chaitanya6Nli profile") | [#29](https://github.com/Grow-with-Open-Source/Python-Projects/pull/29 "visit pr \#29") | [/Grow-with-Open-Source/Python-Projects/Secure_Password_Manager/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Secure_Password_Manager "view the result of Secure_Password_Manager") | | Youtube_video_finder_using_geminillm | [veerababu1729](https://github.com/veerababu1729 "goto veerababu1729 profile") | [#32](https://github.com/Grow-with-Open-Source/Python-Projects/pull/32 "visit pr \#32") | [/Grow-with-Open-Source/Python-Projects/Youtube_video_finder_using_geminillm/](Youtube_video_finder_using_geminillm "view the result of Youtube_video_finder_using_geminillm") | | {workflows} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#36](https://github.com/Grow-with-Open-Source/Python-Projects/pull/36 "visit pr \#36") | [/Grow-with-Open-Source/Python-Projects/.github/workflows](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github/workflows "view the result of {workflows}") | +| Temp-Cleaner | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#35](https://github.com/Grow-with-Open-Source/Python-Projects/pull/35 "visit pr \#35") | [/Grow-with-Open-Source/Python-Projects/Temp-Cleaner/](Temp-Cleaner "view the result of Temp-Cleaner") | From 3ae1eaddbf30b4bb021e7f359bd0424480b7f28b Mon Sep 17 00:00:00 2001 From: Mudassir Junejo Date: Fri, 24 Oct 2025 13:14:05 +0500 Subject: [PATCH 026/121] update --- {Gesture Volume Control => Gesture_Volume_Control}/README.md | 0 .../volume_control.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {Gesture Volume Control => Gesture_Volume_Control}/README.md (100%) rename {Gesture Volume Control => Gesture_Volume_Control}/volume_control.py (100%) diff --git a/Gesture Volume Control/README.md b/Gesture_Volume_Control/README.md similarity index 100% rename from Gesture Volume Control/README.md rename to Gesture_Volume_Control/README.md diff --git a/Gesture Volume Control/volume_control.py b/Gesture_Volume_Control/volume_control.py similarity index 100% rename from Gesture Volume Control/volume_control.py rename to Gesture_Volume_Control/volume_control.py From d0376c1b21f540e6b484f1011652fce5cfe24a94 Mon Sep 17 00:00:00 2001 From: iamwatchdogs Date: Mon, 27 Oct 2025 21:51:03 +0530 Subject: [PATCH 027/121] Created check-password.py --- Password-Checker/check-password.py | 257 +++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100755 Password-Checker/check-password.py diff --git a/Password-Checker/check-password.py b/Password-Checker/check-password.py new file mode 100755 index 0000000..c62742f --- /dev/null +++ b/Password-Checker/check-password.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python +import re +import sys +import random +from getpass import getpass + +# ANSI escape codes for colors +COLOR = { + "RED": '\033[91m', + "YELLOW": '\033[93m', + "GREEN": '\033[92m', + "BLUE": '\033[94m', + "RESET": '\033[0m' +} + +KEYBOARD_PATTERNS = ['qwerty', 'asdfgh', 'zxcvbn'] +COMMON_SUBSTITUTIONS = { + '@': 'a', '4': 'a', '3': 'e', '0': 'o', + '1': 'i', '$': 's', '7': 't' +} + +COMMON_WORDS = { + 'adjectives': ['Happy', 'Clever', 'Swift', 'Brave', 'Bright'], + 'nouns': ['Tiger', 'River', 'Mountain', 'Storm', 'Star'], + 'numbers': ['365', '42', '777', '314', '999'], + 'separators': ['_', '.', '#', '*', '@'] +} + +PATTERNS = { + 'uppercase': re.compile(r'[A-Z]'), + 'lowercase': re.compile(r'[a-z]'), + 'numbers': re.compile(r'\d'), + 'special': re.compile(r'[!@#$%^&*(),.?":{}|<>]') +} + + +def format_to_header( + msg: str, + *, + rep: float = 1, + new_line_at_end: bool = False, + is_main_header: bool = False): + if not isinstance(msg, str): + raise TypeError("msg must be a string") + + res_str = "\n" + no_of_hypens = int(len(msg)*rep) + + if is_main_header: + no_of_hypens += 4 + msg = f"| {msg.upper()} |" + + header_str = [ + '-'*no_of_hypens, + msg, + '-'*no_of_hypens, + ] + + res_str += "\n".join(header_str) + if new_line_at_end: + res_str += "\n" + return res_str + + +def check_password_strength(password): + score = 0 + suggestions = [] + + # Check length + if len(password) < 12: + suggestions.append("Password should be at least 12 characters long.") + elif len(password) >= 16: + score += 2 + else: + score += 1 + + # Check for uppercase + if not PATTERNS['uppercase'].search(password): + suggestions.append("Add uppercase letters.") + else: + score += 1 + + # Check for lowercase + if not PATTERNS['lowercase'].search(password): + suggestions.append("Add lowercase letters.") + else: + score += 1 + + # Check for numbers + if not PATTERNS['numbers'].search(password): + suggestions.append("Add numbers.") + else: + score += 1 + + # Check for special characters + if not PATTERNS['special'].search(password): + suggestions.append("Add special characters.") + else: + score += 1 + + # Check for repeated patterns (like 'testtest') + half_length = len(password) // 2 + for i in range(2, half_length + 1): + if password[:i] * (len(password) // i) == password[:len(password) // i * i]: + suggestions.append("Avoid repeating patterns in your password.") + score -= 1 + break + + # Check for keyboard patterns + lower_pass = password.lower() + for pattern in KEYBOARD_PATTERNS: + if pattern in lower_pass: + suggestions.append("Avoid common keyboard patterns") + score -= 1 + break + + # Check for simple character substitutions + substituted = password.lower() + for k, v in COMMON_SUBSTITUTIONS.items(): + substituted = substituted.replace(k, v) + if substituted.isalpha() and len(substituted) > 3: + suggestions.append( + "Using symbol substitutions (like '@' for 'a') isn't very secure.") + score -= 1 + + # Ensure score doesn't go below 0 + score = max(0, score) + + return score, suggestions + + +def categorize_password(score): + if score < 2: + return "WEAK", COLOR["RED"] + if score < 4: + return "GOOD", COLOR["YELLOW"] + return "STRONG", COLOR["GREEN"] + + +def create_memorable_suggestion(base_word): + adj = random.choice(COMMON_WORDS['adjectives']) + noun = random.choice(COMMON_WORDS['nouns']) + num = random.choice(COMMON_WORDS['numbers']) + sep = random.choice(COMMON_WORDS['separators']) + + # Use the base word if it's good enough (not too short and has letters) + if len(base_word) >= 4 and any(c.isalpha() for c in base_word): + base = base_word.capitalize() + else: + base = noun + + patterns = [ + f"{adj}{sep}{base}{num}", + f"{base}{sep}{noun}{num}", + f"{num}{sep}{adj}{base}" + ] + + return random.choice(patterns) + + +def suggest_better_password(password): + # If password is very weak, create a completely new memorable one + score, _ = check_password_strength(password) + if score < 2: + return create_memorable_suggestion(password) + + suggestion = password + + # Smart character substitutions (maintain readability) + smart_subs = { + 'a': '@', 'e': '3', 'i': '!', 'o': '0', 's': '$', + 'ate': '8', 'to': '2', 'for': '4' + } + + # Apply substitutions intelligently + for word, replacement in smart_subs.items(): + if word in suggestion.lower() and random.random() < 0.5: # 50% chance + suggestion = suggestion.replace(word, replacement) + + # Ensure at least one capital letter in a natural position + if not any(c.isupper() for c in suggestion): + words = suggestion.split() + if words: + words[0] = words[0].capitalize() + suggestion = ''.join(words) + + # Add complexity if needed while keeping it memorable + if len(suggestion) < 12: + suggestion += random.choice(COMMON_WORDS['numbers']) + + if not re.search(r'[!@#$%^&*(),.?":{}|<>]', suggestion): + suggestion += random.choice(COMMON_WORDS['separators']) + + return suggestion + + +def input_handler(): + if len(sys.argv) > 1: + password = sys.argv[1] + print( + f"{COLOR['RED']}It is recommended to avoid entering passwords directly on the command line,{COLOR['RESET']}") + print( + f"{COLOR['RED']}as they may be visible to others and recorded in the shell history.{COLOR['RESET']}") + return password + print( + format_to_header( + "Password Strength Checker", + new_line_at_end=True, + is_main_header=True + ) + ) + print("For enhanced security, your input will be hidden.") + print("Hence, you may not see the characters as you type.") + try: + password = getpass("\nEnter password to check: ") + except KeyboardInterrupt: + print("\nExiting...") + sys.exit(0) + return password + + +def output_handler(password, category, color, suggestions): + print(f"\nPassword Strength: {color}{category}{COLOR['RESET']}") + + if suggestions: + print(format_to_header("Suggestions to improve:")) + for suggestion in suggestions: + print(f"{COLOR['BLUE']}- {suggestion}{COLOR['RESET']}") + + # Add this block to show suggested password + if category != "STRONG": + better_password = suggest_better_password(password) + print( + f"\nSuggested stronger password: {COLOR['GREEN']}{better_password}{COLOR['RESET']}") + + points_to_remember = [ + "Never use your personal information while creating a password.", + "Consider using a passphrase made up of multiple words for better security.", + "Avoid using common phrases or easily guessable patterns.", + "Avoid using the same password for multiple accounts.", + "Regularly update your passwords to enhance security.", + "Use a reputable password manager to generate and store complex passwords securely." + ] + print(format_to_header('Points to Remember:')) + for points in points_to_remember: + print(f"{COLOR['BLUE']}- {points}{COLOR['RESET']}") + + +def main(): + password = input_handler() + score, suggestions = check_password_strength(password) + category, color = categorize_password(score) + output_handler(password, category, color, suggestions) + + +if __name__ == "__main__": + main() From ba8cf53b240db70083303f060697d30b6c4099a2 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 27 Oct 2025 16:42:25 +0000 Subject: [PATCH 028/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 39a0d90..2f6c3b4 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -214,5 +214,14 @@ "35" ], "demo-path": "Temp-Cleaner" + }, + "Gesture_Volume_Control": { + "contributor-name": [ + "10mudassir007" + ], + "pull-request-number": [ + "34" + ], + "demo-path": "Gesture_Volume_Control" } } \ No newline at end of file diff --git a/index.md b/index.md index 9bb2fad..e333938 100644 --- a/index.md +++ b/index.md @@ -33,6 +33,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Youtube_video_finder_using_geminillm | [veerababu1729](https://github.com/veerababu1729 "goto veerababu1729 profile") | [#32](https://github.com/Grow-with-Open-Source/Python-Projects/pull/32 "visit pr \#32") | [/Grow-with-Open-Source/Python-Projects/Youtube_video_finder_using_geminillm/](Youtube_video_finder_using_geminillm "view the result of Youtube_video_finder_using_geminillm") | | {workflows} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#36](https://github.com/Grow-with-Open-Source/Python-Projects/pull/36 "visit pr \#36") | [/Grow-with-Open-Source/Python-Projects/.github/workflows](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github/workflows "view the result of {workflows}") | | Temp-Cleaner | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#35](https://github.com/Grow-with-Open-Source/Python-Projects/pull/35 "visit pr \#35") | [/Grow-with-Open-Source/Python-Projects/Temp-Cleaner/](Temp-Cleaner "view the result of Temp-Cleaner") | +| Gesture_Volume_Control | [10mudassir007](https://github.com/10mudassir007 "goto 10mudassir007 profile") | [#34](https://github.com/Grow-with-Open-Source/Python-Projects/pull/34 "visit pr \#34") | [/Grow-with-Open-Source/Python-Projects/Gesture_Volume_Control/](Gesture_Volume_Control "view the result of Gesture_Volume_Control") | From 39a343e652bc144ab820b84bb8defe8292a92e53 Mon Sep 17 00:00:00 2001 From: iamwatchdogs Date: Mon, 27 Oct 2025 22:20:10 +0530 Subject: [PATCH 029/121] Added README.md --- Password-Checker/README.md | 70 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 Password-Checker/README.md diff --git a/Password-Checker/README.md b/Password-Checker/README.md new file mode 100644 index 0000000..cae7ad4 --- /dev/null +++ b/Password-Checker/README.md @@ -0,0 +1,70 @@ +# Password-Checker + +Password-Checker is a simple script that checks the strength of a given password and provides suggestions to improve its security. It also suggests a better password based on the given password. + +You can either provide your input as a command line argument or interactively through the terminal _(but it's always recommended to use interactive session on console)_. This is a basic script that perform minimum basic checks and suggestion. + +> [!IMPORTANT] +> Since this script serves as a foundational example, it may not be fully suitable for real-world use cases yet. However, its purpose is to establish a solid groundwork for more advanced versions. As the author, I look forward to seeing further improvements and refactoring that will enhance this script to meet real-world requirements. + +## Requirements + +The only requirement for running this script in your local system is Python 3.6 or above. No external dependencies are required. + +## Usage + +### For *unix-based systems +To use this script, run the following command: + +```bash +curl -s https://raw.githubusercontent.com/Grow-with-Open-Source/Python-Projects/main/Password-Checker/check-password.py | python +``` + +or + +```bash +wget -qO- https://raw.githubusercontent.com/Grow-with-Open-Source/Python-Projects/main/Password-Checker/check-password.py | python +``` + +or you can download the file from GitHub and then run the script by giving permission to execute the file as shown below: + +```bash +# Downloading the script +wget https://raw.githubusercontent.com/Grow-with-Open-Source/Python-Projects/main/Password-Checker/check-password.py + +# --- OR --- +# curl -o script.py https://raw.githubusercontent.com/Grow-with-Open-Source/Python-Projects/main/Password-Checker/check-password.py +# ---------- + +# Giving permission to execute the file +chmod +x check-password.py + +# Running the script +./check-password.py +``` + +### For Windows + +Usually, powershell in Window 10 or later version consist of `curl` binary, so you can do that same thing as shown above. But in case it doesn't work, you can use the following command: + +```powershell +Invoke-WebRequest https://raw.githubusercontent.com/Grow-with-Open-Source/Python-Projects/main/Password-Checker/check-password.py -OutFile "$env:TEMP\temp_script.py" +python "$env:TEMP\temp_script.py" +``` + +If you want to save the script for later usage, then it's best recommended to download the script in a desired location and run the script using python interpreter. + +## Contributing + +Please make sure you have used it this script before you start contributing, and then please go through the [Contributing Guidelines](https://github.com/Grow-with-Open-Source/Python-Projects/blob/main/CONTRIBUTING.md) to make your contribution. + +> [!NOTE] +> Since this mini-project was meant to be a sample groundwork for more advancements, add your changes and contributions into the following [Change Log](#change-log) in the given format. + +## Change Log + +- PR [#37](https://github.com/Grow-with-Open-Source/Python-Projects/pull/37): Created the basic script with minimum features. + +## License + +This project is released under the [Apache License 2.0](https://github.com/Grow-with-Open-Source/Python-Projects/blob/main/LICENSE). \ No newline at end of file From 0608ab0b71ad99d659c70560b8803bfb20d3ceb1 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 27 Oct 2025 16:56:15 +0000 Subject: [PATCH 030/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 2f6c3b4..ca5117a 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -223,5 +223,14 @@ "34" ], "demo-path": "Gesture_Volume_Control" + }, + "Password-Checker": { + "contributor-name": [ + "iamwatchdogs" + ], + "pull-request-number": [ + "37" + ], + "demo-path": "Password-Checker" } } \ No newline at end of file diff --git a/index.md b/index.md index e333938..f2c1674 100644 --- a/index.md +++ b/index.md @@ -34,6 +34,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | {workflows} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#36](https://github.com/Grow-with-Open-Source/Python-Projects/pull/36 "visit pr \#36") | [/Grow-with-Open-Source/Python-Projects/.github/workflows](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github/workflows "view the result of {workflows}") | | Temp-Cleaner | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#35](https://github.com/Grow-with-Open-Source/Python-Projects/pull/35 "visit pr \#35") | [/Grow-with-Open-Source/Python-Projects/Temp-Cleaner/](Temp-Cleaner "view the result of Temp-Cleaner") | | Gesture_Volume_Control | [10mudassir007](https://github.com/10mudassir007 "goto 10mudassir007 profile") | [#34](https://github.com/Grow-with-Open-Source/Python-Projects/pull/34 "visit pr \#34") | [/Grow-with-Open-Source/Python-Projects/Gesture_Volume_Control/](Gesture_Volume_Control "view the result of Gesture_Volume_Control") | +| Password-Checker | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#37](https://github.com/Grow-with-Open-Source/Python-Projects/pull/37 "visit pr \#37") | [/Grow-with-Open-Source/Python-Projects/Password-Checker/](Password-Checker "view the result of Password-Checker") | From 580f605187cddc842bab9abd4b863c5887361b7c Mon Sep 17 00:00:00 2001 From: iamwatchdogs Date: Tue, 28 Oct 2025 21:48:09 +0530 Subject: [PATCH 031/121] Fix for linting works for Jupyter Notebook --- .github/workflows/python-linter.yml | 99 +++++++++++++++++------------ 1 file changed, 58 insertions(+), 41 deletions(-) diff --git a/.github/workflows/python-linter.yml b/.github/workflows/python-linter.yml index 3d0c94f..985afb7 100644 --- a/.github/workflows/python-linter.yml +++ b/.github/workflows/python-linter.yml @@ -3,50 +3,67 @@ name: lint-python-code on: pull_request: branches: [main] - paths: ['**.py', '**.ipynb'] + paths: ["**.py", "**.ipynb"] workflow_call: jobs: python-linter: runs-on: ubuntu-latest steps: - - name: Checking out repo - uses: actions/checkout@v4.1.0 - - - name: Set up Python - uses: actions/setup-python@v6.0.0 - with: - python-version: '3.12' - - - name: Install dependencies - run: | - pip install flake8 - pip install pynblint - - - name: Lint Python Code - run: | - flake8 . --select=E901,E999,F821,F822,F823 --exclude=__init__.py - env: - FLAKE8_OPTIONS: "--ignore=E203,W503" - - - name: Getting PR details - run: | - touch pr.json - gh pr view $PR_NUMBER --json files > pr.json - touch pr.json - env: - PR_NUMBER: ${{ github.event.pull_request.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Linting all Jupyter Notebook files - uses: jannekem/run-python-script-action@v1.6 - with: - script: | - import os - import json - with open('pr.json','r') as json_file: - data = json.load(json_file) - for file in data["files"]: - path = file["path"] - if os.path.exists(path): - os.system(f"pynblint {path}") + - name: Checking out repo + uses: actions/checkout@v4.1.0 + + - name: Set up Python + uses: actions/setup-python@v6.0.0 + with: + python-version: "3.12" + + - name: Install dependencies + run: | + pip install flake8 nbqa pylint + + - name: Lint Python Code + run: | + flake8 . --select=E901,E999,F821,F822,F823 --exclude=__init__.py + env: + FLAKE8_OPTIONS: "--ignore=E203,W503" + + - name: Getting PR details + run: | + touch pr.json + gh pr view $PR_NUMBER --json files > pr.json + touch pr.json + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Linting all Jupyter Notebook files + uses: jannekem/run-python-script-action@v1.7 + with: + script: | + import os + import sys + import json + import subprocess + + with open('pr.json', 'r') as pr_details: + files = [ + f['path'] + for f in json.load(pr_details)['files'] + if f['path'].endswith('.ipynb') and os.path.exists(f['path']) + ] + + exit_codes = [] + for path in files: + cmd = ['nbqa', 'pylint', *os.environ['LINTER_CONFIG'].split(), + path, *os.environ['NBQA_CONFIG'].split()] + result = subprocess.run(cmd, capture_output=True, text=True) + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + exit_codes.append(result.returncode) + + sys.exit(int(any(exit_codes))) + env: + LINTER_CONFIG: "--disable=C,import-error,no-name-in-module --fail-under=8 --verbose --output-format=colorized" + NBQA_CONFIG: "--nbqa-dont-skip-bad-cells" From 7780980eb679e96794d646067e8b1c11fdc55328 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 28 Oct 2025 16:37:07 +0000 Subject: [PATCH 032/121] Updated Contributors Details --- .github/data/contributors-log.json | 3 ++- index.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index ca5117a..27e6767 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -202,7 +202,8 @@ "iamwatchdogs" ], "pull-request-number": [ - "36" + "36", + "39" ], "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github/workflows" }, diff --git a/index.md b/index.md index f2c1674..a37019a 100644 --- a/index.md +++ b/index.md @@ -31,7 +31,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Digital_Clock | [DarkSlayer102](https://github.com/DarkSlayer102 "goto DarkSlayer102 profile") | [#25](https://github.com/Grow-with-Open-Source/Python-Projects/pull/25 "visit pr \#25") | [/Grow-with-Open-Source/Python-Projects/Digital_Clock/](Digital_Clock "view the result of Digital_Clock") | | Secure_Password_Manager | [Chaitanya6Nli](https://github.com/Chaitanya6Nli "goto Chaitanya6Nli profile") | [#29](https://github.com/Grow-with-Open-Source/Python-Projects/pull/29 "visit pr \#29") | [/Grow-with-Open-Source/Python-Projects/Secure_Password_Manager/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Secure_Password_Manager "view the result of Secure_Password_Manager") | | Youtube_video_finder_using_geminillm | [veerababu1729](https://github.com/veerababu1729 "goto veerababu1729 profile") | [#32](https://github.com/Grow-with-Open-Source/Python-Projects/pull/32 "visit pr \#32") | [/Grow-with-Open-Source/Python-Projects/Youtube_video_finder_using_geminillm/](Youtube_video_finder_using_geminillm "view the result of Youtube_video_finder_using_geminillm") | -| {workflows} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#36](https://github.com/Grow-with-Open-Source/Python-Projects/pull/36 "visit pr \#36") | [/Grow-with-Open-Source/Python-Projects/.github/workflows](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github/workflows "view the result of {workflows}") | +| {workflows} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#36](https://github.com/Grow-with-Open-Source/Python-Projects/pull/36 "visit pr \#36"), [#39](https://github.com/Grow-with-Open-Source/Python-Projects/pull/39 "visit pr \#39") | [/Grow-with-Open-Source/Python-Projects/.github/workflows](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github/workflows "view the result of {workflows}") | | Temp-Cleaner | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#35](https://github.com/Grow-with-Open-Source/Python-Projects/pull/35 "visit pr \#35") | [/Grow-with-Open-Source/Python-Projects/Temp-Cleaner/](Temp-Cleaner "view the result of Temp-Cleaner") | | Gesture_Volume_Control | [10mudassir007](https://github.com/10mudassir007 "goto 10mudassir007 profile") | [#34](https://github.com/Grow-with-Open-Source/Python-Projects/pull/34 "visit pr \#34") | [/Grow-with-Open-Source/Python-Projects/Gesture_Volume_Control/](Gesture_Volume_Control "view the result of Gesture_Volume_Control") | | Password-Checker | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#37](https://github.com/Grow-with-Open-Source/Python-Projects/pull/37 "visit pr \#37") | [/Grow-with-Open-Source/Python-Projects/Password-Checker/](Password-Checker "view the result of Password-Checker") | From dc43e647d5b883433f5a0589da42cdda8ab0338b Mon Sep 17 00:00:00 2001 From: Omprakash00724 <84080435+Omprakash00724@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:12:20 +0530 Subject: [PATCH 033/121] Add temperature converter project --- Temperature_Converter/README.md | 31 ++++++++++ .../temperature_converter.py | 62 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 Temperature_Converter/README.md create mode 100644 Temperature_Converter/temperature_converter.py diff --git a/Temperature_Converter/README.md b/Temperature_Converter/README.md new file mode 100644 index 0000000..0885a92 --- /dev/null +++ b/Temperature_Converter/README.md @@ -0,0 +1,31 @@ +# 🌑️ Temperature Converter + +A simple and interactive Python program to convert temperatures between **Celsius**, **Fahrenheit**, and **Kelvin**. + +## πŸš€ Features +- Convert between all major temperature units +- Clean and easy-to-use CLI interface +- Accurate conversion formulas + +## 🧩 How to Run +```bash +python temperature_converter.py +``` + +## πŸ“– Example +``` +🌑️ Temperature Converter +1. Celsius to Fahrenheit +2. Fahrenheit to Celsius +3. Celsius to Kelvin +4. Kelvin to Celsius +5. Fahrenheit to Kelvin +6. Kelvin to Fahrenheit +Enter choice (1–6): 1 +Enter Celsius: 25 +25Β°C = 77.00Β°F +``` + +--- + +βœ… **Simple. Accurate. Handy.** diff --git a/Temperature_Converter/temperature_converter.py b/Temperature_Converter/temperature_converter.py new file mode 100644 index 0000000..105d19a --- /dev/null +++ b/Temperature_Converter/temperature_converter.py @@ -0,0 +1,62 @@ +# Temperature Converter +# Converts temperature between Celsius, Fahrenheit, and Kelvin + +def celsius_to_fahrenheit(celsius): + return (celsius * 9 / 5) + 32 + + +def fahrenheit_to_celsius(fahrenheit): + return (fahrenheit - 32) * 5 / 9 + + +def celsius_to_kelvin(celsius): + return celsius + 273.15 + + +def kelvin_to_celsius(kelvin): + return kelvin - 273.15 + + +def fahrenheit_to_kelvin(fahrenheit): + return (fahrenheit - 32) * 5 / 9 + 273.15 + + +def kelvin_to_fahrenheit(kelvin): + return (kelvin - 273.15) * 9 / 5 + 32 + + +def main(): + print("🌑️ Temperature Converter") + print("1. Celsius to Fahrenheit") + print("2. Fahrenheit to Celsius") + print("3. Celsius to Kelvin") + print("4. Kelvin to Celsius") + print("5. Fahrenheit to Kelvin") + print("6. Kelvin to Fahrenheit") + + choice = input("Enter choice (1–6): ") + + if choice == "1": + c = float(input("Enter Celsius: ")) + print(f"{c}Β°C = {celsius_to_fahrenheit(c):.2f}Β°F") + elif choice == "2": + f = float(input("Enter Fahrenheit: ")) + print(f"{f}Β°F = {fahrenheit_to_celsius(f):.2f}Β°C") + elif choice == "3": + c = float(input("Enter Celsius: ")) + print(f"{c}Β°C = {celsius_to_kelvin(c):.2f}K") + elif choice == "4": + k = float(input("Enter Kelvin: ")) + print(f"{k}K = {kelvin_to_celsius(k):.2f}Β°C") + elif choice == "5": + f = float(input("Enter Fahrenheit: ")) + print(f"{f}Β°F = {fahrenheit_to_kelvin(f):.2f}K") + elif choice == "6": + k = float(input("Enter Kelvin: ")) + print(f"{k}K = {kelvin_to_fahrenheit(k):.2f}Β°F") + else: + print("Invalid choice ❌") + + +if __name__ == "__main__": + main() From 13eab9acd059fa1027a96a9667fd7309f62debe8 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 30 Oct 2025 17:14:49 +0000 Subject: [PATCH 034/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 27e6767..07286f0 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -233,5 +233,14 @@ "37" ], "demo-path": "Password-Checker" + }, + "Temperature_Converter": { + "contributor-name": [ + "omprakash0702" + ], + "pull-request-number": [ + "40" + ], + "demo-path": "Temperature_Converter" } } \ No newline at end of file diff --git a/index.md b/index.md index a37019a..be157fe 100644 --- a/index.md +++ b/index.md @@ -35,6 +35,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Temp-Cleaner | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#35](https://github.com/Grow-with-Open-Source/Python-Projects/pull/35 "visit pr \#35") | [/Grow-with-Open-Source/Python-Projects/Temp-Cleaner/](Temp-Cleaner "view the result of Temp-Cleaner") | | Gesture_Volume_Control | [10mudassir007](https://github.com/10mudassir007 "goto 10mudassir007 profile") | [#34](https://github.com/Grow-with-Open-Source/Python-Projects/pull/34 "visit pr \#34") | [/Grow-with-Open-Source/Python-Projects/Gesture_Volume_Control/](Gesture_Volume_Control "view the result of Gesture_Volume_Control") | | Password-Checker | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#37](https://github.com/Grow-with-Open-Source/Python-Projects/pull/37 "visit pr \#37") | [/Grow-with-Open-Source/Python-Projects/Password-Checker/](Password-Checker "view the result of Password-Checker") | +| Temperature_Converter | [omprakash0702](https://github.com/omprakash0702 "goto omprakash0702 profile") | [#40](https://github.com/Grow-with-Open-Source/Python-Projects/pull/40 "visit pr \#40") | [/Grow-with-Open-Source/Python-Projects/Temperature_Converter/](Temperature_Converter "view the result of Temperature_Converter") | From 0e21fb2071e2472074a240d0fffeb88e20d5ae0e Mon Sep 17 00:00:00 2001 From: sun-fibo-intern Date: Sat, 1 Nov 2025 13:22:58 +0530 Subject: [PATCH 035/121] Added real-time date display feature to Digital Clock --- Digital_Clock/digital_clock.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Digital_Clock/digital_clock.py b/Digital_Clock/digital_clock.py index 5e11037..9d6fc29 100644 --- a/Digital_Clock/digital_clock.py +++ b/Digital_Clock/digital_clock.py @@ -11,6 +11,7 @@ def __init__(self, font=None): self.set_font(font) self.add_header() self.add_clock() + self.add_date() # βœ… Added new method to show date self.update_time_on_clock() def create_window(self): @@ -39,6 +40,19 @@ def add_clock(self): 'times', 90, 'bold'), bg='blue', fg='white') self.clock.grid(row=2, column=2, padx=620, pady=250) + def add_date(self): + """Add a date label below the clock.""" + self.date_label = Label(self.window, font=('times', 40, 'bold'), bg='black', fg='white') + self.date_label.grid(row=3, column=2) + self.update_date_on_clock() + + def update_date_on_clock(self): + """Update the date displayed below the clock.""" + currentDate = time.strftime("%d-%b-%Y") + self.date_label.config(text=currentDate) + # Update every midnight (24*60*60*1000 ms) + self.date_label.after(86400000, self.update_date_on_clock) + def update_time_on_clock(self): """Update the time displayed on the clock every second.""" currentTime = time.strftime("%H:%M:%S") @@ -53,4 +67,3 @@ def start(self): if __name__ == "__main__": clock = DigitalClock() clock.start() - From 55eb126b0ad45c621ee4b31dc4d8998ca3f13a8d Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 4 Nov 2025 15:51:22 +0000 Subject: [PATCH 036/121] Updated Contributors Details --- .github/data/contributors-log.json | 6 ++++-- index.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 07286f0..30fd51e 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -172,10 +172,12 @@ }, "Digital_Clock": { "contributor-name": [ - "DarkSlayer102" + "DarkSlayer102", + "swati-londhe" ], "pull-request-number": [ - "25" + "25", + "41" ], "demo-path": "Digital_Clock" }, diff --git a/index.md b/index.md index be157fe..4a6dfcc 100644 --- a/index.md +++ b/index.md @@ -28,7 +28,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | {others} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#22](https://github.com/Grow-with-Open-Source/Python-Projects/pull/22 "visit pr \#22") | [/Grow-with-Open-Source/Python-Projects/.github](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github "view the result of {others}") | | 3d_pose_estimation | [mahipalimkar](https://github.com/mahipalimkar "goto mahipalimkar profile") | [#23](https://github.com/Grow-with-Open-Source/Python-Projects/pull/23 "visit pr \#23") | [/Grow-with-Open-Source/Python-Projects/3d_pose_estimation/](3d_pose_estimation "view the result of 3d_pose_estimation") | | Coin-Poison | [niharikah005](https://github.com/niharikah005 "goto niharikah005 profile") | [#24](https://github.com/Grow-with-Open-Source/Python-Projects/pull/24 "visit pr \#24") | [/Grow-with-Open-Source/Python-Projects/Coin-Poison/](Coin-Poison "view the result of Coin-Poison") | -| Digital_Clock | [DarkSlayer102](https://github.com/DarkSlayer102 "goto DarkSlayer102 profile") | [#25](https://github.com/Grow-with-Open-Source/Python-Projects/pull/25 "visit pr \#25") | [/Grow-with-Open-Source/Python-Projects/Digital_Clock/](Digital_Clock "view the result of Digital_Clock") | +| Digital_Clock | [DarkSlayer102](https://github.com/DarkSlayer102 "goto DarkSlayer102 profile"), [swati-londhe](https://github.com/swati-londhe "goto swati-londhe profile") | [#25](https://github.com/Grow-with-Open-Source/Python-Projects/pull/25 "visit pr \#25"), [#41](https://github.com/Grow-with-Open-Source/Python-Projects/pull/41 "visit pr \#41") | [/Grow-with-Open-Source/Python-Projects/Digital_Clock/](Digital_Clock "view the result of Digital_Clock") | | Secure_Password_Manager | [Chaitanya6Nli](https://github.com/Chaitanya6Nli "goto Chaitanya6Nli profile") | [#29](https://github.com/Grow-with-Open-Source/Python-Projects/pull/29 "visit pr \#29") | [/Grow-with-Open-Source/Python-Projects/Secure_Password_Manager/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Secure_Password_Manager "view the result of Secure_Password_Manager") | | Youtube_video_finder_using_geminillm | [veerababu1729](https://github.com/veerababu1729 "goto veerababu1729 profile") | [#32](https://github.com/Grow-with-Open-Source/Python-Projects/pull/32 "visit pr \#32") | [/Grow-with-Open-Source/Python-Projects/Youtube_video_finder_using_geminillm/](Youtube_video_finder_using_geminillm "view the result of Youtube_video_finder_using_geminillm") | | {workflows} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#36](https://github.com/Grow-with-Open-Source/Python-Projects/pull/36 "visit pr \#36"), [#39](https://github.com/Grow-with-Open-Source/Python-Projects/pull/39 "visit pr \#39") | [/Grow-with-Open-Source/Python-Projects/.github/workflows](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github/workflows "view the result of {workflows}") | From 083a7a1f6b85d98486c7e18fcb193992f6a33f68 Mon Sep 17 00:00:00 2001 From: Eyshila Ivanha Date: Wed, 5 Nov 2025 20:28:04 -0300 Subject: [PATCH 037/121] Added Text Analyzer mini project --- Number-Guess/TextAnalyzer/README.md | 0 Number-Guess/TextAnalyzer/Text.py | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 Number-Guess/TextAnalyzer/README.md create mode 100644 Number-Guess/TextAnalyzer/Text.py diff --git a/Number-Guess/TextAnalyzer/README.md b/Number-Guess/TextAnalyzer/README.md new file mode 100644 index 0000000..e69de29 diff --git a/Number-Guess/TextAnalyzer/Text.py b/Number-Guess/TextAnalyzer/Text.py new file mode 100644 index 0000000..6095274 --- /dev/null +++ b/Number-Guess/TextAnalyzer/Text.py @@ -0,0 +1,23 @@ +from collections import Counter + +def analyze_text(text): + words = text.split() + total_words = len(words) + total_chars = len(text) + total_no_spaces = len(text.replace(" ", "")) + + most_common_word = Counter(words).most_common(1)[0] + + print("\nπŸ“Š Text Analysis Results:") + print(f"➑️ Total words: {total_words}") + print(f"➑️ Total characters (with spaces): {total_chars}") + print(f"➑️ Total characters (without spaces): {total_no_spaces}") + print(f"➑️ Most used word: '{most_common_word[0]}' ({most_common_word[1]}x)") + +def main(): + print("🧠 Text Analyzer β€” Python Project") + text = input("Enter or paste the text you want to analyze:\n\n") + analyze_text(text) + +if __name__ == "__main__": + main() From 74745074146a98ab43081e015f92314804ff35dc Mon Sep 17 00:00:00 2001 From: EyshilaIvanha Date: Thu, 6 Nov 2025 08:41:22 -0300 Subject: [PATCH 038/121] Atualizar o README.md --- Number-Guess/TextAnalyzer/README.md | 99 +++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/Number-Guess/TextAnalyzer/README.md b/Number-Guess/TextAnalyzer/README.md index e69de29..138dde4 100644 --- a/Number-Guess/TextAnalyzer/README.md +++ b/Number-Guess/TextAnalyzer/README.md @@ -0,0 +1,99 @@ +🧠 Text Analyzer β€” Python Project + +A simple and efficient text analyzer in Python that calculates basic statistics from user-provided text, such as total words, characters, and the most frequently used word. + + +--- + +πŸš€ Features + +Counts the total number of words + +Counts total characters, both with and without spaces + +Identifies the most used word in the text + +Displays results clearly in the terminal + + + +--- + +🧩 Technologies Used + +Python 3 + +Standard library collections (Counter) + + + +--- + +πŸ“¦ Installation + +1. Clone this repository: + +git clone https://github.com/sheylaghost/text-analyzer.git + + +2. Navigate to the project directory: + +cd text-analyzer + + +3. Run the script: + +python main.py + + + + +--- + +🧠 How to Use + +1. Run the program in your terminal. + + +2. Enter or paste any text when prompted. + + +3. View the automatically generated analysis. + + + +Example: + +🧠 Text Analyzer β€” Python Project +Enter or paste the text you want to analyze: + +Python is amazing. Python is powerful. + +πŸ“Š Text Analysis Results: +➑️ Total words: 5 +➑️ Total characters (with spaces): 39 +➑️ Total characters (without spaces): 34 +➑️ Most used word: 'Python' (2x) + + +--- + +πŸ’‘ Possible Future Improvements + +Make the word count case-insensitive + +Remove punctuation before counting + +Calculate the number of unique words + +Export results to a .txt or .json file + + + +--- + +πŸ§‘β€πŸ’» Author + +Eyshila Ivanha de Brito +Created as a Python learning exercise and open-source contribution. +πŸ’¬ Feel free to open issues or suggest improvements! \ No newline at end of file From 09ec75525463beb73306bf485cf21251d889b27d Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 9 Nov 2025 05:19:26 +0000 Subject: [PATCH 039/121] Updated Contributors Details --- .github/data/contributors-log.json | 6 ++++-- index.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 30fd51e..1cf3ba6 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -118,10 +118,12 @@ }, "Number-Guess": { "contributor-name": [ - "adedayoprcs" + "adedayoprcs", + "sheylaghost" ], "pull-request-number": [ - "19" + "19", + "43" ], "demo-path": "Number-Guess" }, diff --git a/index.md b/index.md index 4a6dfcc..b93070c 100644 --- a/index.md +++ b/index.md @@ -22,7 +22,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Hungry_Serpent | [AnilKumarTeegala](https://github.com/AnilKumarTeegala "goto AnilKumarTeegala profile") | [#16](https://github.com/Grow-with-Open-Source/Python-Projects/pull/16 "visit pr \#16") | [/Grow-with-Open-Source/Python-Projects/Hungry_Serpent/](Hungry_Serpent "view the result of Hungry_Serpent") | | Web-Blocker | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile") | [#14](https://github.com/Grow-with-Open-Source/Python-Projects/pull/14 "visit pr \#14") | [/Grow-with-Open-Source/Python-Projects/Web-Blocker/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Web-Blocker "view the result of Web-Blocker") | | File_Organizer | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile") | [#15](https://github.com/Grow-with-Open-Source/Python-Projects/pull/15 "visit pr \#15") | [/Grow-with-Open-Source/Python-Projects/File_Organizer/](File_Organizer "view the result of File_Organizer") | -| Number-Guess | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile") | [#19](https://github.com/Grow-with-Open-Source/Python-Projects/pull/19 "visit pr \#19") | [/Grow-with-Open-Source/Python-Projects/Number-Guess/](Number-Guess "view the result of Number-Guess") | +| Number-Guess | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile"), [sheylaghost](https://github.com/sheylaghost "goto sheylaghost profile") | [#19](https://github.com/Grow-with-Open-Source/Python-Projects/pull/19 "visit pr \#19"), [#43](https://github.com/Grow-with-Open-Source/Python-Projects/pull/43 "visit pr \#43") | [/Grow-with-Open-Source/Python-Projects/Number-Guess/](Number-Guess "view the result of Number-Guess") | | Remove-Empty-Lines | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile") | [#20](https://github.com/Grow-with-Open-Source/Python-Projects/pull/20 "visit pr \#20") | [/Grow-with-Open-Source/Python-Projects/Remove-Empty-Lines/](Remove-Empty-Lines "view the result of Remove-Empty-Lines") | | Pomodoro-Timer | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile") | [#21](https://github.com/Grow-with-Open-Source/Python-Projects/pull/21 "visit pr \#21") | [/Grow-with-Open-Source/Python-Projects/Pomodoro-Timer/](Pomodoro-Timer "view the result of Pomodoro-Timer") | | {others} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#22](https://github.com/Grow-with-Open-Source/Python-Projects/pull/22 "visit pr \#22") | [/Grow-with-Open-Source/Python-Projects/.github](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github "view the result of {others}") | From 2bb11e56c039af98e27081a1e4d0d6f7e0f3ec3d Mon Sep 17 00:00:00 2001 From: ramanuj-droid Date: Mon, 10 Nov 2025 20:01:19 +0530 Subject: [PATCH 040/121] adding water adder using python --- Image_watermark_Adder/README.md | 19 ++++++++ Image_watermark_Adder/app.py | 53 ++++++++++++++++++++ Image_watermark_Adder/requirements.txt | 2 + Image_watermark_Adder/utils/__init__.py | 0 Image_watermark_Adder/utils/watermark.py | 61 ++++++++++++++++++++++++ 5 files changed, 135 insertions(+) create mode 100644 Image_watermark_Adder/README.md create mode 100644 Image_watermark_Adder/app.py create mode 100644 Image_watermark_Adder/requirements.txt create mode 100644 Image_watermark_Adder/utils/__init__.py create mode 100644 Image_watermark_Adder/utils/watermark.py diff --git a/Image_watermark_Adder/README.md b/Image_watermark_Adder/README.md new file mode 100644 index 0000000..e420fe4 --- /dev/null +++ b/Image_watermark_Adder/README.md @@ -0,0 +1,19 @@ +# πŸ–‹οΈ Image Watermarking Tool + +A simple **Streamlit** app to add **text or logo watermarks** to images. + +--- + +## πŸš€ Features +- Upload image (JPG/PNG) +- Add **text** watermark with adjustable font size, position, and opacity +- Add **logo** watermark with adjustable size, position, and transparency +- Live preview before download +- Download final image as JPG + +--- + +## 🧱 Tech Stack +- **Python 3** +- **Streamlit** +- **Pillow (PIL)** \ No newline at end of file diff --git a/Image_watermark_Adder/app.py b/Image_watermark_Adder/app.py new file mode 100644 index 0000000..0a9bccc --- /dev/null +++ b/Image_watermark_Adder/app.py @@ -0,0 +1,53 @@ +import streamlit as st +from PIL import Image +import os +from utils.watermark import add_text_watermark, add_logo_watermark + +st.set_page_config(page_title=" Image Watermarking Tool", layout="wide") + +st.title(" Image Watermarking Tool") +st.write("Upload an image, add a text or logo watermark, and download the result.") + +os.makedirs("output", exist_ok=True) + +uploaded_file = st.file_uploader("Upload an image", type=["jpg", "jpeg", "png"]) + +if uploaded_file: + image = Image.open(uploaded_file).convert("RGB") + st.image(image, caption="Original Image", use_container_width=True) + + st.sidebar.header(" Settings") + wm_type = st.sidebar.radio("Watermark Type", ["Text", "Logo"]) + + position = st.sidebar.selectbox("Position", ["Top-Left", "Top-Right", "Bottom-Left", "Bottom-Right", "Center"]) + opacity = st.sidebar.slider("Opacity", 0.0, 1.0, 0.5) + + if wm_type == "Text": + text = st.sidebar.text_input("Enter Watermark Text", "Β© MyBrand") + font_size = st.sidebar.slider("Font Size", 20, 100, 40) + + if st.sidebar.button("Apply Text Watermark"): + result = add_text_watermark(image, text, position, opacity, font_size) + output_path = os.path.join("output", "watermarked_text.jpg") + result.save(output_path) + st.image(result, caption="Watermarked Image", use_container_width=True) + st.download_button("Download Image", data=open(output_path, "rb"), file_name="watermarked_text.jpg") + + elif wm_type == "Logo": + logo_file = st.sidebar.file_uploader("Upload Logo (PNG preferred)", type=["png"]) + scale = st.sidebar.slider("Logo Scale", 0.05, 0.5, 0.2) + + if st.sidebar.button("Apply Logo Watermark"): + if logo_file: + logo_path = os.path.join("assets", "temp_logo.png") + os.makedirs("assets", exist_ok=True) + with open(logo_path, "wb") as f: + f.write(logo_file.getbuffer()) + + result = add_logo_watermark(image, logo_path, position, opacity, scale) + output_path = os.path.join("output", "watermarked_logo.jpg") + result.save(output_path) + st.image(result, caption="Watermarked Image", use_container_width=True) + st.download_button("Download Image", data=open(output_path, "rb"), file_name="watermarked_logo.jpg") + else: + st.warning("Please upload a logo image.") diff --git a/Image_watermark_Adder/requirements.txt b/Image_watermark_Adder/requirements.txt new file mode 100644 index 0000000..ff7db04 --- /dev/null +++ b/Image_watermark_Adder/requirements.txt @@ -0,0 +1,2 @@ +streamlit +pillow diff --git a/Image_watermark_Adder/utils/__init__.py b/Image_watermark_Adder/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Image_watermark_Adder/utils/watermark.py b/Image_watermark_Adder/utils/watermark.py new file mode 100644 index 0000000..2476a70 --- /dev/null +++ b/Image_watermark_Adder/utils/watermark.py @@ -0,0 +1,61 @@ +from PIL import Image, ImageDraw, ImageFont, ImageEnhance +import os + +def add_text_watermark(image, text, position, opacity=0.5, font_size=40): + watermark = image.copy() + drawable = ImageDraw.Draw(watermark) + + try: + font = ImageFont.truetype("arial.ttf", font_size) + except: + font = ImageFont.load_default() + + bbox = drawable.textbbox((0, 0), text, font=font) + textwidth = bbox[2] - bbox[0] + textheight = bbox[3] - bbox[1] + + width, height = image.size + margin = 10 + positions = { + "Top-Left": (margin, margin), + "Top-Right": (width - textwidth - margin, margin), + "Bottom-Left": (margin, height - textheight - margin), + "Bottom-Right": (width - textwidth - margin, height - textheight - margin), + "Center": ((width - textwidth) // 2, (height - textheight) // 2) + } + pos = positions.get(position, positions["Bottom-Right"]) + + # Draw text with opacity + drawable.text(pos, text, fill=(255, 255, 255, int(255 * opacity)), font=font) + return watermark + + + +def add_logo_watermark(image, logo_path, position, opacity=0.5, scale=0.2): + """ + Add a logo watermark to the given image. + """ + base = image.convert("RGBA") + logo = Image.open(logo_path).convert("RGBA") + logo_width = int(base.width * scale) + aspect_ratio = logo.height / logo.width + logo_height = int(logo_width * aspect_ratio) + logo = logo.resize((logo_width, logo_height)) + + + alpha = logo.split()[3] + alpha = ImageEnhance.Brightness(alpha).enhance(opacity) + logo.putalpha(alpha) + + margin = 10 + positions = { + "Top-Left": (margin, margin), + "Top-Right": (base.width - logo_width - margin, margin), + "Bottom-Left": (margin, base.height - logo_height - margin), + "Bottom-Right": (base.width - logo_width - margin, base.height - logo_height - margin), + "Center": ((base.width - logo_width) // 2, (base.height - logo_height) // 2) + } + pos = positions.get(position, positions["Bottom-Right"]) + + base.paste(logo, pos, logo) + return base.convert("RGB") From 9cc55ea6b34e050650b067fbf4afaaafec7c1ef0 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 10 Nov 2025 15:09:54 +0000 Subject: [PATCH 041/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 1cf3ba6..b838bef 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -246,5 +246,14 @@ "40" ], "demo-path": "Temperature_Converter" + }, + "Image_watermark_Adder": { + "contributor-name": [ + "ramanuj-droid" + ], + "pull-request-number": [ + "45" + ], + "demo-path": "Image_watermark_Adder" } } \ No newline at end of file diff --git a/index.md b/index.md index b93070c..f58a5a6 100644 --- a/index.md +++ b/index.md @@ -36,6 +36,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Gesture_Volume_Control | [10mudassir007](https://github.com/10mudassir007 "goto 10mudassir007 profile") | [#34](https://github.com/Grow-with-Open-Source/Python-Projects/pull/34 "visit pr \#34") | [/Grow-with-Open-Source/Python-Projects/Gesture_Volume_Control/](Gesture_Volume_Control "view the result of Gesture_Volume_Control") | | Password-Checker | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#37](https://github.com/Grow-with-Open-Source/Python-Projects/pull/37 "visit pr \#37") | [/Grow-with-Open-Source/Python-Projects/Password-Checker/](Password-Checker "view the result of Password-Checker") | | Temperature_Converter | [omprakash0702](https://github.com/omprakash0702 "goto omprakash0702 profile") | [#40](https://github.com/Grow-with-Open-Source/Python-Projects/pull/40 "visit pr \#40") | [/Grow-with-Open-Source/Python-Projects/Temperature_Converter/](Temperature_Converter "view the result of Temperature_Converter") | +| Image_watermark_Adder | [ramanuj-droid](https://github.com/ramanuj-droid "goto ramanuj-droid profile") | [#45](https://github.com/Grow-with-Open-Source/Python-Projects/pull/45 "visit pr \#45") | [/Grow-with-Open-Source/Python-Projects/Image_watermark_Adder/](Image_watermark_Adder "view the result of Image_watermark_Adder") | From e099e21159dc782532779e3d943877094ea6f320 Mon Sep 17 00:00:00 2001 From: ROWENBEN KAMAU NGANGA Date: Wed, 12 Nov 2025 00:02:48 +0300 Subject: [PATCH 042/121] feat: Add Clear Playlist functionality to File menu --- Music-Player/music_player.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Music-Player/music_player.py b/Music-Player/music_player.py index 82d232f..f3068a6 100644 --- a/Music-Player/music_player.py +++ b/Music-Player/music_player.py @@ -53,9 +53,21 @@ def add_to_playlist(filename): playlist.insert(index, filename_path) index += 1 +def clear_playlist(): + global playlist + stop_music() + # Clear the internal playlist list + playlist.clear() + # Clear the Listbox GUI element + playlistbox.delete(0, END) + statusbar['text'] = "Playlist Cleared" + + menubar.add_cascade(label="File", menu=subMenu) subMenu.add_command(label="Open", command=browse_file) +subMenu.add_command(label="Clear Playlist", command=clear_playlist) +subMenu.add_separator() subMenu.add_command(label="Exit", command=root.destroy) From 00a6c933eef13c7a053a6197aab895321b7d3a69 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 14 Nov 2025 04:42:25 +0000 Subject: [PATCH 043/121] Updated Contributors Details --- .github/data/contributors-log.json | 6 ++++-- index.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index b838bef..5112272 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -73,10 +73,12 @@ }, "Music-Player": { "contributor-name": [ - "ShashiNova" + "ShashiNova", + "Robekaben254" ], "pull-request-number": [ - "12" + "12", + "47" ], "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Music-Player/" }, diff --git a/index.md b/index.md index f58a5a6..a7a64ca 100644 --- a/index.md +++ b/index.md @@ -17,7 +17,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Wine-Quality-Analysis | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#9](https://github.com/Grow-with-Open-Source/Python-Projects/pull/9 "visit pr \#9") | [/Grow-with-Open-Source/Python-Projects/Wine-Quality-Analysis/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Wine-Quality-Analysis "view the result of Wine-Quality-Analysis") | | Spell-Checker | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#10](https://github.com/Grow-with-Open-Source/Python-Projects/pull/10 "visit pr \#10") | [/Grow-with-Open-Source/Python-Projects/Spell-Checker/](Spell-Checker "view the result of Spell-Checker") | | QRCODE-Generator | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#11](https://github.com/Grow-with-Open-Source/Python-Projects/pull/11 "visit pr \#11") | [/Grow-with-Open-Source/Python-Projects/QRCODE-Generator/](QRCODE-Generator "view the result of QRCODE-Generator") | -| Music-Player | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile") | [#12](https://github.com/Grow-with-Open-Source/Python-Projects/pull/12 "visit pr \#12") | [/Grow-with-Open-Source/Python-Projects/Music-Player/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Music-Player/ "view the result of Music-Player") | +| Music-Player | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile"), [Robekaben254](https://github.com/Robekaben254 "goto Robekaben254 profile") | [#12](https://github.com/Grow-with-Open-Source/Python-Projects/pull/12 "visit pr \#12"), [#47](https://github.com/Grow-with-Open-Source/Python-Projects/pull/47 "visit pr \#47") | [/Grow-with-Open-Source/Python-Projects/Music-Player/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Music-Player/ "view the result of Music-Player") | | Password-Generator | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile") | [#13](https://github.com/Grow-with-Open-Source/Python-Projects/pull/13 "visit pr \#13") | [/Grow-with-Open-Source/Python-Projects/Password-Generator/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Password-Generator/ "view the result of Password-Generator") | | Hungry_Serpent | [AnilKumarTeegala](https://github.com/AnilKumarTeegala "goto AnilKumarTeegala profile") | [#16](https://github.com/Grow-with-Open-Source/Python-Projects/pull/16 "visit pr \#16") | [/Grow-with-Open-Source/Python-Projects/Hungry_Serpent/](Hungry_Serpent "view the result of Hungry_Serpent") | | Web-Blocker | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile") | [#14](https://github.com/Grow-with-Open-Source/Python-Projects/pull/14 "visit pr \#14") | [/Grow-with-Open-Source/Python-Projects/Web-Blocker/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Web-Blocker "view the result of Web-Blocker") | From bb81dc2839490fda7d2d29aea8217f6d57ea1ada Mon Sep 17 00:00:00 2001 From: sharat Date: Sun, 7 Dec 2025 12:27:06 +0530 Subject: [PATCH 044/121] Add baseline essential-gene classifier with main.py, requirements, and README --- Binary-Gene-Classifier-Model/README.md | 179 ++++++++++++++++++ Binary-Gene-Classifier-Model/main.py | 82 ++++++++ Binary-Gene-Classifier-Model/requirements.txt | 5 + 3 files changed, 266 insertions(+) create mode 100644 Binary-Gene-Classifier-Model/README.md create mode 100644 Binary-Gene-Classifier-Model/main.py create mode 100644 Binary-Gene-Classifier-Model/requirements.txt diff --git a/Binary-Gene-Classifier-Model/README.md b/Binary-Gene-Classifier-Model/README.md new file mode 100644 index 0000000..896472b --- /dev/null +++ b/Binary-Gene-Classifier-Model/README.md @@ -0,0 +1,179 @@ +# Essential Gene Classification from DNA Sequences + +This project implements a baseline machine learning pipeline to classify bacterial genes as essential or non-essential using DNA sequence information from the **macwiatrak/bacbench-essential-genes-dna** dataset (Hugging Face Datasets). + +## Project Overview + +The notebook: +- Loads the BacBench essential genes dataset (train/validation/test splits). +- Cleans and simplifies the dataset by removing unused metadata columns. +- Encodes DNA sequences into integer representations using a custom nucleotide mapping. +- Extracts non-overlapping 4-mer (length-4 subsequence) count features. +- Trains a Logistic Regression classifier on the resulting feature vectors. +- Evaluates model performance using accuracy and F1 score on validation and test splits. + +This serves as a simple, fast baseline for essential-gene prediction from raw DNA sequences. + +## Dataset + +The project uses the `macwiatrak/bacbench-essential-genes-dna` dataset loaded via `datasets.load_dataset`. +Each split (train, validation, test) originally contains, among others, the following fields: +- `dna_seq`: DNA sequence of the gene. +- `essential`: Label indicating whether a gene is essential (`"Yes"` or `"No"`). +- Several metadata columns (e.g., `genome_name`, `start`, `end`, `protein_id`, `strand`, `product`, `__index_level_0__`). + +In this notebook, the unnecessary metadata columns are dropped, and only `dna_seq` and `essential` are retained for modeling. + +## Preprocessing + +Key preprocessing steps: + +- **Label encoding** + The `essential` field is converted from string to integer: + - `"Yes"` β†’ `1` + - `"No"` β†’ `0` + +- **DNA character mapping** + Each base in `dna_seq` is mapped to an integer to prioritize efficiency: + - `A β†’ 0`, `T β†’ 1`, `C β†’ 2`, `G β†’ 3` + - Ambiguous bases: `N β†’ 4`, `K β†’ 5`, `R β†’ 6`, `S β†’ 7`, `Y β†’ 8`, `M β†’ 9`, `W β†’ 10` + +- **Sequence encoding** + A helper function converts each DNA string into a list of integers using the mapping above, discarding characters not present in the map. + +## Feature Extraction + +The feature representation is based on **non-overlapping 4-mers**: + +- The number of possible symbols is `NUM_BASES = 11`. +- The total number of distinct 4-mers is `NUM_4MERS = 11^4 = 14641`. +- For each encoded sequence, the notebook: + - Iterates with step size `STEP = 4` to form non-overlapping 4-mers. + - Maps each 4-mer to a unique integer index using positional encoding: + \[ + \text{kmer\_int} = b_0 \cdot 11^3 + b_1 \cdot 11^2 + b_2 \cdot 11 + b_3 + \] + - Increments the corresponding position in a length-14641 count vector. + +The resulting dense feature matrix is then converted to a SciPy CSR sparse matrix for memory efficiency. + +## Model + +The classification model is a **Logistic Regression** from `sklearn.linear_model` with: + +- `solver='saga'` +- `max_iter=2000` +- `n_jobs=-1` (parallel training where possible) + +Training is performed on the 4-mer count features of the train split. + +## Evaluation + +Model performance is evaluated on both validation and test splits using: + +- **Accuracy** (`sklearn.metrics.accuracy_score`) +- **F1 Score** (`sklearn.metrics.f1_score`) + +The notebook prints: + +- Validation Accuracy +- Validation F1 Score +- Test Accuracy +- Test F1 Score + +These metrics provide an initial benchmark for this simple 4-mer + Logistic Regression approach. + +## Requirements + +Main Python dependencies: + +- `pandas` +- `numpy` +- `scipy` +- `datasets` (Hugging Face Datasets) +- `scikit-learn` + +Example installation (if running locally): +`pip install pandas numpy scipy datasets scikit-learn` + +## How to Run + +1. Open the notebook in Google Colab or your preferred environment. +2. Ensure all required packages are installed. +3. Run the cells in order: + - Dataset loading and column filtering + - Label encoding + - DNA mapping and sequence encoding + - 4-mer feature extraction + - Model training + - Evaluation on validation and test splits + +## Possible Extensions + +- Use overlapping k-mers or different k-mer sizes to capture more sequence context. +- Try more expressive models (e.g., tree-based methods, neural networks). +- Explore alternative encodings (e.g., one-hot, embeddings, or biologically informed encodings). +- Add cross-validation and hyperparameter tuning for more robust performance estimates. +# Issues with Current Gene Classifier + +1. **Class Imbalance** + - Essential genes (`1`) are much rarer than non-essential genes (`0`). + - Logistic Regression tends to predict the majority class, lowering F1 score on validation. + +2. **Simple Features** + - Using **non-overlapping 4-mer counts** loses many sequence patterns. + - Linear combinations of k-mer counts may not capture complex dependencies between nucleotides. + +3. **Non-Overlapping k-mers** + - Step size of 4 skips many overlapping patterns in the DNA sequence. + - Important motifs or codon patterns might be missed. + +4. **Normalization** + - Raw 4-mer counts vary with sequence length. + - Longer sequences dominate the feature vectors, potentially biasing the classifier. + +5. **Linear Model Limitations** + - Logistic Regression is a linear classifier. + - Cannot capture non-linear interactions between k-mers that may be biologically relevant. + +6. **Potential Data Leakage** + - Some sequences in train/test splits may be very similar or overlapping. + - This can inflate test accuracy artificially, as seen in the high test F1 compared to validation. + +7. **Limited Biological Context** + - Only nucleotide sequences are considered. + - Other biological features (gene location, GC content, protein info) are ignored, which may be predictive of essentiality. + +8. **Sparse Signal** + - Many 4-mer combinations may never appear, making feature vectors sparse. + - Sparse linear models may struggle to generalize with limited data for certain patterns. +9. **Mapping** + - I did not take into account whether W which is mapped to 10 will be treated as 10 or 1 and 0 which would essentialy derail the classification +## Model Evaluation + +The baseline Logistic Regression classifier was evaluated on the validation and test splits using **accuracy** and **F1 score**: + +| Split | Accuracy | F1 Score | +|------------|---------|----------| +| Validation | 0.45 | 0.25 | +| Test | 0.90 | 0.80 | + +> ⚠️ Note: +> - Validation F1 is low due to class imbalance and simple linear model. +> - The high test metrics may be artificially inflated if some sequences are very similar across splits. +> - This baseline serves as a starting point for further improvements. + + +## Credits + +- **Dataset:** [BacBench Essential Genes DNA Dataset](https://huggingface.co/macwiatrak/bacbench-essential-genes-dna) by Mac Wiatrak et al., hosted on HuggingFace. +- **Libraries & Tools:** + - [HuggingFace `datasets`](https://huggingface.co/docs/datasets) for data loading and preprocessing + - [NumPy](https://numpy.org/) for numerical operations + - [SciPy](https://www.scipy.org/) for scientific computing + - [scikit-learn](https://scikit-learn.org/) for machine learning models and evaluation metrics +- **Inspired by:** Standard bioinformatics workflows for DNA k-mer feature extraction and baseline classification. +-**Workflow & Model Implementation:** Done by Sharat Doddihal +### Note +This was my first attempt at creating a Ml model by myself without too much use from AI.AI has been used here but only for helping with the debugging process. +Overall I am happy with how this turned as this was a great learning experience.There are many fundamental errors that mess with the accuracy. \ No newline at end of file diff --git a/Binary-Gene-Classifier-Model/main.py b/Binary-Gene-Classifier-Model/main.py new file mode 100644 index 0000000..82e2a4f --- /dev/null +++ b/Binary-Gene-Classifier-Model/main.py @@ -0,0 +1,82 @@ +import pandas as pd +import numpy as np +from scipy import stats +from datasets import load_dataset +from scipy.sparse import csr_matrix +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import accuracy_score, f1_score +# Load datasets +ds = load_dataset("macwiatrak/bacbench-essential-genes-dna", split="validation") +ds = ds.remove_columns(['genome_name', 'start', 'end', 'protein_id', + 'strand', 'product', '__index_level_0__']) +ds1 = load_dataset("macwiatrak/bacbench-essential-genes-dna", split="train") +ds1 = ds1.remove_columns(['genome_name', 'start', 'end', 'protein_id', + 'strand', 'product', '__index_level_0__']) +ds2 = load_dataset("macwiatrak/bacbench-essential-genes-dna", split="test") +ds2 = ds2.remove_columns(['genome_name', 'start', 'end', 'protein_id', + 'strand', 'product', '__index_level_0__']) +# Convert Yes/No to 1/0 +def convert(example): + example["essential"] = [1 if x == "Yes" else 0 for x in example["essential"]] + return example +ds = ds.map(convert) +ds1 = ds1.map(convert) +ds2 = ds2.map(convert) +# DNA base mapping +dna_map = { + "A": 0, "T": 1, "C": 2, "G": 3, + "N": 4, "K": 5, "R": 6, "S": 7, + "Y": 8, "M": 9, "W": 10 +} +# encode sequences in each split +for i in range(len(ds1)): + ds1[i]["dna_seq"] = [dna_map[base] for base in ds1[i]["dna_seq"]] +for i in range(len(ds)): + ds[i]["dna_seq"] = [dna_map[base] for base in ds[i]["dna_seq"]] +for i in range(len(ds2)): + ds2[i]["dna_seq"] = [dna_map[base] for base in ds2[i]["dna_seq"]] +# 4-mer encoding utilities +NUM_BASES = len(dna_map) +NUM_4MERS = NUM_BASES ** 4 +STEP = 4 +def encode_sequence(seq, mapping=dna_map): + return [mapping[base] for base in seq if base in mapping] +def sequence_to_4mer_counts(seq, step=STEP): + counts = np.zeros(NUM_4MERS, dtype=int) + for i in range(0, len(seq) - (step - 1), step): + kmer = seq[i:i + step] + if len(kmer) < step: + continue + kmer_int = ( + kmer[0] * NUM_BASES ** 3 + + kmer[1] * NUM_BASES ** 2 + + kmer[2] * NUM_BASES + + kmer[3] + ) + counts[kmer_int] += 1 + return counts +# Prepare dataset for ML +def prepare_dataset(ds_split): + def _map_dna_sequence_to_integers(batch): + return {"dna_seq": [encode_sequence(seq_str) for seq_str in batch["dna_seq"]]} + ds_processed = ds_split.map(_map_dna_sequence_to_integers, batched=True) + X_dense = np.array([sequence_to_4mer_counts(item["dna_seq"]) for item in ds_processed]) + y = np.array([item["essential"][0] for item in ds_processed]) + return X_dense, y +X_train_dense, y_train = prepare_dataset(ds1) +X_val_dense, y_val = prepare_dataset(ds) +X_test_dense, y_test = prepare_dataset(ds2) +# sparse conversion +X_train = csr_matrix(X_train_dense) +X_val = csr_matrix(X_val_dense) +X_test = csr_matrix(X_test_dense) +# Train classifier +clf = LogisticRegression(max_iter=2000, solver='saga', n_jobs=-1) +clf.fit(X_train, y_train) +# Evaluation +y_pred_val = clf.predict(X_val) +print("Validation Accuracy:", accuracy_score(y_val, y_pred_val)) +print("Validation F1 Score:", f1_score(y_val, y_pred_val)) +y_pred_test = clf.predict(X_test) +print("Test Accuracy:", accuracy_score(y_test, y_pred_test)) +print("Test F1 Score:", f1_score(y_test, y_pred_test)) \ No newline at end of file diff --git a/Binary-Gene-Classifier-Model/requirements.txt b/Binary-Gene-Classifier-Model/requirements.txt new file mode 100644 index 0000000..3266877 --- /dev/null +++ b/Binary-Gene-Classifier-Model/requirements.txt @@ -0,0 +1,5 @@ +numpy==1.25.0 +pandas==2.0.1 +scipy==1.11.0 +scikit-learn==1.3.0 +datasets==2.16.0 \ No newline at end of file From d44802aa639b44dbdf98c6e6799b4ef7cabaa4ff Mon Sep 17 00:00:00 2001 From: PieBerlin Date: Tue, 23 Dec 2025 21:09:31 +0300 Subject: [PATCH 045/121] dynamic-password-generator --- Password-Generator/dynamic_password.py | 73 ++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 Password-Generator/dynamic_password.py diff --git a/Password-Generator/dynamic_password.py b/Password-Generator/dynamic_password.py new file mode 100644 index 0000000..7321659 --- /dev/null +++ b/Password-Generator/dynamic_password.py @@ -0,0 +1,73 @@ +"""_summary_ + implementing password generation program that allows symbols and capital letters if the user wants to include them + + Returns: + password +""" + + + +import random + + +#constants +SMALL_LETTERS="abcdefghijklmnopqrstuvwxyz" +CAPITAL_LETTERS="ABCDEFGHIJKLMNOPQRSTUVWXYZ" +SYMBOLS="~!@#$%^&*()_-+={[}]|:;<>?" +MIN_LENGTH=12 +MAX_LENGTH=24 + + +#function to generate the password +def dynamic_password(passlength,capitals=False,symbols=False): + characters=SMALL_LETTERS + if capitals: + characters+=CAPITAL_LETTERS + if symbols: + characters+=SYMBOLS + password="" + for i in range(0,passlength): + randomletter=random.choice(characters) + password+=(randomletter) + + return password + +#Function to take user inputs and validate them +def inputs_validation(): + while True: + + pass_length=input(f"Enter password length ({MIN_LENGTH}-{MAX_LENGTH}): ") + input_capitals=input("Do we include capitals (y/n): ").strip().lower() + input_symbols=input("Do we include symbols (y/n): ").strip().lower() + + # 1. Validate both inputs at once + if input_capitals not in ['y', 'n'] or input_symbols not in ['y', 'n']: + print("Please type 'y' or 'n' for the options.") + continue # Restarts the loop to ask again + + #2. convert to booleans + capitals=(input_capitals=='y') + symbols=(input_symbols=='y') + + if pass_length.isdigit(): + #validate password length + length=int(pass_length) + if 12<=length<=24: + return length,capitals,symbols + else: + print("Please enter password length within the range!!") + continue + print("Password length should be a Number") + + + + + +def main(): + length,capitals,symbols=inputs_validation() + password=dynamic_password(length,capitals,symbols) + print(f"Your Generated Password is : {password}") + + +if __name__ == "__main__": + main() \ No newline at end of file From e1d4757a6f807d175f46a451091df2766e497b85 Mon Sep 17 00:00:00 2001 From: Bassel Darwesh Date: Fri, 26 Dec 2025 03:52:06 +0200 Subject: [PATCH 046/121] Add Auto Clicker project --- Auto-Clicker/README.md | 59 +++++++++++++++++++++++++++++++++++ Auto-Clicker/auto_clicker.py | 34 ++++++++++++++++++++ Auto-Clicker/requirements.txt | 2 ++ 3 files changed, 95 insertions(+) create mode 100644 Auto-Clicker/README.md create mode 100644 Auto-Clicker/auto_clicker.py create mode 100644 Auto-Clicker/requirements.txt diff --git a/Auto-Clicker/README.md b/Auto-Clicker/README.md new file mode 100644 index 0000000..596d9e0 --- /dev/null +++ b/Auto-Clicker/README.md @@ -0,0 +1,59 @@ +# Auto Clicker + +A Python automation tool that allows you to automatically click the mouse at rapid intervals. This is useful for repetitive clicking tasks and can be controlled via keyboard hotkeys. + +## Features + +- Start and stop automatic clicking with keyboard hotkeys +- Adjustable click interval (currently set to 1ms between clicks) +- Simple keyboard controls for easy on/off toggling +- Minimal resource usage + +## Requirements + +- Python 3.x +- pyautogui library +- keyboard library + +## Installation + +Install the required dependencies using pip: + +```bash +pip install -r requirements.txt +``` + +## How to Use + +1. Run the script by executing the `auto_clicker.py` file: + +```bash +python auto_clicker.py +``` + +2. The program will start and display instructions: + - Press `'S'` to **start** the auto clicker + - Press `'E'` to **stop** the auto clicker + - Press `'Q'` to **quit** the program + +3. Once started (by pressing 'S'), the auto clicker will automatically perform mouse clicks at the current cursor position at fixed intervals (Can be edited in the code). + +4. Press 'E' to stop the clicking, and press 'Q' to exit the program entirely. + +## Caution + +⚠️ **WARNING**: Use this tool responsibly. Automated clicking can: +- Interfere with other applications +- Cause unintended actions if not carefully controlled +- May violate terms of service for certain applications or games + +Always ensure you have full control over what the auto clicker is doing before starting it. + +## How It Works + +The script uses the following libraries: + +- **pyautogui**: For performing automated mouse clicks +- **keyboard**: For detecting keyboard hotkey presses to control the clicker + +The program runs in an infinite loop, continuously checking if the 'q' key is pressed (to exit) and performing clicks when the `clicking` flag is set to `True`. diff --git a/Auto-Clicker/auto_clicker.py b/Auto-Clicker/auto_clicker.py new file mode 100644 index 0000000..c0c9d23 --- /dev/null +++ b/Auto-Clicker/auto_clicker.py @@ -0,0 +1,34 @@ +import pyautogui +import keyboard +import time + +clicking = False + + +def start_clicking(): + global clicking + clicking = True + print("Auto clicker started") + + +def stop_clicking(): + global clicking + clicking = False + print("Auto clicker stopped") + + +keyboard.add_hotkey("s", start_clicking) +keyboard.add_hotkey("e", stop_clicking) + +print("Press 'S' to start clicking") +print("Press 'E' to stop clicking") +print("Press 'Q' to quit") + +while True: + if clicking: + pyautogui.click() + time.sleep(0.001) + + if keyboard.is_pressed("q"): + print("Exiting program") + break diff --git a/Auto-Clicker/requirements.txt b/Auto-Clicker/requirements.txt new file mode 100644 index 0000000..215233d --- /dev/null +++ b/Auto-Clicker/requirements.txt @@ -0,0 +1,2 @@ +pyautogui +keyboard From 88643fa37ab7eee18d7809dbab7d47469d7f787a Mon Sep 17 00:00:00 2001 From: tech-z <152055911+Achi-Vyshnavi@users.noreply.github.com> Date: Fri, 26 Dec 2025 19:46:00 +0530 Subject: [PATCH 047/121] Improve animalguess.py: modularize, fix typos, remove globals, add input validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removed global variable `score` and replaced with return values for better modularity. - Fixed typos in questions and answers (e.g., "larget" β†’ "largest"). - Added input validation to ensure non-empty guesses. - Wrapped the game logic in a main() function for reusability. - Enhanced feedback messages with clearer prompts and emojis. - Overall improvements make the code cleaner, more maintainable, and easier to extend. --- Animal-Guess/animalguess.py | 63 ++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/Animal-Guess/animalguess.py b/Animal-Guess/animalguess.py index 6dc3fe4..62dbd77 100644 --- a/Animal-Guess/animalguess.py +++ b/Animal-Guess/animalguess.py @@ -1,25 +1,44 @@ + +# Improved version with modular structure, input validation, and typo fixes + def check_guess(guess, answer): - global score - still_guessing = True + """ + Checks the user's guess against the correct answer. + Allows up to 3 attempts. + Returns 1 if correct, 0 otherwise. + """ attempt = 0 - while still_guessing and attempt < 3: - if guess.lower() == answer.lower(): - print("Correct Answer") - score = score + 1 - still_guessing = False + while attempt < 3: + if guess.lower().strip() == answer.lower(): + print("βœ… Correct Answer!") + return 1 # increment score else: - if attempt < 2: - guess = input("Sorry Wrong Answer, try again") - attempt = attempt + 1 - if attempt == 3: - print("The Correct answer is ",answer ) - -score = 0 -print("Guess the Animal") -guess1 = input("Which bear lives at the North Pole? ") -check_guess(guess1, "polar bear") -guess2 = input("Which is the fastest land animal? ") -check_guess(guess2, "Cheetah") -guess3 = input("Which is the larget animal? ") -check_guess(guess3, "Blue Whale") -print("Your Score is "+ str(score)) + attempt += 1 + if attempt < 3: + guess = input("❌ Wrong answer. Try again: ").strip() + print("The correct answer is:", answer) + return 0 + +def main(): + """ + Main game function. Loops through all questions and calculates the total score. + """ + questions = [ + ("Which bear lives at the North Pole?", "polar bear"), + ("Which is the fastest land animal?", "cheetah"), + ("Which is the largest animal?", "blue whale") + ] + + print("🦁 Welcome to 'Guess the Animal' Game! 🐘") + score = 0 + + for question, answer in questions: + guess = input(question + " ").strip() + while not guess: + guess = input("Please enter a valid guess: ").strip() + score += check_guess(guess, answer) + + print(f"\nπŸ† Your Total Score is: {score} out of {len(questions)}") + +if __name__ == "__main__": + main() From 59b7b23ba8afb7bb595a0d2dfd5f6529e625fa4a Mon Sep 17 00:00:00 2001 From: tech-z <152055911+Achi-Vyshnavi@users.noreply.github.com> Date: Fri, 26 Dec 2025 20:08:18 +0530 Subject: [PATCH 048/121] Enhance Coin-Poison game: add levels, lives, shield, and dynamic score - Introduced levels: game speed and difficulty increase after clearing coins - Added 3-life system before game over - Added shield power-up that protects from poison collision - Display updated score, coins left, lives, and level dynamically - Refactored spawn and reset logic for better code structure - Significant gameplay improvements to make game more engaging and robust --- Coin-Poison/coin-poison.py | 142 +++++++++++++++++++++++++++---------- 1 file changed, 106 insertions(+), 36 deletions(-) diff --git a/Coin-Poison/coin-poison.py b/Coin-Poison/coin-poison.py index 68a7147..fa8aaf6 100644 --- a/Coin-Poison/coin-poison.py +++ b/Coin-Poison/coin-poison.py @@ -1,24 +1,32 @@ import pygame import random + pygame.init() +# ================== CONFIG ================== GRID_SIZE = 40 -GRID_WIDTH = 17 -GRID_HEIGHT = 17 +GRID_WIDTH = 17 +GRID_HEIGHT = 17 SCREEN_WIDTH = GRID_SIZE * GRID_WIDTH SCREEN_HEIGHT = GRID_SIZE * GRID_HEIGHT -BG_COLOR = (30, 30, 30) + +BG_COLOR = (30, 30, 30) PLAYER_COLOR = (0, 255, 0) COIN_COLOR = (255, 215, 0) POISON_COLOR = (255, 0, 0) +SHIELD_COLOR = (0, 191, 255) + FPS = 15 screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) -pygame.display.set_caption("coin&poison") +pygame.display.set_caption("Coin & Poison Adventure") font = pygame.font.Font(None, 36) +# ================== CLASSES ================== + class Player(pygame.sprite.Sprite): + """Player controlled by arrow keys""" def __init__(self): super().__init__() self.image = pygame.Surface((GRID_SIZE, GRID_SIZE)) @@ -26,15 +34,18 @@ def __init__(self): self.rect = self.image.get_rect() self.rect.topleft = (0, 0) self.score = 0 + self.lives = 3 + self.shield = False def move(self, dx, dy): new_rect = self.rect.move(dx * GRID_SIZE, dy * GRID_SIZE) - if 0 <= new_rect.left < SCREEN_WIDTH and 0 <= new_rect.top < SCREEN_HEIGHT: # within limits + if 0 <= new_rect.left < SCREEN_WIDTH and 0 <= new_rect.top < SCREEN_HEIGHT: self.rect = new_rect else: - self.score -= 1 # hit the boundary + self.score -= 1 # penalize hitting boundary class Coin(pygame.sprite.Sprite): + """Collectible coin""" def __init__(self, x, y): super().__init__() self.image = pygame.Surface((GRID_SIZE, GRID_SIZE)) @@ -43,6 +54,7 @@ def __init__(self, x, y): self.rect.topleft = (x * GRID_SIZE, y * GRID_SIZE) class Poison(pygame.sprite.Sprite): + """Poison block that reduces life""" def __init__(self, x, y): super().__init__() self.image = pygame.Surface((GRID_SIZE, GRID_SIZE)) @@ -50,8 +62,19 @@ def __init__(self, x, y): self.rect = self.image.get_rect() self.rect.topleft = (x * GRID_SIZE, y * GRID_SIZE) -# to spawn coin and poison blocks without overlapping with each other and player +class Shield(pygame.sprite.Sprite): + """Power-up shield to protect from poison""" + def __init__(self, x, y): + super().__init__() + self.image = pygame.Surface((GRID_SIZE, GRID_SIZE)) + self.image.fill(SHIELD_COLOR) + self.rect = self.image.get_rect() + self.rect.topleft = (x * GRID_SIZE, y * GRID_SIZE) + +# ================== HELPER FUNCTIONS ================== + def spawn_objects(num_objects, avoid_positions): + """Spawn objects without overlapping""" objects = [] while len(objects) < num_objects: x = random.randint(0, GRID_WIDTH - 1) @@ -61,35 +84,67 @@ def spawn_objects(num_objects, avoid_positions): avoid_positions.add((x, y)) return objects +def reset_level(): + """Reset coins, poisons, and optionally add shield""" + global coins, poisons, shield_powerups, all_sprites, avoid_positions, FPS, level + level += 1 + FPS += 1 # increase speed for next level + num_coins = 10 + level * 2 + num_poisons = 5 + level + + # Clear old objects + for sprite in coins.sprites() + poisons.sprites() + shield_powerups.sprites(): + all_sprites.remove(sprite) + + # Reset positions + avoid_positions = {(player.rect.x // GRID_SIZE, player.rect.y // GRID_SIZE)} + + # Spawn coins + coin_positions = spawn_objects(num_coins, avoid_positions) + coins = pygame.sprite.Group() + for pos in coin_positions: + coin = Coin(*pos) + coins.add(coin) + all_sprites.add(coin) + + # Spawn poisons + poison_positions = spawn_objects(num_poisons, avoid_positions) + poisons = pygame.sprite.Group() + for pos in poison_positions: + poison = Poison(*pos) + poisons.add(poison) + all_sprites.add(poison) + + # Spawn shield occasionally + shield_positions = spawn_objects(1, avoid_positions) + shield_powerups = pygame.sprite.Group() + for pos in shield_positions: + shield = Shield(*pos) + shield_powerups.add(shield) + all_sprites.add(shield) + +# ================== GAME INIT ================== + player = Player() all_sprites = pygame.sprite.Group() all_sprites.add(player) -# total no of coins and poison blocks -num_coins = 20 -num_poisons = 10 - +coins = pygame.sprite.Group() +poisons = pygame.sprite.Group() +shield_powerups = pygame.sprite.Group() avoid_positions = {(player.rect.x // GRID_SIZE, player.rect.y // GRID_SIZE)} -poison_positions = spawn_objects(num_poisons, avoid_positions) -poisons = pygame.sprite.Group() -for pos in poison_positions: - poison = Poison(*pos) - poisons.add(poison) - all_sprites.add(poison) - -coin_positions = spawn_objects(num_coins, avoid_positions) -coins = pygame.sprite.Group() -for pos in coin_positions: - coin = Coin(*pos) - coins.add(coin) - all_sprites.add(coin) +level = 1 +reset_level() clock = pygame.time.Clock() - running = True + +# ================== GAME LOOP ================== + while running: - clock.tick(FPS) + clock.tick(FPS) + for event in pygame.event.get(): if event.type == pygame.QUIT: running = False @@ -104,24 +159,39 @@ def spawn_objects(num_objects, avoid_positions): if keys[pygame.K_DOWN]: player.move(0, 1) + # Collect coins collected_coins = pygame.sprite.spritecollide(player, coins, True) player.score += len(collected_coins) - if pygame.sprite.spritecollideany(player, poisons): - print(f"collided with poison block! score: {player.score}") - running = False + # Collect shield + if pygame.sprite.spritecollide(player, shield_powerups, True): + player.shield = True + print("Shield activated! Next poison hit will be ignored.") - # if all coins collected, won + # Poison collision + if pygame.sprite.spritecollideany(player, poisons): + if player.shield: + player.shield = False + print("Shield protected you from poison!") + else: + player.lives -= 1 + if player.lives == 0: + print(f"Game Over! Final Score: {player.score}") + running = False + else: + print(f"Hit poison! Lives left: {player.lives}") + + # Level completed if len(coins) == 0: - print(f"collected all coins! score: {player.score}") - running = False + print(f"Level {level} cleared! Moving to next level...") + reset_level() + # Draw everything screen.fill(BG_COLOR) all_sprites.draw(screen) - - score_text = font.render(f"score: {player.score}", True, (255, 255, 255)) - screen.blit(score_text, (10, 10)) + score_text = font.render(f"Score: {player.score} | Coins left: {len(coins)} | Lives: {player.lives} | Level: {level}", True, (255, 255, 255)) + screen.blit(score_text, (10, 10)) pygame.display.flip() -pygame.quit() \ No newline at end of file +pygame.quit() From 70ae268355f93f86f24d6aabe67b37b0235adfa5 Mon Sep 17 00:00:00 2001 From: tech-z <152055911+Achi-Vyshnavi@users.noreply.github.com> Date: Fri, 26 Dec 2025 20:31:04 +0530 Subject: [PATCH 049/121] Enhance Number-Guess game: add levels, score system, attempts limit, and replay This update significantly improves the Number-Guess game by introducing multiple new features: 1. **Levels with increasing difficulty:** Each new level increases the range of numbers to guess, making the game progressively harder. 2. **Score system:** Players earn points based on remaining attempts per level, rewarding faster correct guesses. 3. **Attempts limit:** Each level allows a maximum of 5 attempts to guess the number, adding challenge and excitement. 4. **Replay / Continue option:** After completing a level, the player can choose to continue to the next level or quit the game. 5. **Improved input validation:** Ensures only positive integers are accepted, making the game more robust and user-friendly. 6. **Cleaner and modular code:** Functions are modular, easy to read, and maintain. These enhancements make the game more engaging, interactive, and recruiter-friendly, demonstrating the ability to improve existing code and add meaningful features. --- Number-Guess/number-guess.py | 84 ++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/Number-Guess/number-guess.py b/Number-Guess/number-guess.py index 6922dd9..e5365cf 100644 --- a/Number-Guess/number-guess.py +++ b/Number-Guess/number-guess.py @@ -1,47 +1,57 @@ from random import randrange + def main(): - randomized_no = randomize_no() + print("Welcome to Number Guessing Game!") + level = 1 + total_score = 0 + while True: - user_guessed_input = guess() - if int(user_guessed_input) > randomized_no: - print("Too large!") - continue - elif int(user_guessed_input) < randomized_no: - print("Too small!") - continue + print(f"\n--- Level {level} ---") + max_number = get_level_max(level) + randomized_no = randrange(1, max_number + 1) + attempts_left = 5 # max attempts per level + + while attempts_left > 0: + user_guess = get_guess(level) + if user_guess > randomized_no: + print("Too high! Try again.") + elif user_guess < randomized_no: + print("Too low! Try again.") + else: + print(f"Correct! You've cleared Level {level}.") + total_score += attempts_left * 10 # more points for fewer attempts + break + attempts_left -= 1 + print(f"Attempts left: {attempts_left}") else: - print("Just right!") + print(f"Game Over! The number was {randomized_no}.") break + + level += 1 + print(f"Total Score: {total_score}") + cont = input("Do you want to continue to the next level? (y/n): ").lower() + if cont != 'y': + print(f"Thanks for playing! Final Score: {total_score}") + break + def is_positive_integer(n): - """This function takes an input and check if the user input is an integer""" + try: + num = int(n) + return num > 0 + except ValueError: + return False + +def get_guess(level): while True: - try: - num = int(n) - if int(num) < 1: - return False - except ValueError: - return False + guess_input = input(f"Level {level} Guess: ") + if is_positive_integer(guess_input): + return int(guess_input) else: - return True -def get_user_input(): - """Prompt the user for an input and check if it's a positive integer""" - while True: - user_input = input("Level 1: ") - if (is_positive_integer(user_input)): - return int(user_input) -def guess(): - "Prompt the user for an input guess, and check if it's an integer" - while True: - user_guess = input("Guess: ") - if is_positive_integer(user_guess): - return user_guess -def randomize_no(): - """Randomize number""" - user_inputted_number = get_user_input() - if int(user_inputted_number) > 1: - random_number = randrange(1, user_inputted_number) - return random_number - else: - return int(user_inputted_number) + print("Enter a valid positive integer!") + +def get_level_max(level): + """Increase range as levels go up""" + return 10 + (level - 1) * 5 + if __name__ == "__main__": main() From cd8ac196fac8b8f4e83728abe2c84dbd3734f63f Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 26 Dec 2025 19:10:39 +0000 Subject: [PATCH 050/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 5112272..3494cc8 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -257,5 +257,14 @@ "45" ], "demo-path": "Image_watermark_Adder" + }, + "Binary-Gene-Classifier-Model": { + "contributor-name": [ + "venkamita" + ], + "pull-request-number": [ + "49" + ], + "demo-path": "Binary-Gene-Classifier-Model" } } \ No newline at end of file diff --git a/index.md b/index.md index a7a64ca..020901b 100644 --- a/index.md +++ b/index.md @@ -37,6 +37,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Password-Checker | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#37](https://github.com/Grow-with-Open-Source/Python-Projects/pull/37 "visit pr \#37") | [/Grow-with-Open-Source/Python-Projects/Password-Checker/](Password-Checker "view the result of Password-Checker") | | Temperature_Converter | [omprakash0702](https://github.com/omprakash0702 "goto omprakash0702 profile") | [#40](https://github.com/Grow-with-Open-Source/Python-Projects/pull/40 "visit pr \#40") | [/Grow-with-Open-Source/Python-Projects/Temperature_Converter/](Temperature_Converter "view the result of Temperature_Converter") | | Image_watermark_Adder | [ramanuj-droid](https://github.com/ramanuj-droid "goto ramanuj-droid profile") | [#45](https://github.com/Grow-with-Open-Source/Python-Projects/pull/45 "visit pr \#45") | [/Grow-with-Open-Source/Python-Projects/Image_watermark_Adder/](Image_watermark_Adder "view the result of Image_watermark_Adder") | +| Binary-Gene-Classifier-Model | [venkamita](https://github.com/venkamita "goto venkamita profile") | [#49](https://github.com/Grow-with-Open-Source/Python-Projects/pull/49 "visit pr \#49") | [/Grow-with-Open-Source/Python-Projects/Binary-Gene-Classifier-Model/](Binary-Gene-Classifier-Model "view the result of Binary-Gene-Classifier-Model") | From 0d38532e422a85b1140e490d8dc76d92b6712d96 Mon Sep 17 00:00:00 2001 From: Dhanush D Prabhu <=itsdevdhanush> Date: Sat, 27 Dec 2025 15:15:52 +0530 Subject: [PATCH 051/121] Add number plate detection using OpenCV --- .../Numberplatedetection.py | 61 +++++++++++++++++++ Number-Plate-Detection/README.md | 37 +++++++++++ 2 files changed, 98 insertions(+) create mode 100644 Number-Plate-Detection/Numberplatedetection.py create mode 100644 Number-Plate-Detection/README.md diff --git a/Number-Plate-Detection/Numberplatedetection.py b/Number-Plate-Detection/Numberplatedetection.py new file mode 100644 index 0000000..13d6845 --- /dev/null +++ b/Number-Plate-Detection/Numberplatedetection.py @@ -0,0 +1,61 @@ +import cv2 +import os +import time + +cascade_path = cv2.data.haarcascades + "haarcascade_russian_plate_number.xml" +plate_cascade = cv2.CascadeClassifier(cascade_path) + +if plate_cascade.empty(): + raise IOError("Error loading Haar cascade") + +# Output directory +os.makedirs("plates", exist_ok=True) + +cap = cv2.VideoCapture(0) +if not cap.isOpened(): + raise IOError("Cannot open webcam") + +plate_count = 0 +last_save_time = 0 +save_delay = 0.5 + +while True: + ret, frame = cap.read() + if not ret: + break + + frame = cv2.resize(frame, (960, 540)) + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + gray = cv2.equalizeHist(gray) + gray = cv2.bilateralFilter(gray, 11, 17, 17) + + roi = gray[270:540, :] + plates = plate_cascade.detectMultiScale( + roi, + scaleFactor=1.05, + minNeighbors=7, + minSize=(60, 20) + ) + + current_time = time.time() + + for (x, y, w, h) in plates: + y += 270 + aspect_ratio = w / h + + if 2 < aspect_ratio < 5: + cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) + + if current_time - last_save_time > save_delay: + plate_count += 1 + plate_img = frame[y:y+h, x:x+w] + cv2.imwrite(f"plates/plate_{plate_count}.jpg", plate_img) + last_save_time = current_time + + cv2.imshow("Number Plate Detection", frame) + + if cv2.waitKey(1) & 0xFF == ord("q"): + break + +cap.release() +cv2.destroyAllWindows() diff --git a/Number-Plate-Detection/README.md b/Number-Plate-Detection/README.md new file mode 100644 index 0000000..0e8441a --- /dev/null +++ b/Number-Plate-Detection/README.md @@ -0,0 +1,37 @@ +# Number Plate Detection πŸš˜πŸ” + +This project is a simple **Number Plate Detection** system built using **Python** and **OpenCV**. +It uses classical image processing techniques and OpenCV’s **built-in Haar Cascade classifier** to detect vehicle number plates in real time using a webcam. + +--- + +## πŸ“Έ Demo + +![Demo Screenshot](https://github.com/user-attachments/assets/fd6d233e-a948-4015-aab3-50ec09ac9f75) + +--- + +## 🧠 Features + +- Real-time number plate detection using webcam +- Uses OpenCV’s built-in Haar Cascade classifier +- Draws bounding boxes around detected number plates +- Lightweight and beginner-friendly +- Can be extended for OCR (text extraction) + +--- + +## πŸ› οΈ Technologies Used + +- Python 3.x +- OpenCV + +--- + +## πŸ“ Project Structure + +```text +Number-Plate-Detection/ +β”‚ +β”œβ”€β”€ Numberplatedetection.py # Main script +└── README.md # Project documentation From 3ad12ae3c1e75c632bd9ac779989e49f10f21581 Mon Sep 17 00:00:00 2001 From: Dhanush D Prabhu Date: Sun, 28 Dec 2025 10:44:42 +0530 Subject: [PATCH 052/121] Update project structure section in README.md --- Number-Plate-Detection/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Number-Plate-Detection/README.md b/Number-Plate-Detection/README.md index 0e8441a..1f57cdd 100644 --- a/Number-Plate-Detection/README.md +++ b/Number-Plate-Detection/README.md @@ -28,10 +28,11 @@ It uses classical image processing techniques and OpenCV’s **built-in Haar Cas --- + ## πŸ“ Project Structure -```text Number-Plate-Detection/ β”‚ β”œβ”€β”€ Numberplatedetection.py # Main script └── README.md # Project documentation + From 9e37441ca6942f8a19ee0684ffb53f46da3083cd Mon Sep 17 00:00:00 2001 From: Dhanush D Prabhu Date: Sun, 28 Dec 2025 11:22:22 +0530 Subject: [PATCH 053/121] Update Number-Plate-Detection/README.md Co-authored-by: Shamith Nakka --- Number-Plate-Detection/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Number-Plate-Detection/README.md b/Number-Plate-Detection/README.md index 1f57cdd..8b91d1b 100644 --- a/Number-Plate-Detection/README.md +++ b/Number-Plate-Detection/README.md @@ -31,8 +31,10 @@ It uses classical image processing techniques and OpenCV’s **built-in Haar Cas ## πŸ“ Project Structure +``` Number-Plate-Detection/ β”‚ β”œβ”€β”€ Numberplatedetection.py # Main script -└── README.md # Project documentation +└── README.md # Project documentation +``` From 31acebaef63a9efe8255b13fe33eeaf307aba66a Mon Sep 17 00:00:00 2001 From: PieBerlin Date: Sun, 28 Dec 2025 10:40:17 +0300 Subject: [PATCH 054/121] formatted-code --- Password-Generator/dynamic_password.py | 123 +++++++++++++++---------- 1 file changed, 75 insertions(+), 48 deletions(-) diff --git a/Password-Generator/dynamic_password.py b/Password-Generator/dynamic_password.py index 7321659..8cb26f1 100644 --- a/Password-Generator/dynamic_password.py +++ b/Password-Generator/dynamic_password.py @@ -1,72 +1,99 @@ -"""_summary_ - implementing password generation program that allows symbols and capital letters if the user wants to include them - - Returns: - password """ +Password generation program that allows symbols and capital letters +based on user preferences. - +Returns: + str: Generated password +""" import random - -#constants -SMALL_LETTERS="abcdefghijklmnopqrstuvwxyz" -CAPITAL_LETTERS="ABCDEFGHIJKLMNOPQRSTUVWXYZ" -SYMBOLS="~!@#$%^&*()_-+={[}]|:;<>?" -MIN_LENGTH=12 -MAX_LENGTH=24 - - -#function to generate the password -def dynamic_password(passlength,capitals=False,symbols=False): - characters=SMALL_LETTERS +# Constants +SMALL_LETTERS = "abcdefghijklmnopqrstuvwxyz" +CAPITAL_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +SYMBOLS = "~!@#$%^&*()_-+={[}]|:;<>?" +MIN_LENGTH = 12 +MAX_LENGTH = 24 + + +def dynamic_password(passlength, capitals=False, symbols=False): + """ + Generate a random password based on specified criteria. + + Args: + passlength (int): Length of the password + capitals (bool): Whether to include capital letters + symbols (bool): Whether to include symbols + + Returns: + str: Generated password + """ + # Start with small letters as the base character set + characters = SMALL_LETTERS + + # Add capital letters if requested if capitals: - characters+=CAPITAL_LETTERS + characters += CAPITAL_LETTERS + + # Add symbols if requested if symbols: - characters+=SYMBOLS - password="" - for i in range(0,passlength): - randomletter=random.choice(characters) - password+=(randomletter) + characters += SYMBOLS + + # Generate password by randomly selecting characters + password = "" + for i in range(0, passlength): + random_letter = random.choice(characters) + password += random_letter return password -#Function to take user inputs and validate them -def inputs_validation(): - while True: - - pass_length=input(f"Enter password length ({MIN_LENGTH}-{MAX_LENGTH}): ") - input_capitals=input("Do we include capitals (y/n): ").strip().lower() - input_symbols=input("Do we include symbols (y/n): ").strip().lower() - # 1. Validate both inputs at once +def inputs_validation(): + """ + Get and validate user inputs for password generation. + + Returns: + tuple: (length, capitals, symbols) - validated user preferences + """ + while True: + # Get user inputs + pass_length = input(f"Enter password length ({MIN_LENGTH}-{MAX_LENGTH}): ") + input_capitals = input("Do we include capitals (y/n): ").strip().lower() + input_symbols = input("Do we include symbols (y/n): ").strip().lower() + + # 1. Validate both yes/no inputs at once if input_capitals not in ['y', 'n'] or input_symbols not in ['y', 'n']: print("Please type 'y' or 'n' for the options.") - continue # Restarts the loop to ask again - - #2. convert to booleans - capitals=(input_capitals=='y') - symbols=(input_symbols=='y') + continue # Restart the loop to ask again + + # 2. Convert inputs to booleans + capitals = (input_capitals == 'y') + symbols = (input_symbols == 'y') + # 3. Validate password length if pass_length.isdigit(): - #validate password length - length=int(pass_length) - if 12<=length<=24: - return length,capitals,symbols + length = int(pass_length) + + # Check if length is within valid range + if MIN_LENGTH <= length <= MAX_LENGTH: + return length, capitals, symbols else: print("Please enter password length within the range!!") continue - print("Password length should be a Number") - - - + + print("Password length should be a number") def main(): - length,capitals,symbols=inputs_validation() - password=dynamic_password(length,capitals,symbols) - print(f"Your Generated Password is : {password}") + """Main function to orchestrate the password generation process.""" + # Get validated user inputs + length, capitals, symbols = inputs_validation() + + # Generate password + password = dynamic_password(length, capitals, symbols) + + # Display the generated password + print(f"Your Generated Password is: {password}") if __name__ == "__main__": From a95c00b80eba3fb8b6fbf0affcffc92c1f8a92f3 Mon Sep 17 00:00:00 2001 From: Shamith Nakka Date: Sun, 28 Dec 2025 14:25:12 +0530 Subject: [PATCH 055/121] Refined automation workflow - Updated condition to run the job. - Provided version to Python. - Added logic to pull any newly added changes to avoid push failure. - Printed only diffs instead of whole files. - Grouped related logs --- .../workflows/update-contributors-details.yml | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/.github/workflows/update-contributors-details.yml b/.github/workflows/update-contributors-details.yml index 7a0d56d..4c4ab77 100644 --- a/.github/workflows/update-contributors-details.yml +++ b/.github/workflows/update-contributors-details.yml @@ -5,30 +5,39 @@ on: types: [closed] branches: [main] -env: - REPO_NAME: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - jobs: update-by-pr: - if: ${{ github.event.pull_request.title != 'init' && github.event.pull_request.merged == true }} + if: | + github.event.pull_request.merged == true && + !contains(github.event.pull_request.title, 'maintenance') && + !contains(github.event.pull_request.title, 'Maintenance') + runs-on: ubuntu-latest permissions: contents: write + env: + REPO_NAME: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + steps: - name: Checking out the repo uses: actions/checkout@v4.1.0 - name: Setup Python uses: actions/setup-python@v4.7.1 + with: + python-version: 3.12 - name: Getting PR details run: | touch pr.json gh pr view $PR_NUMBER --json author,url,files > pr.json + cat pr.json + + - name: Pull any new changes + run: git pull origin main - name: Updating log file run: | @@ -37,12 +46,12 @@ jobs: echo "Create `.github/data` directory" fi python .github/scripts/update_contributors_log.py - cat .github/data/contributors-log.json + git diff .github/data/contributors-log.json - name: Updating index.md file run: | python .github/scripts/update_index_md.py - cat index.md + git diff index.md - name: Remove unwanted files run: rm pr.json @@ -50,14 +59,23 @@ jobs: - name: Commit and Push run: | if [ "$(git status | grep 'Changes not staged\|Untracked files')" ]; then + echo "::group::Printing All Diffs" git diff + echo "::endgroup::" + + echo "::group::Configuring GitHub Action as author" git config --global user.email "actions@github.com" git config --global user.name "GitHub Actions" + git config --list | cat + echo "::endgroup::" + + echo "::group::Adding and Pushing Changes" git add . git commit -m "Updated Contributors Details" git push origin main echo "Pushed the update successfully" + echo "::endgroup::" else - echo "Nothing to push" + echo "::error::No Changes detected!" exit 1 fi From b433326b818ff7b23b4243775389d2748570a21e Mon Sep 17 00:00:00 2001 From: Shamith Nakka Date: Sun, 28 Dec 2025 09:31:27 +0000 Subject: [PATCH 056/121] Manually updated contribution-log --- .github/data/contributors-log.json | 36 +++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 3494cc8..6fa8110 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -37,10 +37,12 @@ }, "Animal-Guess": { "contributor-name": [ - "ShashiNova" + "ShashiNova", + "Achi-Vyshnavi" ], "pull-request-number": [ - "8" + "8", + "56" ], "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Animal-Guess" }, @@ -121,11 +123,13 @@ "Number-Guess": { "contributor-name": [ "adedayoprcs", - "sheylaghost" + "sheylaghost", + "Achi-Vyshnavi" ], "pull-request-number": [ "19", - "43" + "43", + "57" ], "demo-path": "Number-Guess" }, @@ -167,10 +171,12 @@ }, "Coin-Poison": { "contributor-name": [ - "niharikah005" + "niharikah005", + "Achi-Vyshnavi" ], "pull-request-number": [ - "24" + "24", + "55" ], "demo-path": "Coin-Poison" }, @@ -266,5 +272,23 @@ "49" ], "demo-path": "Binary-Gene-Classifier-Model" + }, + "Auto-Clicker": { + "contributor-name": [ + "BasselDar" + ], + "pull-request-number": [ + "54" + ], + "demo-path": "Auto-Clicker" + }, + "Number-Plate-Detection": { + "contributor-name": [ + "iamdevdhanush" + ], + "pull-request-number": [ + "58" + ], + "demo-path": "Number-Plate-Detection" } } \ No newline at end of file From a17e91445cfb093c00597d8b9813dd88147f9dff Mon Sep 17 00:00:00 2001 From: Shamith Nakka Date: Sun, 28 Dec 2025 09:34:55 +0000 Subject: [PATCH 057/121] Removed neccessary backslash --- .github/scripts/convert_to_html_tables.py | 2 +- .github/scripts/update_index_md.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/convert_to_html_tables.py b/.github/scripts/convert_to_html_tables.py index 1b8b014..e23c50b 100644 --- a/.github/scripts/convert_to_html_tables.py +++ b/.github/scripts/convert_to_html_tables.py @@ -117,7 +117,7 @@ def main(): # Processing pull-requests pull_requests = details['pull-request-number'] pull_requests_list = [ - f'{pr}' for pr in pull_requests] + f'{pr}' for pr in pull_requests] pull_requests_output = ', '.join(pull_requests_list) # Processing demo-path diff --git a/.github/scripts/update_index_md.py b/.github/scripts/update_index_md.py index 7095743..9e5d5d5 100644 --- a/.github/scripts/update_index_md.py +++ b/.github/scripts/update_index_md.py @@ -115,7 +115,7 @@ def main(): # Processing pull-requests pull_requests = details['pull-request-number'] pull_requests_list = [ - f'[#{pr}](https://github.com/{REPO_NAME}/pull/{pr} "visit pr \#{pr}")' for pr in pull_requests] + f'[#{pr}](https://github.com/{REPO_NAME}/pull/{pr} "visit pr #{pr}")' for pr in pull_requests] pull_requests_output = ', '.join(pull_requests_list) # Processing demo-path From 7c8954295cb31d03872e69a809204eb5046a93c2 Mon Sep 17 00:00:00 2001 From: Shamith Nakka Date: Sun, 28 Dec 2025 09:35:30 +0000 Subject: [PATCH 058/121] Manually updated index.md --- index.md | 60 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/index.md b/index.md index 020901b..9930c67 100644 --- a/index.md +++ b/index.md @@ -9,35 +9,37 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Project Title | Contributor Names | Pull Requests | Demo | | --- | --- | --- | --- | -| {init} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#1](https://github.com/Grow-with-Open-Source/Python-Projects/pull/1 "visit pr \#1") | [/Grow-with-Open-Source/Python-Projects/](https://github.com/Grow-with-Open-Source/Python-Projects "view the result of {init}") | -| Language_Detector | [rahulch-1](https://github.com/rahulch-1 "goto rahulch-1 profile") | [#4](https://github.com/Grow-with-Open-Source/Python-Projects/pull/4 "visit pr \#4") | [/Grow-with-Open-Source/Python-Projects/Language_Detector/](Language_Detector "view the result of Language_Detector") | -| TIC_TAC_TOE | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#7](https://github.com/Grow-with-Open-Source/Python-Projects/pull/7 "visit pr \#7") | [/Grow-with-Open-Source/Python-Projects/TIC_TAC_TOE/](TIC_TAC_TOE "view the result of TIC_TAC_TOE") | -| Weather_Forecasting | [rahulch-1](https://github.com/rahulch-1 "goto rahulch-1 profile") | [#6](https://github.com/Grow-with-Open-Source/Python-Projects/pull/6 "visit pr \#6") | [/Grow-with-Open-Source/Python-Projects/Weather_Forecasting/](Weather_Forecasting "view the result of Weather_Forecasting") | -| Animal-Guess | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile") | [#8](https://github.com/Grow-with-Open-Source/Python-Projects/pull/8 "visit pr \#8") | [/Grow-with-Open-Source/Python-Projects/Animal-Guess/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Animal-Guess "view the result of Animal-Guess") | -| Wine-Quality-Analysis | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#9](https://github.com/Grow-with-Open-Source/Python-Projects/pull/9 "visit pr \#9") | [/Grow-with-Open-Source/Python-Projects/Wine-Quality-Analysis/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Wine-Quality-Analysis "view the result of Wine-Quality-Analysis") | -| Spell-Checker | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#10](https://github.com/Grow-with-Open-Source/Python-Projects/pull/10 "visit pr \#10") | [/Grow-with-Open-Source/Python-Projects/Spell-Checker/](Spell-Checker "view the result of Spell-Checker") | -| QRCODE-Generator | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#11](https://github.com/Grow-with-Open-Source/Python-Projects/pull/11 "visit pr \#11") | [/Grow-with-Open-Source/Python-Projects/QRCODE-Generator/](QRCODE-Generator "view the result of QRCODE-Generator") | -| Music-Player | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile"), [Robekaben254](https://github.com/Robekaben254 "goto Robekaben254 profile") | [#12](https://github.com/Grow-with-Open-Source/Python-Projects/pull/12 "visit pr \#12"), [#47](https://github.com/Grow-with-Open-Source/Python-Projects/pull/47 "visit pr \#47") | [/Grow-with-Open-Source/Python-Projects/Music-Player/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Music-Player/ "view the result of Music-Player") | -| Password-Generator | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile") | [#13](https://github.com/Grow-with-Open-Source/Python-Projects/pull/13 "visit pr \#13") | [/Grow-with-Open-Source/Python-Projects/Password-Generator/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Password-Generator/ "view the result of Password-Generator") | -| Hungry_Serpent | [AnilKumarTeegala](https://github.com/AnilKumarTeegala "goto AnilKumarTeegala profile") | [#16](https://github.com/Grow-with-Open-Source/Python-Projects/pull/16 "visit pr \#16") | [/Grow-with-Open-Source/Python-Projects/Hungry_Serpent/](Hungry_Serpent "view the result of Hungry_Serpent") | -| Web-Blocker | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile") | [#14](https://github.com/Grow-with-Open-Source/Python-Projects/pull/14 "visit pr \#14") | [/Grow-with-Open-Source/Python-Projects/Web-Blocker/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Web-Blocker "view the result of Web-Blocker") | -| File_Organizer | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile") | [#15](https://github.com/Grow-with-Open-Source/Python-Projects/pull/15 "visit pr \#15") | [/Grow-with-Open-Source/Python-Projects/File_Organizer/](File_Organizer "view the result of File_Organizer") | -| Number-Guess | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile"), [sheylaghost](https://github.com/sheylaghost "goto sheylaghost profile") | [#19](https://github.com/Grow-with-Open-Source/Python-Projects/pull/19 "visit pr \#19"), [#43](https://github.com/Grow-with-Open-Source/Python-Projects/pull/43 "visit pr \#43") | [/Grow-with-Open-Source/Python-Projects/Number-Guess/](Number-Guess "view the result of Number-Guess") | -| Remove-Empty-Lines | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile") | [#20](https://github.com/Grow-with-Open-Source/Python-Projects/pull/20 "visit pr \#20") | [/Grow-with-Open-Source/Python-Projects/Remove-Empty-Lines/](Remove-Empty-Lines "view the result of Remove-Empty-Lines") | -| Pomodoro-Timer | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile") | [#21](https://github.com/Grow-with-Open-Source/Python-Projects/pull/21 "visit pr \#21") | [/Grow-with-Open-Source/Python-Projects/Pomodoro-Timer/](Pomodoro-Timer "view the result of Pomodoro-Timer") | -| {others} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#22](https://github.com/Grow-with-Open-Source/Python-Projects/pull/22 "visit pr \#22") | [/Grow-with-Open-Source/Python-Projects/.github](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github "view the result of {others}") | -| 3d_pose_estimation | [mahipalimkar](https://github.com/mahipalimkar "goto mahipalimkar profile") | [#23](https://github.com/Grow-with-Open-Source/Python-Projects/pull/23 "visit pr \#23") | [/Grow-with-Open-Source/Python-Projects/3d_pose_estimation/](3d_pose_estimation "view the result of 3d_pose_estimation") | -| Coin-Poison | [niharikah005](https://github.com/niharikah005 "goto niharikah005 profile") | [#24](https://github.com/Grow-with-Open-Source/Python-Projects/pull/24 "visit pr \#24") | [/Grow-with-Open-Source/Python-Projects/Coin-Poison/](Coin-Poison "view the result of Coin-Poison") | -| Digital_Clock | [DarkSlayer102](https://github.com/DarkSlayer102 "goto DarkSlayer102 profile"), [swati-londhe](https://github.com/swati-londhe "goto swati-londhe profile") | [#25](https://github.com/Grow-with-Open-Source/Python-Projects/pull/25 "visit pr \#25"), [#41](https://github.com/Grow-with-Open-Source/Python-Projects/pull/41 "visit pr \#41") | [/Grow-with-Open-Source/Python-Projects/Digital_Clock/](Digital_Clock "view the result of Digital_Clock") | -| Secure_Password_Manager | [Chaitanya6Nli](https://github.com/Chaitanya6Nli "goto Chaitanya6Nli profile") | [#29](https://github.com/Grow-with-Open-Source/Python-Projects/pull/29 "visit pr \#29") | [/Grow-with-Open-Source/Python-Projects/Secure_Password_Manager/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Secure_Password_Manager "view the result of Secure_Password_Manager") | -| Youtube_video_finder_using_geminillm | [veerababu1729](https://github.com/veerababu1729 "goto veerababu1729 profile") | [#32](https://github.com/Grow-with-Open-Source/Python-Projects/pull/32 "visit pr \#32") | [/Grow-with-Open-Source/Python-Projects/Youtube_video_finder_using_geminillm/](Youtube_video_finder_using_geminillm "view the result of Youtube_video_finder_using_geminillm") | -| {workflows} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#36](https://github.com/Grow-with-Open-Source/Python-Projects/pull/36 "visit pr \#36"), [#39](https://github.com/Grow-with-Open-Source/Python-Projects/pull/39 "visit pr \#39") | [/Grow-with-Open-Source/Python-Projects/.github/workflows](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github/workflows "view the result of {workflows}") | -| Temp-Cleaner | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#35](https://github.com/Grow-with-Open-Source/Python-Projects/pull/35 "visit pr \#35") | [/Grow-with-Open-Source/Python-Projects/Temp-Cleaner/](Temp-Cleaner "view the result of Temp-Cleaner") | -| Gesture_Volume_Control | [10mudassir007](https://github.com/10mudassir007 "goto 10mudassir007 profile") | [#34](https://github.com/Grow-with-Open-Source/Python-Projects/pull/34 "visit pr \#34") | [/Grow-with-Open-Source/Python-Projects/Gesture_Volume_Control/](Gesture_Volume_Control "view the result of Gesture_Volume_Control") | -| Password-Checker | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#37](https://github.com/Grow-with-Open-Source/Python-Projects/pull/37 "visit pr \#37") | [/Grow-with-Open-Source/Python-Projects/Password-Checker/](Password-Checker "view the result of Password-Checker") | -| Temperature_Converter | [omprakash0702](https://github.com/omprakash0702 "goto omprakash0702 profile") | [#40](https://github.com/Grow-with-Open-Source/Python-Projects/pull/40 "visit pr \#40") | [/Grow-with-Open-Source/Python-Projects/Temperature_Converter/](Temperature_Converter "view the result of Temperature_Converter") | -| Image_watermark_Adder | [ramanuj-droid](https://github.com/ramanuj-droid "goto ramanuj-droid profile") | [#45](https://github.com/Grow-with-Open-Source/Python-Projects/pull/45 "visit pr \#45") | [/Grow-with-Open-Source/Python-Projects/Image_watermark_Adder/](Image_watermark_Adder "view the result of Image_watermark_Adder") | -| Binary-Gene-Classifier-Model | [venkamita](https://github.com/venkamita "goto venkamita profile") | [#49](https://github.com/Grow-with-Open-Source/Python-Projects/pull/49 "visit pr \#49") | [/Grow-with-Open-Source/Python-Projects/Binary-Gene-Classifier-Model/](Binary-Gene-Classifier-Model "view the result of Binary-Gene-Classifier-Model") | +| {init} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#1](https://github.com/Grow-with-Open-Source/Python-Projects/pull/1 "visit pr #1") | [/Grow-with-Open-Source/Python-Projects/](https://github.com/Grow-with-Open-Source/Python-Projects "view the result of {init}") | +| Language_Detector | [rahulch-1](https://github.com/rahulch-1 "goto rahulch-1 profile") | [#4](https://github.com/Grow-with-Open-Source/Python-Projects/pull/4 "visit pr #4") | [/Grow-with-Open-Source/Python-Projects/Language_Detector/](Language_Detector "view the result of Language_Detector") | +| TIC_TAC_TOE | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#7](https://github.com/Grow-with-Open-Source/Python-Projects/pull/7 "visit pr #7") | [/Grow-with-Open-Source/Python-Projects/TIC_TAC_TOE/](TIC_TAC_TOE "view the result of TIC_TAC_TOE") | +| Weather_Forecasting | [rahulch-1](https://github.com/rahulch-1 "goto rahulch-1 profile") | [#6](https://github.com/Grow-with-Open-Source/Python-Projects/pull/6 "visit pr #6") | [/Grow-with-Open-Source/Python-Projects/Weather_Forecasting/](Weather_Forecasting "view the result of Weather_Forecasting") | +| Animal-Guess | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile"), [Achi-Vyshnavi](https://github.com/Achi-Vyshnavi "goto Achi-Vyshnavi profile") | [#8](https://github.com/Grow-with-Open-Source/Python-Projects/pull/8 "visit pr #8"), [#56](https://github.com/Grow-with-Open-Source/Python-Projects/pull/56 "visit pr #56") | [/Grow-with-Open-Source/Python-Projects/Animal-Guess/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Animal-Guess "view the result of Animal-Guess") | +| Wine-Quality-Analysis | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#9](https://github.com/Grow-with-Open-Source/Python-Projects/pull/9 "visit pr #9") | [/Grow-with-Open-Source/Python-Projects/Wine-Quality-Analysis/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Wine-Quality-Analysis "view the result of Wine-Quality-Analysis") | +| Spell-Checker | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#10](https://github.com/Grow-with-Open-Source/Python-Projects/pull/10 "visit pr #10") | [/Grow-with-Open-Source/Python-Projects/Spell-Checker/](Spell-Checker "view the result of Spell-Checker") | +| QRCODE-Generator | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#11](https://github.com/Grow-with-Open-Source/Python-Projects/pull/11 "visit pr #11") | [/Grow-with-Open-Source/Python-Projects/QRCODE-Generator/](QRCODE-Generator "view the result of QRCODE-Generator") | +| Music-Player | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile"), [Robekaben254](https://github.com/Robekaben254 "goto Robekaben254 profile") | [#12](https://github.com/Grow-with-Open-Source/Python-Projects/pull/12 "visit pr #12"), [#47](https://github.com/Grow-with-Open-Source/Python-Projects/pull/47 "visit pr #47") | [/Grow-with-Open-Source/Python-Projects/Music-Player/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Music-Player/ "view the result of Music-Player") | +| Password-Generator | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile") | [#13](https://github.com/Grow-with-Open-Source/Python-Projects/pull/13 "visit pr #13") | [/Grow-with-Open-Source/Python-Projects/Password-Generator/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Password-Generator/ "view the result of Password-Generator") | +| Hungry_Serpent | [AnilKumarTeegala](https://github.com/AnilKumarTeegala "goto AnilKumarTeegala profile") | [#16](https://github.com/Grow-with-Open-Source/Python-Projects/pull/16 "visit pr #16") | [/Grow-with-Open-Source/Python-Projects/Hungry_Serpent/](Hungry_Serpent "view the result of Hungry_Serpent") | +| Web-Blocker | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile") | [#14](https://github.com/Grow-with-Open-Source/Python-Projects/pull/14 "visit pr #14") | [/Grow-with-Open-Source/Python-Projects/Web-Blocker/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Web-Blocker "view the result of Web-Blocker") | +| File_Organizer | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile") | [#15](https://github.com/Grow-with-Open-Source/Python-Projects/pull/15 "visit pr #15") | [/Grow-with-Open-Source/Python-Projects/File_Organizer/](File_Organizer "view the result of File_Organizer") | +| Number-Guess | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile"), [sheylaghost](https://github.com/sheylaghost "goto sheylaghost profile"), [Achi-Vyshnavi](https://github.com/Achi-Vyshnavi "goto Achi-Vyshnavi profile") | [#19](https://github.com/Grow-with-Open-Source/Python-Projects/pull/19 "visit pr #19"), [#43](https://github.com/Grow-with-Open-Source/Python-Projects/pull/43 "visit pr #43"), [#57](https://github.com/Grow-with-Open-Source/Python-Projects/pull/57 "visit pr #57") | [/Grow-with-Open-Source/Python-Projects/Number-Guess/](Number-Guess "view the result of Number-Guess") | +| Remove-Empty-Lines | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile") | [#20](https://github.com/Grow-with-Open-Source/Python-Projects/pull/20 "visit pr #20") | [/Grow-with-Open-Source/Python-Projects/Remove-Empty-Lines/](Remove-Empty-Lines "view the result of Remove-Empty-Lines") | +| Pomodoro-Timer | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile") | [#21](https://github.com/Grow-with-Open-Source/Python-Projects/pull/21 "visit pr #21") | [/Grow-with-Open-Source/Python-Projects/Pomodoro-Timer/](Pomodoro-Timer "view the result of Pomodoro-Timer") | +| {others} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#22](https://github.com/Grow-with-Open-Source/Python-Projects/pull/22 "visit pr #22") | [/Grow-with-Open-Source/Python-Projects/.github](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github "view the result of {others}") | +| 3d_pose_estimation | [mahipalimkar](https://github.com/mahipalimkar "goto mahipalimkar profile") | [#23](https://github.com/Grow-with-Open-Source/Python-Projects/pull/23 "visit pr #23") | [/Grow-with-Open-Source/Python-Projects/3d_pose_estimation/](3d_pose_estimation "view the result of 3d_pose_estimation") | +| Coin-Poison | [niharikah005](https://github.com/niharikah005 "goto niharikah005 profile"), [Achi-Vyshnavi](https://github.com/Achi-Vyshnavi "goto Achi-Vyshnavi profile") | [#24](https://github.com/Grow-with-Open-Source/Python-Projects/pull/24 "visit pr #24"), [#55](https://github.com/Grow-with-Open-Source/Python-Projects/pull/55 "visit pr #55") | [/Grow-with-Open-Source/Python-Projects/Coin-Poison/](Coin-Poison "view the result of Coin-Poison") | +| Digital_Clock | [DarkSlayer102](https://github.com/DarkSlayer102 "goto DarkSlayer102 profile"), [swati-londhe](https://github.com/swati-londhe "goto swati-londhe profile") | [#25](https://github.com/Grow-with-Open-Source/Python-Projects/pull/25 "visit pr #25"), [#41](https://github.com/Grow-with-Open-Source/Python-Projects/pull/41 "visit pr #41") | [/Grow-with-Open-Source/Python-Projects/Digital_Clock/](Digital_Clock "view the result of Digital_Clock") | +| Secure_Password_Manager | [Chaitanya6Nli](https://github.com/Chaitanya6Nli "goto Chaitanya6Nli profile") | [#29](https://github.com/Grow-with-Open-Source/Python-Projects/pull/29 "visit pr #29") | [/Grow-with-Open-Source/Python-Projects/Secure_Password_Manager/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Secure_Password_Manager "view the result of Secure_Password_Manager") | +| Youtube_video_finder_using_geminillm | [veerababu1729](https://github.com/veerababu1729 "goto veerababu1729 profile") | [#32](https://github.com/Grow-with-Open-Source/Python-Projects/pull/32 "visit pr #32") | [/Grow-with-Open-Source/Python-Projects/Youtube_video_finder_using_geminillm/](Youtube_video_finder_using_geminillm "view the result of Youtube_video_finder_using_geminillm") | +| {workflows} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#36](https://github.com/Grow-with-Open-Source/Python-Projects/pull/36 "visit pr #36"), [#39](https://github.com/Grow-with-Open-Source/Python-Projects/pull/39 "visit pr #39") | [/Grow-with-Open-Source/Python-Projects/.github/workflows](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github/workflows "view the result of {workflows}") | +| Temp-Cleaner | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#35](https://github.com/Grow-with-Open-Source/Python-Projects/pull/35 "visit pr #35") | [/Grow-with-Open-Source/Python-Projects/Temp-Cleaner/](Temp-Cleaner "view the result of Temp-Cleaner") | +| Gesture_Volume_Control | [10mudassir007](https://github.com/10mudassir007 "goto 10mudassir007 profile") | [#34](https://github.com/Grow-with-Open-Source/Python-Projects/pull/34 "visit pr #34") | [/Grow-with-Open-Source/Python-Projects/Gesture_Volume_Control/](Gesture_Volume_Control "view the result of Gesture_Volume_Control") | +| Password-Checker | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#37](https://github.com/Grow-with-Open-Source/Python-Projects/pull/37 "visit pr #37") | [/Grow-with-Open-Source/Python-Projects/Password-Checker/](Password-Checker "view the result of Password-Checker") | +| Temperature_Converter | [omprakash0702](https://github.com/omprakash0702 "goto omprakash0702 profile") | [#40](https://github.com/Grow-with-Open-Source/Python-Projects/pull/40 "visit pr #40") | [/Grow-with-Open-Source/Python-Projects/Temperature_Converter/](Temperature_Converter "view the result of Temperature_Converter") | +| Image_watermark_Adder | [ramanuj-droid](https://github.com/ramanuj-droid "goto ramanuj-droid profile") | [#45](https://github.com/Grow-with-Open-Source/Python-Projects/pull/45 "visit pr #45") | [/Grow-with-Open-Source/Python-Projects/Image_watermark_Adder/](Image_watermark_Adder "view the result of Image_watermark_Adder") | +| Binary-Gene-Classifier-Model | [venkamita](https://github.com/venkamita "goto venkamita profile") | [#49](https://github.com/Grow-with-Open-Source/Python-Projects/pull/49 "visit pr #49") | [/Grow-with-Open-Source/Python-Projects/Binary-Gene-Classifier-Model/](Binary-Gene-Classifier-Model "view the result of Binary-Gene-Classifier-Model") | +| Auto-Clicker | [BasselDar](https://github.com/BasselDar "goto BasselDar profile") | [#54](https://github.com/Grow-with-Open-Source/Python-Projects/pull/54 "visit pr #54") | [/Grow-with-Open-Source/Python-Projects/Auto-Clicker/](Auto-Clicker "view the result of Auto-Clicker") | +| Number-Plate-Detection | [iamdevdhanush](https://github.com/iamdevdhanush "goto iamdevdhanush profile") | [#58](https://github.com/Grow-with-Open-Source/Python-Projects/pull/58 "visit pr #58") | [/Grow-with-Open-Source/Python-Projects/Number-Plate-Detection/](Number-Plate-Detection "view the result of Number-Plate-Detection") | From c6fc9404ca2c51c6577843f91aeea69b9a7aab9e Mon Sep 17 00:00:00 2001 From: Shamith Nakka Date: Sun, 28 Dec 2025 15:24:09 +0530 Subject: [PATCH 059/121] Formatted tic-tac-toe project --- TIC_TAC_TOE/TIC_TAC_TOE.py | 40 ++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/TIC_TAC_TOE/TIC_TAC_TOE.py b/TIC_TAC_TOE/TIC_TAC_TOE.py index 4b62c1b..c71c8c4 100644 --- a/TIC_TAC_TOE/TIC_TAC_TOE.py +++ b/TIC_TAC_TOE/TIC_TAC_TOE.py @@ -1,21 +1,30 @@ from tkinter import * import tkinter.messagebox + tk = Tk() tk.title("Tic Tac Toe") tk.configure(bg='yellow') + p1 = StringVar() p2 = StringVar() + player1_name = Entry(textvariable=p1, bd=5, bg='white', width=40) player1_name.grid(row=1, column=1, columnspan=8) + player2_name = Entry(tk, textvariable=p2, bd=5, bg='white', width=40) player2_name.grid(row=2, column=1, columnspan=8) + bclick = True flag = 0 current_player_name = p1.get() if p1.get() else 'X' + + def disableButton(): for i in range(3): for j in range(3): buttons[i][j].configure(state=DISABLED) + + def checkForWin(): for i in range(3): if buttons[i][0]["text"] == buttons[i][1]["text"] == buttons[i][2]["text"] != " ": @@ -23,7 +32,8 @@ def checkForWin(): buttons[i][1].config(bg="green") buttons[i][2].config(bg="green") disableButton() - winner_name = p1.get() if buttons[i][0]['text'] == 'X' else p2.get() + winner_name = p1.get( + ) if buttons[i][0]['text'] == 'X' else p2.get() if not winner_name: winner_name = 'Player 1' if buttons[i][0]['text'] == 'X' else 'Player 2' tkinter.messagebox.showinfo("Tic-Tac-Toe", f"{winner_name} wins!") @@ -33,7 +43,8 @@ def checkForWin(): buttons[1][i].config(bg="green") buttons[2][i].config(bg="green") disableButton() - winner_name = p1.get() if buttons[0][i]['text'] == 'X' else p2.get() + winner_name = p1.get( + ) if buttons[0][i]['text'] == 'X' else p2.get() if not winner_name: winner_name = 'Player 1' if buttons[0][i]['text'] == 'X' else 'Player 2' tkinter.messagebox.showinfo("Tic-Tac-Toe", f"{winner_name} wins!") @@ -60,6 +71,8 @@ def checkForWin(): return if flag == 8: tkinter.messagebox.showinfo("Tic-Tac-Toe", "It's a Tie") + + def resetGame(): global bclick, flag, current_player_name for i in range(3): @@ -69,9 +82,16 @@ def resetGame(): bclick = True flag = 0 current_player_name = p1.get() if p1.get() else 'X' -Label(tk, text="Player 1:", font='Times 20 bold', bg='yellow', fg='black', height=1, width=8).grid(row=1, column=0) -Label(tk, text="Player 2:", font='Times 20 bold', bg='yellow', fg='black', height=1, width=8).grid(row=2, column=0) -buttons = [[Button(tk, text=' ', font='Times 20 bold', bg='black', fg='white', height=4, width=8) for _ in range(3)] for _ in range(3)] + + +Label(tk, text="Player 1:", font='Times 20 bold', bg='yellow', + fg='black', height=1, width=8).grid(row=1, column=0) +Label(tk, text="Player 2:", font='Times 20 bold', bg='yellow', + fg='black', height=1, width=8).grid(row=2, column=0) +buttons = [[Button(tk, text=' ', font='Times 20 bold', bg='black', + fg='white', height=4, width=8) for _ in range(3)] for _ in range(3)] + + def btnClick(button): global bclick, flag if button["text"] == " ": @@ -84,10 +104,14 @@ def btnClick(button): checkForWin() else: tkinter.messagebox.showinfo("Tic-Tac-Toe", "Button already Clicked!") + + for i in range(3): for j in range(3): - buttons[i][j].configure(command=lambda row=i, col=j: btnClick(buttons[row][col])) + buttons[i][j].configure(command=lambda row=i, + col=j: btnClick(buttons[row][col])) buttons[i][j].grid(row=i + 3, column=j) -reset_button = Button(tk, text="Reset Game", font='Times 16 bold', bg='white', fg='black', command=resetGame) +reset_button = Button(tk, text="Reset Game", font='Times 16 bold', + bg='white', fg='black', command=resetGame) reset_button.grid(row=6, column=0, columnspan=3) -tk.mainloop() \ No newline at end of file +tk.mainloop() From 4417e0f24020715be22c6dbe492f8863eaf3d07d Mon Sep 17 00:00:00 2001 From: Shamith Nakka Date: Sun, 28 Dec 2025 15:30:12 +0530 Subject: [PATCH 060/121] Add GH_TOKEN environment variable to workflow Added environment variable for GitHub token in workflow. --- .github/workflows/update-contributors-details.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/update-contributors-details.yml b/.github/workflows/update-contributors-details.yml index 4c4ab77..ce46bad 100644 --- a/.github/workflows/update-contributors-details.yml +++ b/.github/workflows/update-contributors-details.yml @@ -35,6 +35,8 @@ jobs: touch pr.json gh pr view $PR_NUMBER --json author,url,files > pr.json cat pr.json + env: + GH_TOKEN: ${{ github.token }} - name: Pull any new changes run: git pull origin main From 795199cad64b287bab28434e08b39b877206b203 Mon Sep 17 00:00:00 2001 From: iamdevdhanush Date: Mon, 5 Jan 2026 09:02:41 +0000 Subject: [PATCH 061/121] Added Motion Detection Project --- Motion-Detection | 1 + 1 file changed, 1 insertion(+) create mode 160000 Motion-Detection diff --git a/Motion-Detection b/Motion-Detection new file mode 160000 index 0000000..e4011e7 --- /dev/null +++ b/Motion-Detection @@ -0,0 +1 @@ +Subproject commit e4011e7254c239ba2286c6c300c633ff52045f9e From d3ada1e750c2d31d9b964ba0c7cd7adaf376f0e6 Mon Sep 17 00:00:00 2001 From: iamdevdhanush Date: Mon, 5 Jan 2026 09:39:29 +0000 Subject: [PATCH 062/121] Convert Motion-Detection from submodule to normal folder --- Motion-Detection | 1 - Motion-Detection/OpenCvPROJECT.py | 52 ++++++++++++++++++++++++++++ Motion-Detection/README.md | 57 +++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) delete mode 160000 Motion-Detection create mode 100644 Motion-Detection/OpenCvPROJECT.py create mode 100644 Motion-Detection/README.md diff --git a/Motion-Detection b/Motion-Detection deleted file mode 160000 index e4011e7..0000000 --- a/Motion-Detection +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e4011e7254c239ba2286c6c300c633ff52045f9e diff --git a/Motion-Detection/OpenCvPROJECT.py b/Motion-Detection/OpenCvPROJECT.py new file mode 100644 index 0000000..404de24 --- /dev/null +++ b/Motion-Detection/OpenCvPROJECT.py @@ -0,0 +1,52 @@ +import cv2 +import time +import imutils + +cam = cv2.VideoCapture(0) +time.sleep(0) +firstFrame = None +area = 500 +frameUpdateInterval = 100 +frameCount = 0 + +while True: + ret, img = cam.read() + if not ret: + break + + text = "Normal" + img = imutils.resize(img, width=600) + grayImg = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + gaussianImg = cv2.GaussianBlur(grayImg, (21, 21), 0) + + if firstFrame is None or frameCount % frameUpdateInterval == 0: + firstFrame = gaussianImg + frameCount = 0 + + frameCount += 1 + + imgDiff = cv2.absdiff(firstFrame, gaussianImg) + + threshImg = cv2.threshold(imgDiff, 25, 255, cv2.THRESH_BINARY)[1] + threshImg = cv2.dilate(threshImg, None, iterations=2) + + cnts = cv2.findContours(threshImg.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + cnts = imutils.grab_contours(cnts) + + for c in cnts: + if cv2.contourArea(c) < area: + continue + + (x, y, w, h) = cv2.boundingRect(c) + cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) + text = "Moving Object detected" + + cv2.putText(img, text, (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2) + cv2.imshow("cameraFeed", img) + + key = cv2.waitKey(1) & 0xFF + if key == ord("q"): + break + +cam.release() +cv2.destroyAllWindows() diff --git a/Motion-Detection/README.md b/Motion-Detection/README.md new file mode 100644 index 0000000..485594a --- /dev/null +++ b/Motion-Detection/README.md @@ -0,0 +1,57 @@ +# πŸŽ₯ Motion Detection Using OpenCV πŸ•΅οΈβ€β™‚οΈ + +This project demonstrates a **basic motion detection system** using OpenCV and Python. It captures video from a camera, processes the frames to detect moving objects, and highlights detected motion areas in real-time. πŸš€ + +--- + +## ✨ Features + +- πŸ“Ή Capture video feed from the webcam. +- 🎨 Convert frames to grayscale and apply Gaussian blur for noise reduction. +- πŸ” Detect motion by comparing the current frame with the first frame. +- πŸ”² Highlight moving objects with bounding rectangles. +- πŸ“ Display status text indicating whether motion is detected. +- ❌ Press **`q`** to quit the application. + +--- + +## πŸ› οΈ Requirements + +- Python 3.x 🐍 +- OpenCV (`cv2`) πŸ–ΌοΈ +- Imutils πŸ”§ +- Time module (standard Python library) ⏰ + +--- + +## πŸ“¦ Installation + +Install required libraries using pip: + +```bash +pip install opencv-python imutils +``` + +▢️ Usage + +Run the script to start the motion detection: + +```bash +python motion_detection.py +``` + +βš™οΈ How It Works + +πŸ₯‡ The first frame is captured and used as a reference. + +πŸ–€ Each new frame is converted to grayscale and blurred. + +βž– The absolute difference between the current frame and the reference frame is calculated. + +⚫ Thresholding and dilation are applied to highlight differences. + +πŸ”Ž Contours are detected in the thresholded image. + +βœ… If contours exceed a minimum area, bounding boxes are drawn, and motion is detected. + +πŸ“Ί The video feed with detected motion highlighted is displayed. From 730de6be5faceca54929b7329453a3835b95fa01 Mon Sep 17 00:00:00 2001 From: Efiom Ndaeyo Date: Sat, 10 Jan 2026 21:25:22 +0800 Subject: [PATCH 063/121] Rename project folder to youtube-relevance-finder --- .../README.md | 0 .../app.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {Youtube_video_finder_using_geminillm => youtube-relevance-finder}/README.md (100%) rename {Youtube_video_finder_using_geminillm => youtube-relevance-finder}/app.py (100%) diff --git a/Youtube_video_finder_using_geminillm/README.md b/youtube-relevance-finder/README.md similarity index 100% rename from Youtube_video_finder_using_geminillm/README.md rename to youtube-relevance-finder/README.md diff --git a/Youtube_video_finder_using_geminillm/app.py b/youtube-relevance-finder/app.py similarity index 100% rename from Youtube_video_finder_using_geminillm/app.py rename to youtube-relevance-finder/app.py From 05e4d702f10bab1a659da698085d634f7a878e48 Mon Sep 17 00:00:00 2001 From: Efiom Ndaeyo Date: Sat, 10 Jan 2026 21:36:56 +0800 Subject: [PATCH 064/121] Add .gitignore for environment, OS, and IDE files --- youtube-relevance-finder/.gitignore | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 youtube-relevance-finder/.gitignore diff --git a/youtube-relevance-finder/.gitignore b/youtube-relevance-finder/.gitignore new file mode 100644 index 0000000..257b1d6 --- /dev/null +++ b/youtube-relevance-finder/.gitignore @@ -0,0 +1,21 @@ +# macOS +.DS_Store + +# Environment variables +.env +.env.* + +# Python +__pycache__/ +*.py[cod] + +# Virtual environments +venv/ +.venv/ + +# IDEs +.vscode/ +.idea/ + +# Logs +*.log From 889950ff1cbb2e2aea5cecc9a94d9e64d43e5791 Mon Sep 17 00:00:00 2001 From: Efiom Ndaeyo Date: Sun, 11 Jan 2026 17:31:34 +0800 Subject: [PATCH 065/121] Handle Gemini API failure with fallback scoring --- youtube-relevance-finder/app.py | 129 ++++++++++++++++++++------------ 1 file changed, 82 insertions(+), 47 deletions(-) diff --git a/youtube-relevance-finder/app.py b/youtube-relevance-finder/app.py index f08df6f..e851683 100644 --- a/youtube-relevance-finder/app.py +++ b/youtube-relevance-finder/app.py @@ -5,9 +5,14 @@ from typing import Dict from datetime import datetime, timedelta, timezone from googleapiclient.discovery import build -import google.generativeai as genai +from google import genai +from dotenv import load_dotenv +import isodate +# Load keys from .env file +load_dotenv() + # β€”β€”β€” ENV variables β€”β€”β€” YT_API_KEY = os.environ.get('YT_API_KEY') GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY') @@ -15,49 +20,51 @@ # β€”β€”β€” CONSTANTS β€”β€”β€” SERVICE_TYPE = 'youtube' SERVICE_VERSION = 'v3' -MODEL_NAME = 'gemini-1.5-flash-latest' +MODEL_NAME = 'gemini-1.5-flash' DEFAULT_MAX_API_CALLS = 5 DEFAULT_MAX_RESULTS_PER_PAGE = 50 -DEFAULT_MAX_RESULTS = 20 +DEFAULT_MAX_RESULTS = 5 DEFAULT_MIN_VIDEO_DURATION_MINUTES = 10 DEFAULT_MAX_VIDEO_DURATION_MINUTES = 60 DEFAULT_NO_OF_PREV_DAYS = 14 -DEFAULT_MAX_RESULTS = 5 REGEX_PATTERN = r'\b(10|[1-9](\.\d+)?|0?\.\d+)\b' DEFAULT_SCORE = 5.0 class TimeUtils: + """ + Utility class for handling time-related calculations for YouTube videos. + """ @staticmethod def get_timestamp_n_days_from_now(days: int) -> str: """ - Get the timestamp for a date n days ago in ISO 8601 format. + Get the timestamp for a date N days ago in ISO 8601 format. """ date_before_n_days = datetime.now(timezone.utc) - timedelta(days=days) - formatted_date = date_before_n_days \ - .isoformat('T') \ - .replace('+00:00', 'Z') + formatted_date = date_before_n_days.isoformat('T').replace('+00:00', 'Z') return formatted_date @staticmethod def is_duration_in_mins(duration: str) -> bool: """ - Check if the duration is in minutes. + Return True if the duration string contains hours or does not contain minutes. + Used to filter out videos that are too short or too long. """ return 'H' in duration or 'M' not in duration @staticmethod def derive_total_seconds_from_duration(duration: str) -> int: """ - Derive total seconds from duration (ISO 8601 format, e.g. "PT5M30S"). + Convert ISO 8601 duration (e.g., 'PT5M30S') to total seconds. """ - parts = duration.split('M') - mins = int(parts[0]) - secs = parts[1].replace('S', '') if len(parts) > 1 else '0' - seconds = int(secs) if secs else 0 - total_seconds = mins * 60 + seconds + import isodate + try: + total_seconds = int(isodate.parse_duration(duration).total_seconds()) + except Exception as e: + print(f"Failed to parse duration {duration}: {e}") + total_seconds = 0 return total_seconds @staticmethod @@ -67,7 +74,7 @@ def is_video_duration_in_range( min_duration: int = DEFAULT_MIN_VIDEO_DURATION_MINUTES, max_duration: int = DEFAULT_MAX_VIDEO_DURATION_MINUTES) -> bool: """ - Check if the video duration is within the specified range in minutes. + Return True if the video duration is within min and max minutes. """ return min_duration * 60 <= total_seconds <= max_duration * 60 @@ -100,11 +107,10 @@ def __init__( self.__max_pages = max_pages self.query = query - self.__targeted_date = TimeUtils \ - .get_timestamp_n_days_from_now(no_prev_days) - self.__search_response = self.get_new_search_response() + self.__targeted_date = TimeUtils.get_timestamp_n_days_from_now(no_prev_days) self.__max_results = max_results + self.__search_response = self.get_new_search_response() self.scan_videos() def get_new_search_response(self) -> dict: @@ -132,7 +138,7 @@ def get_new_search_response(self) -> dict: def get_video_ids_from_search_response(self) -> list: """ - Extract video IDs from the search response. + Extract video IDs from the current search response. """ items_list = self.__search_response.get('items', []) return [item['id']['videoId'] for item in items_list] @@ -140,34 +146,33 @@ def get_video_ids_from_search_response(self) -> list: def filter_videos(self) -> None: """ Filter videos based on duration and recency. - This method processes the search response to filter videos that meet the criteria. + Adds filtered video details to self.__filtered_videos. """ video_ids = self.get_video_ids_from_search_response() - if not video_ids: print("No video IDs found in the search response.") return + # Fetch full video details details_config = { "part": "contentDetails,snippet", "id": ",".join(video_ids) } - details = VideoDetailsExtractor.__platform_conn \ - .videos() \ - .list(**details_config) \ - .execute() + details = VideoDetailsExtractor.__platform_conn.videos().list(**details_config).execute() for item in details.get('items', []): try: - duration = item['contentDetails']['duration'].replace('PT', '') - + duration = item['contentDetails']['duration'] + + # Skip videos with hours or missing minutes if TimeUtils.is_duration_in_mins(duration): continue - total_seconds = TimeUtils \ - .derive_total_seconds_from_duration(duration) + # Convert duration to total seconds + total_seconds = TimeUtils.derive_total_seconds_from_duration(duration) + # Check if video is in the desired range if TimeUtils.is_video_duration_in_range(total_seconds): video_details = { 'id': item['id'], @@ -177,15 +182,15 @@ def filter_videos(self) -> None: } self.__filtered_videos.append(video_details) - if len(self.__filtered_videos) >= DEFAULT_MAX_RESULTS: + # Stop if we reach the max results + if len(self.__filtered_videos) >= self.__max_results: break except Exception as e: print(f"Error processing video {item.get('id', 'N/A')}: {e}") continue - print( - f"Filtered {len(self.__filtered_videos)} videos based on criteria.") + print(f"Filtered {len(self.__filtered_videos)} videos based on criteria.") def has_filtered_videos_reached_limit(self) -> bool: """ @@ -197,14 +202,13 @@ def has_page_token_reached_limit(self) -> bool: """ Check if the maximum number of API calls has been reached. """ - return self.__page_count >= self.__max_pages + return self.__page_count < self.__max_pages def update_next_page_token(self) -> None: """ Update the next page token based on the search response. """ - self.__next_page_token = \ - self.__search_response.get('nextPageToken', None) + self.__next_page_token = self.__search_response.get('nextPageToken', None) def scan_videos(self) -> None: """ @@ -212,13 +216,17 @@ def scan_videos(self) -> None: This method keeps searching until it finds enough videos that meet the criteria or exhausts the search results. """ + # NOTE: + # First search page is fetched but not filtered due to original pagination logic. + # This is intentional to keep parity with the upstream source code. + while self.has_filtered_videos_reached_limit() and self.has_page_token_reached_limit(): self.__search_response = self.get_new_search_response() self.filter_videos() self.update_next_page_token() if not self.__next_page_token: break - + def get_video_details(self) -> list: """ Fetch video details for a list of filtered video based that were previously computed. @@ -241,8 +249,13 @@ def _initialize_model(cls): Initialize the Gemini model if it hasn't been initialized yet. """ if cls._model is None: - genai.configure(api_key=GEMINI_API_KEY) - cls._model = genai.GenerativeModel(MODEL_NAME) + #cls._client = genai.Client(api_key=GEMINI_API_KEY) + #cls._model = cls._client + + cls._client = genai.Client(api_key=GEMINI_API_KEY) + models = cls._client.models.list() + print(models) + @staticmethod def get_prompt_for_title(title: str, query: str) -> str: @@ -254,7 +267,7 @@ def get_prompt_for_title(title: str, query: str) -> str: f"Title: {title}\n" "Rate relevance & quality 1–10 (just give the number)." ) - + @classmethod def get_score_for_title(cls, title: str, query: str) -> float: """ @@ -265,15 +278,27 @@ def get_score_for_title(cls, title: str, query: str) -> float: cls._initialize_model() prompt = cls.get_prompt_for_title(title, query) try: - response = cls._model.generate_content(prompt) + response = cls._model.models.generate_content( + model=MODEL_NAME, + contents=prompt + ) score_text = response.text.strip() match = re.search(REGEX_PATTERN, score_text) return float(match.group()) if match else DEFAULT_SCORE - except (ValueError, AttributeError) as e: - print(f"[Error] Failed to parse score for '{title}': {e}") + + except (ValueError, AttributeError): + print( + "[Warning] Gemini response could not be parsed. " + "Using default relevance score." + ) return DEFAULT_SCORE + + except Exception as e: - print(f"[Error] API call failed for '{title}': {e}") + print( + "[Warning] Gemini API call failed. " + "Falling back to default relevance score." + ) return DEFAULT_SCORE @@ -306,13 +331,23 @@ def find_and_rank_videos(self, query: str, num_results: int = DEFAULT_MAX_RESULT if __name__ == '__main__': required_env_vars = ['YT_API_KEY', 'GEMINI_API_KEY'] + missing = [v for v in required_env_vars if not os.environ.get(v)] - if any([env_var not in os.environ for env_var in required_env_vars]): - raise KeyError( - "Error: YouTube and/or Gemini API keys not set in environment variables.") + if missing: + raise EnvironmentError( + f"Missing required environment variables: {', '.join(missing)}" + ) user_query = input("Enter your search: ") video_processor = VideoProcessor() pick_best = video_processor.find_and_rank_videos(user_query) + if not pick_best: + print("No relevant videos found.") + else: + for idx, video in enumerate(pick_best, start=1): + print(f"\n#{idx}") + print(f"Title: {video['title']}") + print(f"Score: {video['score']}") + print(f"Published: {video['publishedAt']}") From da9e9af562b1159ad334f96940ac5aee316dfd55 Mon Sep 17 00:00:00 2001 From: Efiom Ndaeyo Date: Mon, 12 Jan 2026 05:19:48 +0800 Subject: [PATCH 066/121] Add README documenting enhanced Gemini fallback and project structure --- youtube-relevance-finder/README.md | 113 +++++++++++++++++++---------- 1 file changed, 73 insertions(+), 40 deletions(-) diff --git a/youtube-relevance-finder/README.md b/youtube-relevance-finder/README.md index 5837c16..903b668 100644 --- a/youtube-relevance-finder/README.md +++ b/youtube-relevance-finder/README.md @@ -1,59 +1,92 @@ -# YouTube Relevance Finder with Gemini AI +# YouTube Relevance Finder with Gemini AI (Enhanced Version) -This Python script searches YouTube for recent videos based on a user query and ranks them by relevance using Google's Gemini AI model and Youtube API. It filters results by duration and recency, scores video titles for relevance, and returns the top-ranked videos. +This Python application searches YouTube for recent videos based on a user query +and ranks them by relevance using Google’s Gemini AI model and the YouTube Data API. + +--- ## πŸ” Features -- Searches YouTube for videos from the past 14 days using Youtube API which is publicly available. -- Filters videos by duration (4–20 minutes) -- Uses Gemini AI to score title relevance to a query -- Prints the top relevant video links with scores and metadata +- Searches YouTube for videos published within the last 14 days +- Filters videos by duration (10–60 minutes) +- Uses Gemini AI to score video title relevance to a search query +- Gracefully falls back to a default score if Gemini API calls fail +- Prints ranked video titles with relevance scores and publication dates -## πŸ› οΈ Setup +--- -1. **Clone the repository**: - ```bash - git clone https://github.com/yourusername/your-repo-name.git - cd your-repo-name -```` +## πŸ†• Differences from the Original Implementation -2. **Install dependencies**: +This version introduces several improvements compared to the original source code: - ```bash - pip install google-api-python-client google-generativeai - ``` +- **Graceful Gemini API fallback** + When the Gemini API is unavailable, rate-limited, or returns an unexpected + response, the application assigns a default relevance score instead of failing. -3. **Set up environment variables**: - Create a `.env` file or export in terminal: +- **Cleaner error handling** + SDK and API-related errors are handled internally and surfaced as clear, + user-friendly warning messages. - ```bash - export YT_API_KEY=your_youtube_api_key - export GEMINI_API_KEY=your_gemini_api_key - ``` +- **Improved project structure** + The application logic is organized into dedicated classes for: + - Time utilities + - YouTube video extraction and filtering + - Gemini-based scoring + - Video ranking and processing -## πŸš€ Usage +- **Explicit documentation of limitations** + Known API constraints and fallback behavior are documented to reflect + real-world usage conditions. -Run the script: +These changes make the project more robust and suitable for learning and experimentation. -```bash -python your_script_name.py -``` +--- -You'll be prompted to enter a search query. The script will then display a list of the top relevant YouTube videos based on that query. +## πŸ› οΈ Setup -## πŸ“„ Example Output +### 1. Clone the repository -``` -1. - β€’ Title: Learn Python in 10 Minutes - β€’ URL: https://youtu.be/xyz123 - β€’ Score: 9.2 - β€’ Duration: 10m30s - β€’ Published: 2025-05-01T12:34:56Z -``` +```bash +git clone https://github.com/yourusername/your-repo-name.git +cd your-repo-name -## πŸ“Œ Notes +2. Install dependencies +pip install google-api-python-client google-generativeai -* Make sure you have valid API keys for both YouTube Data API v3 and Google Gemini. -* The script currently uses the `gemini-1.5-flash-latest` model. +3. Set up environment variables +Create a .env file or export in your terminal: +export YT_API_KEY=your_youtube_api_key +export GEMINI_API_KEY=your_gemini_api_key +πŸš€ Usage +Run the script: +python app.py + +You will be prompted to enter a search query. +The script will then display a list of the top relevant YouTube videos. + +πŸ“„ Example Output +Enter your search: Brazilian Jiu Jitsu +Filtered 5 videos based on criteria. +[Warning] Gemini API call failed. Falling back to default relevance score. + +#1 +Title: The New Face of Brazilian Jiu-Jitsu +Score: 5.0 +Published: 2026-01-08T16:16:56Z + +πŸ“Œ Notes & Known Limitations +Valid API keys are required for: +YouTube Data API v3 +Google Gemini API +Gemini API usage is subject to quota limits and model availability +When Gemini scoring fails, a default relevance score is applied so the +application can continue running without interruption +This fallback behavior is intentional and documented for learning purposes + +🎯 Purpose of This Project +This project was contributed as part of an open-source learning journey to demonstrate: +API integration with third-party services +Defensive programming and graceful error handling +Clean project organization and documentation +Real-world constraints when working with LLM APIs From 6a2dc22c6d22ccdd88975180cddd7549b02248e2 Mon Sep 17 00:00:00 2001 From: Bestbrainof24 Date: Tue, 27 Jan 2026 13:04:31 +0200 Subject: [PATCH 067/121] Improve password generator security and input handling --- Password-Generator/password.py | 125 +++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 54 deletions(-) diff --git a/Password-Generator/password.py b/Password-Generator/password.py index 0dd1ae3..df5808e 100644 --- a/Password-Generator/password.py +++ b/Password-Generator/password.py @@ -1,63 +1,80 @@ -import random +import secrets +import string -def generatePassword(pwlength): - - alphabet = "abcdefghijklmnopqrstuvwxyz" - - passwords = [] - - for i in pwlength: - - password = "" - for j in range(i): - next_letter_index = random.randrange(len(alphabet)) - password = password + alphabet[next_letter_index] - - password = replaceWithNumber(password) - password = replaceWithUppercaseLetter(password) - - passwords.append(password) +def generate_password(length: int = 12, digits: bool = True, symbols: bool = True) -> str: - return passwords - - -def replaceWithNumber(pword): - for i in range(random.randrange(1,3)): - replace_index = random.randrange(len(pword)//2) - pword = pword[0:replace_index] + str(random.randrange(10)) + pword[replace_index+1:] - return pword - - -def replaceWithUppercaseLetter(pword): - for i in range(random.randrange(1,3)): - replace_index = random.randrange(len(pword)//2,len(pword)) - pword = pword[0:replace_index] + pword[replace_index].upper() + pword[replace_index+1:] - return pword - - - -def main(): + min_required = int(digits) + int(symbols) + if length < min_required: + raise ValueError("Password length too small for selected options") - numPasswords = int(input("How many passwords do you want to generate? ")) + pool = string.ascii_letters + required_chars = [] - print("Generating " +str(numPasswords)+" passwords") - - passwordLengths = [] - - print("Minimum length of password should be 3") + if digits: + pool += string.digits + required_chars.append(secrets.choice(string.digits)) - for i in range(numPasswords): - length = int(input("Enter the length of Password #" + str(i+1) + " ")) - if length<3: - length = 3 - passwordLengths.append(length) + if symbols: + pool += string.punctuation + required_chars.append(secrets.choice(string.punctuation)) + remaining_length = length - len(required_chars) - Password = generatePassword(passwordLengths) - - for i in range(numPasswords): - print ("Password #"+str(i+1)+" = " + Password[i]) - + password = required_chars + [secrets.choice(pool) for _ in range(remaining_length)] + secrets.SystemRandom().shuffle(password) + + return ''.join(password) + +def save(passwords: list) -> None: + try: + agree: str = input("Do you want to save the passwords to a file? (y/n): ").lower().strip() + if agree not in ["y", "yes"]: + return + else: + fileName: str = input("Enter the name of the file (without .txt): ").strip() + if not fileName: + fileName: str = "passwords" + print("\nSaving passwords to file...") + with open(fileName + ".txt", "w") as f: + for password in passwords: + f.write(password + "\n") + print("Passwords saved successfully!") + except OSError as e: + print(f"Error saving passwords: {e}") +def main(): + try: + length: int = int(input("Enter the length of the password: ")) + count: int = int(input("Enter the number of passwords to generate: ")) + + if length < 1 or count < 1: + print("Please enter positive integers only.") + return + + if length > 128: + print("Password length cannot be greater than 128 characters.") + return + + if length < 6: + print("Password is too short - Generating Passwords of length 6") + length = 6 + + digits: bool = input("Include digits? (y/n): ").lower().strip() in ["y", "yes"] + symbols: bool = input("Include symbols? (y/n): ").lower().strip() in ["y", "yes"] + + print("\nGenerating Secure Passwords...\n") + passwords = [] + for i in range(count): + password = generate_password(length, digits=digits, symbols=symbols) + passwords.append(password) + print(f"Password #{i + 1}: {password}") + + save(passwords) + + except ValueError: + print("Please Enter Valid Integers Only") + except Exception as e: + print(f"An unexpected error occurred: {e}") -main() +if __name__ == "__main__": + main() \ No newline at end of file From ae387337344f83b7cfe8c516742e45f8fa3ff4c3 Mon Sep 17 00:00:00 2001 From: Bestbrainof24 Date: Wed, 28 Jan 2026 19:36:28 +0200 Subject: [PATCH 068/121] Add Comprehensive README for password Generator and Add Type Hint For function 'main' --- Password-Generator/README.md | 162 +++++++++++++++++++++++++++++++++ Password-Generator/password.py | 2 +- 2 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 Password-Generator/README.md diff --git a/Password-Generator/README.md b/Password-Generator/README.md new file mode 100644 index 0000000..2204e40 --- /dev/null +++ b/Password-Generator/README.md @@ -0,0 +1,162 @@ +# Secure Password Generator + +A secure and customizable password generator built with Python that creates cryptographically strong passwords using the `secrets` module. + +## Features + +- Generates cryptographically secure passwords +- Customizable password length (6-128 characters) +- Option to include/exclude digits and symbols +- Generates multiple passwords at once +- Save passwords to a text file +- Input validation and error handling + +## Installation + +### Prerequisites +- Python 3.6 or higher + +### Setup +1. Clone or download the `password.py` file to your local machine +2. No additional dependencies required - uses only Python's standard library + +### In Terminal +```bash +git clone https://github.com/Grow-with-Open-Source/Python-Projects.git +cd Python-Projects +cd Password-Generator +python password.py +``` + +## Usage + +Run the script from your terminal or command prompt: + +```bash +python password.py +``` + +### In your own script +```python +from password import generate_password + +# Generate a single password +password = generate_password(length=12, digits=True, symbols=True) +print(password) + +# Generate without symbols +simple_password = generate_password(length=10, digits=True, symbols=False) +print(simple_password) +``` + +## Running Examples +### Example 1: Basic Password Generation + +```python +> python password.py +Enter the length of the password: 12 +Enter the number of passwords to generate: 3 +Include digits? (y/n): y +Include symbols? (y/n): y + +Generating Secure Passwords... + +Password #1: aB3@kL9#mN2! +Password #2: pQ8$rS1^tU4* +Password #3: xY7&zW5!vZ9@ + +Do you want to save the passwords to a file? (y/n): y +Enter the name of the file (without .txt): my_passwords + +Saving passwords to file... +Passwords saved successfully! +``` + +### Example 2: Simple Alphanumeric Passwords +```python +> python password.py +Enter the length of the password: 8 +Enter the number of passwords to generate: 2 +Include digits? (y/n): y +Include symbols? (y/n): n + +Generating Secure Passwords... + +Password #1: aB3cD9eF +Password #2: gH4iJ7kL +``` + +### Example 3: Short Password (Auto-corrected) +```python +> python password.py +Enter the length of the password: 4 +Enter the number of passwords to generate: 1 +Include digits? (y/n): y +Include symbols? (y/n): y + +Password is too short - Generating Passwords of length 6 + +Generating Secure Passwords... + +Password #1: a@3b#9 +``` + +## Interactive Options +When you run the script, you'll be prompted for the following: + +1. Password Length: Enter an integer between 6 and 128 +- If you enter less than 6, it will automatically set to 6 +- Maximum length is 128 characters + +2. Number of Passwords: How many passwords to generate (positive integer) + +3. Include Digits?: (y/n) - Whether to include numbers (0-9) + +4. Include Symbols?: (y/n) - Whether to include special characters (!@#$%^&*(), etc.) + +5. Save to File?: (y/n) - Option to save generated passwords to a text file +- If yes, you can specify a filename (default: "passwords.txt") + +## Password Security Features +1. **Cryptographically Secure**: Uses ``secrets`` module (not ``random``) for true randomness + +2. **Character Variety**: Ensures at least one character from each selected character set + +3. **Shuffling**: Passwords are shuffled after generation to avoid predictable patterns + +4. **Length Enforcement**: Minimum 6 characters for basic security + +5. **Input Validation**: Validates all user inputs to prevent errors + +## File Structure +```text +password.py +β”œβ”€β”€ generate_password(length, digits, symbols) # Core generator function +β”œβ”€β”€ save(passwords) # File saving function +└── main() # Interactive CLI interface +``` + +## Error Handling +The script includes comprehensive error handling for: + +- Invalid integer inputs +- File system errors when saving +- Unexpected exceptions +- Length constraints and requirements + +## Limitations +- Maximum password length is 128 characters +- Minimum password length is 6 characters (enforced) +- Uses standard Python string punctuation for symbols +- Text file saving is optional and basic + +## Security Notes +- βœ… Uses secrets module for cryptographically secure random generation +- βœ… No external dependencies or network calls +- βœ… Passwords are generated locally on your machine +- ❗ Saved passwords are stored in plain text - handle with care! +- ❗ Always use strong, unique passwords for different services + +## License +This project is open-source and intended for educational and practical use. +Refer to the repository license for usage terms. \ No newline at end of file diff --git a/Password-Generator/password.py b/Password-Generator/password.py index df5808e..3347a89 100644 --- a/Password-Generator/password.py +++ b/Password-Generator/password.py @@ -42,7 +42,7 @@ def save(passwords: list) -> None: except OSError as e: print(f"Error saving passwords: {e}") -def main(): +def main() -> None: try: length: int = int(input("Enter the length of the password: ")) count: int = int(input("Enter the number of passwords to generate: ")) From e246632b6e936b7a710ac38c7fcf30ab5f772a7b Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 1 Feb 2026 13:01:14 +0000 Subject: [PATCH 069/121] Updated Contributors Details --- .github/data/contributors-log.json | 6 ++++-- index.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 6fa8110..faf80b8 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -86,10 +86,12 @@ }, "Password-Generator": { "contributor-name": [ - "ShashiNova" + "ShashiNova", + "PieBerlin" ], "pull-request-number": [ - "13" + "13", + "53" ], "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Password-Generator/" }, diff --git a/index.md b/index.md index 9930c67..4496785 100644 --- a/index.md +++ b/index.md @@ -18,7 +18,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Spell-Checker | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#10](https://github.com/Grow-with-Open-Source/Python-Projects/pull/10 "visit pr #10") | [/Grow-with-Open-Source/Python-Projects/Spell-Checker/](Spell-Checker "view the result of Spell-Checker") | | QRCODE-Generator | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#11](https://github.com/Grow-with-Open-Source/Python-Projects/pull/11 "visit pr #11") | [/Grow-with-Open-Source/Python-Projects/QRCODE-Generator/](QRCODE-Generator "view the result of QRCODE-Generator") | | Music-Player | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile"), [Robekaben254](https://github.com/Robekaben254 "goto Robekaben254 profile") | [#12](https://github.com/Grow-with-Open-Source/Python-Projects/pull/12 "visit pr #12"), [#47](https://github.com/Grow-with-Open-Source/Python-Projects/pull/47 "visit pr #47") | [/Grow-with-Open-Source/Python-Projects/Music-Player/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Music-Player/ "view the result of Music-Player") | -| Password-Generator | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile") | [#13](https://github.com/Grow-with-Open-Source/Python-Projects/pull/13 "visit pr #13") | [/Grow-with-Open-Source/Python-Projects/Password-Generator/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Password-Generator/ "view the result of Password-Generator") | +| Password-Generator | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile"), [PieBerlin](https://github.com/PieBerlin "goto PieBerlin profile") | [#13](https://github.com/Grow-with-Open-Source/Python-Projects/pull/13 "visit pr #13"), [#53](https://github.com/Grow-with-Open-Source/Python-Projects/pull/53 "visit pr #53") | [/Grow-with-Open-Source/Python-Projects/Password-Generator/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Password-Generator/ "view the result of Password-Generator") | | Hungry_Serpent | [AnilKumarTeegala](https://github.com/AnilKumarTeegala "goto AnilKumarTeegala profile") | [#16](https://github.com/Grow-with-Open-Source/Python-Projects/pull/16 "visit pr #16") | [/Grow-with-Open-Source/Python-Projects/Hungry_Serpent/](Hungry_Serpent "view the result of Hungry_Serpent") | | Web-Blocker | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile") | [#14](https://github.com/Grow-with-Open-Source/Python-Projects/pull/14 "visit pr #14") | [/Grow-with-Open-Source/Python-Projects/Web-Blocker/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Web-Blocker "view the result of Web-Blocker") | | File_Organizer | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile") | [#15](https://github.com/Grow-with-Open-Source/Python-Projects/pull/15 "visit pr #15") | [/Grow-with-Open-Source/Python-Projects/File_Organizer/](File_Organizer "view the result of File_Organizer") | From 3023f4b57dbc70c35489fb8ddb5cba4ac941e7c6 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 1 Feb 2026 13:10:34 +0000 Subject: [PATCH 070/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index faf80b8..2d9dde9 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -292,5 +292,14 @@ "58" ], "demo-path": "Number-Plate-Detection" + }, + "Motion-Detection": { + "contributor-name": [ + "iamdevdhanush" + ], + "pull-request-number": [ + "62" + ], + "demo-path": "Motion-Detection" } } \ No newline at end of file diff --git a/index.md b/index.md index 4496785..fb1308b 100644 --- a/index.md +++ b/index.md @@ -40,6 +40,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Binary-Gene-Classifier-Model | [venkamita](https://github.com/venkamita "goto venkamita profile") | [#49](https://github.com/Grow-with-Open-Source/Python-Projects/pull/49 "visit pr #49") | [/Grow-with-Open-Source/Python-Projects/Binary-Gene-Classifier-Model/](Binary-Gene-Classifier-Model "view the result of Binary-Gene-Classifier-Model") | | Auto-Clicker | [BasselDar](https://github.com/BasselDar "goto BasselDar profile") | [#54](https://github.com/Grow-with-Open-Source/Python-Projects/pull/54 "visit pr #54") | [/Grow-with-Open-Source/Python-Projects/Auto-Clicker/](Auto-Clicker "view the result of Auto-Clicker") | | Number-Plate-Detection | [iamdevdhanush](https://github.com/iamdevdhanush "goto iamdevdhanush profile") | [#58](https://github.com/Grow-with-Open-Source/Python-Projects/pull/58 "visit pr #58") | [/Grow-with-Open-Source/Python-Projects/Number-Plate-Detection/](Number-Plate-Detection "view the result of Number-Plate-Detection") | +| Motion-Detection | [iamdevdhanush](https://github.com/iamdevdhanush "goto iamdevdhanush profile") | [#62](https://github.com/Grow-with-Open-Source/Python-Projects/pull/62 "visit pr #62") | [/Grow-with-Open-Source/Python-Projects/Motion-Detection/](Motion-Detection "view the result of Motion-Detection") | From 61ea33d61eb522f3bd79a142ea17414573596773 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 1 Feb 2026 13:23:05 +0000 Subject: [PATCH 071/121] Updated Contributors Details --- .github/data/contributors-log.json | 6 ++++-- index.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 2d9dde9..60439ce 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -204,10 +204,12 @@ }, "Youtube_video_finder_using_geminillm": { "contributor-name": [ - "veerababu1729" + "veerababu1729", + "asabo-dev" ], "pull-request-number": [ - "32" + "32", + "64" ], "demo-path": "Youtube_video_finder_using_geminillm" }, diff --git a/index.md b/index.md index fb1308b..7a95c26 100644 --- a/index.md +++ b/index.md @@ -30,7 +30,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Coin-Poison | [niharikah005](https://github.com/niharikah005 "goto niharikah005 profile"), [Achi-Vyshnavi](https://github.com/Achi-Vyshnavi "goto Achi-Vyshnavi profile") | [#24](https://github.com/Grow-with-Open-Source/Python-Projects/pull/24 "visit pr #24"), [#55](https://github.com/Grow-with-Open-Source/Python-Projects/pull/55 "visit pr #55") | [/Grow-with-Open-Source/Python-Projects/Coin-Poison/](Coin-Poison "view the result of Coin-Poison") | | Digital_Clock | [DarkSlayer102](https://github.com/DarkSlayer102 "goto DarkSlayer102 profile"), [swati-londhe](https://github.com/swati-londhe "goto swati-londhe profile") | [#25](https://github.com/Grow-with-Open-Source/Python-Projects/pull/25 "visit pr #25"), [#41](https://github.com/Grow-with-Open-Source/Python-Projects/pull/41 "visit pr #41") | [/Grow-with-Open-Source/Python-Projects/Digital_Clock/](Digital_Clock "view the result of Digital_Clock") | | Secure_Password_Manager | [Chaitanya6Nli](https://github.com/Chaitanya6Nli "goto Chaitanya6Nli profile") | [#29](https://github.com/Grow-with-Open-Source/Python-Projects/pull/29 "visit pr #29") | [/Grow-with-Open-Source/Python-Projects/Secure_Password_Manager/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Secure_Password_Manager "view the result of Secure_Password_Manager") | -| Youtube_video_finder_using_geminillm | [veerababu1729](https://github.com/veerababu1729 "goto veerababu1729 profile") | [#32](https://github.com/Grow-with-Open-Source/Python-Projects/pull/32 "visit pr #32") | [/Grow-with-Open-Source/Python-Projects/Youtube_video_finder_using_geminillm/](Youtube_video_finder_using_geminillm "view the result of Youtube_video_finder_using_geminillm") | +| Youtube_video_finder_using_geminillm | [veerababu1729](https://github.com/veerababu1729 "goto veerababu1729 profile"), [asabo-dev](https://github.com/asabo-dev "goto asabo-dev profile") | [#32](https://github.com/Grow-with-Open-Source/Python-Projects/pull/32 "visit pr #32"), [#64](https://github.com/Grow-with-Open-Source/Python-Projects/pull/64 "visit pr #64") | [/Grow-with-Open-Source/Python-Projects/Youtube_video_finder_using_geminillm/](Youtube_video_finder_using_geminillm "view the result of Youtube_video_finder_using_geminillm") | | {workflows} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#36](https://github.com/Grow-with-Open-Source/Python-Projects/pull/36 "visit pr #36"), [#39](https://github.com/Grow-with-Open-Source/Python-Projects/pull/39 "visit pr #39") | [/Grow-with-Open-Source/Python-Projects/.github/workflows](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github/workflows "view the result of {workflows}") | | Temp-Cleaner | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#35](https://github.com/Grow-with-Open-Source/Python-Projects/pull/35 "visit pr #35") | [/Grow-with-Open-Source/Python-Projects/Temp-Cleaner/](Temp-Cleaner "view the result of Temp-Cleaner") | | Gesture_Volume_Control | [10mudassir007](https://github.com/10mudassir007 "goto 10mudassir007 profile") | [#34](https://github.com/Grow-with-Open-Source/Python-Projects/pull/34 "visit pr #34") | [/Grow-with-Open-Source/Python-Projects/Gesture_Volume_Control/](Gesture_Volume_Control "view the result of Gesture_Volume_Control") | From efc408ea34a0a8ba901b657a31a9f8709347330e Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 1 Feb 2026 13:30:02 +0000 Subject: [PATCH 072/121] Updated Contributors Details --- .github/data/contributors-log.json | 6 ++++-- index.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 60439ce..3474570 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -87,11 +87,13 @@ "Password-Generator": { "contributor-name": [ "ShashiNova", - "PieBerlin" + "PieBerlin", + "Bestbrainof24" ], "pull-request-number": [ "13", - "53" + "53", + "66" ], "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Password-Generator/" }, diff --git a/index.md b/index.md index 7a95c26..577caab 100644 --- a/index.md +++ b/index.md @@ -18,7 +18,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Spell-Checker | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#10](https://github.com/Grow-with-Open-Source/Python-Projects/pull/10 "visit pr #10") | [/Grow-with-Open-Source/Python-Projects/Spell-Checker/](Spell-Checker "view the result of Spell-Checker") | | QRCODE-Generator | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#11](https://github.com/Grow-with-Open-Source/Python-Projects/pull/11 "visit pr #11") | [/Grow-with-Open-Source/Python-Projects/QRCODE-Generator/](QRCODE-Generator "view the result of QRCODE-Generator") | | Music-Player | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile"), [Robekaben254](https://github.com/Robekaben254 "goto Robekaben254 profile") | [#12](https://github.com/Grow-with-Open-Source/Python-Projects/pull/12 "visit pr #12"), [#47](https://github.com/Grow-with-Open-Source/Python-Projects/pull/47 "visit pr #47") | [/Grow-with-Open-Source/Python-Projects/Music-Player/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Music-Player/ "view the result of Music-Player") | -| Password-Generator | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile"), [PieBerlin](https://github.com/PieBerlin "goto PieBerlin profile") | [#13](https://github.com/Grow-with-Open-Source/Python-Projects/pull/13 "visit pr #13"), [#53](https://github.com/Grow-with-Open-Source/Python-Projects/pull/53 "visit pr #53") | [/Grow-with-Open-Source/Python-Projects/Password-Generator/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Password-Generator/ "view the result of Password-Generator") | +| Password-Generator | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile"), [PieBerlin](https://github.com/PieBerlin "goto PieBerlin profile"), [Bestbrainof24](https://github.com/Bestbrainof24 "goto Bestbrainof24 profile") | [#13](https://github.com/Grow-with-Open-Source/Python-Projects/pull/13 "visit pr #13"), [#53](https://github.com/Grow-with-Open-Source/Python-Projects/pull/53 "visit pr #53"), [#66](https://github.com/Grow-with-Open-Source/Python-Projects/pull/66 "visit pr #66") | [/Grow-with-Open-Source/Python-Projects/Password-Generator/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Password-Generator/ "view the result of Password-Generator") | | Hungry_Serpent | [AnilKumarTeegala](https://github.com/AnilKumarTeegala "goto AnilKumarTeegala profile") | [#16](https://github.com/Grow-with-Open-Source/Python-Projects/pull/16 "visit pr #16") | [/Grow-with-Open-Source/Python-Projects/Hungry_Serpent/](Hungry_Serpent "view the result of Hungry_Serpent") | | Web-Blocker | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile") | [#14](https://github.com/Grow-with-Open-Source/Python-Projects/pull/14 "visit pr #14") | [/Grow-with-Open-Source/Python-Projects/Web-Blocker/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Web-Blocker "view the result of Web-Blocker") | | File_Organizer | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile") | [#15](https://github.com/Grow-with-Open-Source/Python-Projects/pull/15 "visit pr #15") | [/Grow-with-Open-Source/Python-Projects/File_Organizer/](File_Organizer "view the result of File_Organizer") | From 7dc3ed28fd8d084a5805db3192d678c702bffe90 Mon Sep 17 00:00:00 2001 From: Aishwary Vansh Date: Sun, 8 Feb 2026 00:18:54 +0530 Subject: [PATCH 073/121] Added alarm, 12/24 toggle and improvements --- Digital_Clock/README.MD | 2 - Digital_Clock/digital_clock.py | 118 +++++++++++++++++++++++++++++++-- 2 files changed, 114 insertions(+), 6 deletions(-) diff --git a/Digital_Clock/README.MD b/Digital_Clock/README.MD index 263da1f..b9112e4 100644 --- a/Digital_Clock/README.MD +++ b/Digital_Clock/README.MD @@ -33,5 +33,3 @@ A simple Python-based digital clock application with a graphical user interface 3. **Execution:** The application runs continuously, keeping the time accurate until the user closes the window. - - diff --git a/Digital_Clock/digital_clock.py b/Digital_Clock/digital_clock.py index 9d6fc29..d8775ee 100644 --- a/Digital_Clock/digital_clock.py +++ b/Digital_Clock/digital_clock.py @@ -1,6 +1,9 @@ -from tkinter import Tk, Label +from tkinter import Tk, Label, Entry, Button, messagebox from tkinter.font import Font import time +from tkinter import Checkbutton, BooleanVar +from tkinter import Canvas + class DigitalClock: @@ -8,12 +11,34 @@ def __init__(self, font=None): """Initialize the digital clock.""" self.create_window() self.configure_window() + self.is_dark = True self.set_font(font) self.add_header() self.add_clock() self.add_date() # βœ… Added new method to show date + self.add_theme_button() + # self.add_theme_toggle() + self.is_24_hour = True + self.alarm_time = None + self.add_format_toggle() + self.add_alarm_section() self.update_time_on_clock() + def add_format_toggle(self): + self.format_button = Button(self.window, + text="Switch to 12 Hour", + font=("times", 15, "bold"), + command=self.toggle_format) + self.format_button.grid(row=5, column=2, pady=5) + + def toggle_format(self): + if self.is_24_hour: + self.is_24_hour = False + self.format_button.config(text="Switch to 24 Hour") + else: + self.is_24_hour = True + self.format_button.config(text="Switch to 12 Hour") + def create_window(self): """Create the main window.""" self.window = Tk() @@ -38,7 +63,8 @@ def add_clock(self): """Add the clock label to the window.""" self.clock = Label(self.window, font=( 'times', 90, 'bold'), bg='blue', fg='white') - self.clock.grid(row=2, column=2, padx=620, pady=250) + self.clock.grid(row=2, column=2, padx=500, pady=100) + # self.clock.grid(row=2, column=2, pady=20) def add_date(self): """Add a date label below the clock.""" @@ -46,6 +72,58 @@ def add_date(self): self.date_label.grid(row=3, column=2) self.update_date_on_clock() + # def toggle_theme(self): + # if self.is_dark: + # self.window.config(bg="white") + # self.clock.config(bg="white", fg="black") + # self.date_label.config(bg="white", fg="black") + # self.header.config(bg="lightgray", fg="black") + # else: + # self.window.config(bg="black") + # self.clock.config(bg="black", fg="white") + # self.date_label.config(bg="black", fg="white") + # self.header.config(bg="gray", fg="white") + + # self.is_dark = not self.is_dark + + def toggle_theme(self): + if self.theme_var.get(): # Dark mode ON + self.window.config(bg="black") + self.clock.config(bg="black", fg="white") + self.date_label.config(bg="black", fg="white") + self.header.config(bg="gray", fg="white") + self.theme_toggle.config(bg="black", fg="white") + else: # Light mode + self.window.config(bg="white") + self.clock.config(bg="white", fg="black") + self.date_label.config(bg="white", fg="black") + self.header.config(bg="lightgray", fg="black") + self.theme_toggle.config(bg="white", fg="black") + + # def add_theme_button(self): + # self.theme_button = Label(self.window, text="Toggle Theme", + # font=("times", 20, "bold"), + # bg="green", fg="white", + # cursor="hand2") + # self.theme_button.grid(row=4, column=2) + # self.theme_button.bind("", lambda e: self.toggle_theme()) + + def add_theme_button(self): + self.theme_var = BooleanVar(value=True) + + self.theme_toggle = Checkbutton( + self.window, + text="Dark Mode", + variable=self.theme_var, + command=self.toggle_theme, + bg="black", + fg="white", + selectcolor="black", + font=("times", 15, "bold") + ) + + self.theme_toggle.grid(row=4, column=2, pady=10) + def update_date_on_clock(self): """Update the date displayed below the clock.""" currentDate = time.strftime("%d-%b-%Y") @@ -55,15 +133,47 @@ def update_date_on_clock(self): def update_time_on_clock(self): """Update the time displayed on the clock every second.""" - currentTime = time.strftime("%H:%M:%S") + # currentTime = time.strftime("%H:%M:%S") + + if self.is_24_hour: + currentTime = time.strftime("%H:%M:%S") + else: + currentTime = time.strftime("%I:%M:%S %p") + + if self.alarm_time == currentTime: + messagebox.showinfo("Alarm", "⏰ Time's Up!") + self.alarm_time = None + self.clock.config(text=currentTime) self.clock.after(1000, self.update_time_on_clock) def start(self): """Start the Tkinter main loop.""" self.window.mainloop() + + def add_alarm_section(self): + self.alarm_entry = Entry(self.window, font=("times", 15)) + self.alarm_entry.grid(row=6, column=2, pady=5) + + self.alarm_button = Button(self.window, + text="Set Alarm (HH:MM:SS)", + font=("times", 12, "bold"), + command=self.set_alarm) + self.alarm_button.grid(row=7, column=2, pady=5) + + def set_alarm(self): + alarm_input = self.alarm_entry.get() + + try: + time.strptime(alarm_input, "%H:%M:%S") + self.alarm_time = alarm_input + messagebox.showinfo("Alarm Set", f"Alarm set for {alarm_input}") + except ValueError: + messagebox.showerror("Invalid Format", "Use HH:MM:SS format") + + if __name__ == "__main__": clock = DigitalClock() - clock.start() + clock.start() \ No newline at end of file From bb41117c761d67a95b2cf8cdbad5479b2b13bf9f Mon Sep 17 00:00:00 2001 From: Aishwary Vansh Date: Sun, 8 Feb 2026 00:19:52 +0530 Subject: [PATCH 074/121] Updated Readme file --- Digital_Clock/README.MD | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Digital_Clock/README.MD b/Digital_Clock/README.MD index b9112e4..683b357 100644 --- a/Digital_Clock/README.MD +++ b/Digital_Clock/README.MD @@ -33,3 +33,9 @@ A simple Python-based digital clock application with a graphical user interface 3. **Execution:** The application runs continuously, keeping the time accurate until the user closes the window. + +## Features Added +- Alarm functionality +- 12/24 hour toggle +- Theme switch +- Improved input validation \ No newline at end of file From 47dd9840dac6ec8e835693373d80b396bf4a47e9 Mon Sep 17 00:00:00 2001 From: Tithi Joshi Date: Sat, 14 Feb 2026 11:28:09 +0530 Subject: [PATCH 075/121] refactor: extract input normalization into helper function Added input normalization function and improved input validation. --- Animal-Guess/animalguess.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Animal-Guess/animalguess.py b/Animal-Guess/animalguess.py index 62dbd77..5d30926 100644 --- a/Animal-Guess/animalguess.py +++ b/Animal-Guess/animalguess.py @@ -1,5 +1,10 @@ +def normalize_input(text): + """ + Normalize user input for comparison. + Strips whitespace and converts to lowercase. + """ + return text.strip().lower() -# Improved version with modular structure, input validation, and typo fixes def check_guess(guess, answer): """ @@ -8,17 +13,20 @@ def check_guess(guess, answer): Returns 1 if correct, 0 otherwise. """ attempt = 0 + while attempt < 3: - if guess.lower().strip() == answer.lower(): + if normalize_input(guess) == normalize_input(answer): print("βœ… Correct Answer!") - return 1 # increment score + return 1 else: attempt += 1 if attempt < 3: - guess = input("❌ Wrong answer. Try again: ").strip() + guess = input("❌ Wrong answer. Try again: ") + print("The correct answer is:", answer) return 0 + def main(): """ Main game function. Loops through all questions and calculates the total score. @@ -33,12 +41,15 @@ def main(): score = 0 for question, answer in questions: - guess = input(question + " ").strip() - while not guess: - guess = input("Please enter a valid guess: ").strip() + guess = input(question + " ") + + while not guess.strip(): + guess = input("Please enter a valid guess: ") + score += check_guess(guess, answer) print(f"\nπŸ† Your Total Score is: {score} out of {len(questions)}") + if __name__ == "__main__": main() From 80614c5c3811fbf87506385d2a7c9fdfcf27660c Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 15 Feb 2026 06:13:52 +0000 Subject: [PATCH 076/121] Updated Contributors Details --- .github/data/contributors-log.json | 6 ++++-- index.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 3474570..c7257ee 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -187,11 +187,13 @@ "Digital_Clock": { "contributor-name": [ "DarkSlayer102", - "swati-londhe" + "swati-londhe", + "aishwary-vansh" ], "pull-request-number": [ "25", - "41" + "41", + "68" ], "demo-path": "Digital_Clock" }, diff --git a/index.md b/index.md index 577caab..a3a1197 100644 --- a/index.md +++ b/index.md @@ -28,7 +28,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | {others} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#22](https://github.com/Grow-with-Open-Source/Python-Projects/pull/22 "visit pr #22") | [/Grow-with-Open-Source/Python-Projects/.github](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github "view the result of {others}") | | 3d_pose_estimation | [mahipalimkar](https://github.com/mahipalimkar "goto mahipalimkar profile") | [#23](https://github.com/Grow-with-Open-Source/Python-Projects/pull/23 "visit pr #23") | [/Grow-with-Open-Source/Python-Projects/3d_pose_estimation/](3d_pose_estimation "view the result of 3d_pose_estimation") | | Coin-Poison | [niharikah005](https://github.com/niharikah005 "goto niharikah005 profile"), [Achi-Vyshnavi](https://github.com/Achi-Vyshnavi "goto Achi-Vyshnavi profile") | [#24](https://github.com/Grow-with-Open-Source/Python-Projects/pull/24 "visit pr #24"), [#55](https://github.com/Grow-with-Open-Source/Python-Projects/pull/55 "visit pr #55") | [/Grow-with-Open-Source/Python-Projects/Coin-Poison/](Coin-Poison "view the result of Coin-Poison") | -| Digital_Clock | [DarkSlayer102](https://github.com/DarkSlayer102 "goto DarkSlayer102 profile"), [swati-londhe](https://github.com/swati-londhe "goto swati-londhe profile") | [#25](https://github.com/Grow-with-Open-Source/Python-Projects/pull/25 "visit pr #25"), [#41](https://github.com/Grow-with-Open-Source/Python-Projects/pull/41 "visit pr #41") | [/Grow-with-Open-Source/Python-Projects/Digital_Clock/](Digital_Clock "view the result of Digital_Clock") | +| Digital_Clock | [DarkSlayer102](https://github.com/DarkSlayer102 "goto DarkSlayer102 profile"), [swati-londhe](https://github.com/swati-londhe "goto swati-londhe profile"), [aishwary-vansh](https://github.com/aishwary-vansh "goto aishwary-vansh profile") | [#25](https://github.com/Grow-with-Open-Source/Python-Projects/pull/25 "visit pr #25"), [#41](https://github.com/Grow-with-Open-Source/Python-Projects/pull/41 "visit pr #41"), [#68](https://github.com/Grow-with-Open-Source/Python-Projects/pull/68 "visit pr #68") | [/Grow-with-Open-Source/Python-Projects/Digital_Clock/](Digital_Clock "view the result of Digital_Clock") | | Secure_Password_Manager | [Chaitanya6Nli](https://github.com/Chaitanya6Nli "goto Chaitanya6Nli profile") | [#29](https://github.com/Grow-with-Open-Source/Python-Projects/pull/29 "visit pr #29") | [/Grow-with-Open-Source/Python-Projects/Secure_Password_Manager/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Secure_Password_Manager "view the result of Secure_Password_Manager") | | Youtube_video_finder_using_geminillm | [veerababu1729](https://github.com/veerababu1729 "goto veerababu1729 profile"), [asabo-dev](https://github.com/asabo-dev "goto asabo-dev profile") | [#32](https://github.com/Grow-with-Open-Source/Python-Projects/pull/32 "visit pr #32"), [#64](https://github.com/Grow-with-Open-Source/Python-Projects/pull/64 "visit pr #64") | [/Grow-with-Open-Source/Python-Projects/Youtube_video_finder_using_geminillm/](Youtube_video_finder_using_geminillm "view the result of Youtube_video_finder_using_geminillm") | | {workflows} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#36](https://github.com/Grow-with-Open-Source/Python-Projects/pull/36 "visit pr #36"), [#39](https://github.com/Grow-with-Open-Source/Python-Projects/pull/39 "visit pr #39") | [/Grow-with-Open-Source/Python-Projects/.github/workflows](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github/workflows "view the result of {workflows}") | From 7841987520d468bdfd82b7b510b885f51e9ee2e4 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 15 Feb 2026 06:18:30 +0000 Subject: [PATCH 077/121] Updated Contributors Details --- .github/data/contributors-log.json | 6 ++++-- index.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index c7257ee..ceebeab 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -38,11 +38,13 @@ "Animal-Guess": { "contributor-name": [ "ShashiNova", - "Achi-Vyshnavi" + "Achi-Vyshnavi", + "Tithi234" ], "pull-request-number": [ "8", - "56" + "56", + "69" ], "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Animal-Guess" }, diff --git a/index.md b/index.md index a3a1197..b781402 100644 --- a/index.md +++ b/index.md @@ -13,7 +13,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Language_Detector | [rahulch-1](https://github.com/rahulch-1 "goto rahulch-1 profile") | [#4](https://github.com/Grow-with-Open-Source/Python-Projects/pull/4 "visit pr #4") | [/Grow-with-Open-Source/Python-Projects/Language_Detector/](Language_Detector "view the result of Language_Detector") | | TIC_TAC_TOE | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#7](https://github.com/Grow-with-Open-Source/Python-Projects/pull/7 "visit pr #7") | [/Grow-with-Open-Source/Python-Projects/TIC_TAC_TOE/](TIC_TAC_TOE "view the result of TIC_TAC_TOE") | | Weather_Forecasting | [rahulch-1](https://github.com/rahulch-1 "goto rahulch-1 profile") | [#6](https://github.com/Grow-with-Open-Source/Python-Projects/pull/6 "visit pr #6") | [/Grow-with-Open-Source/Python-Projects/Weather_Forecasting/](Weather_Forecasting "view the result of Weather_Forecasting") | -| Animal-Guess | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile"), [Achi-Vyshnavi](https://github.com/Achi-Vyshnavi "goto Achi-Vyshnavi profile") | [#8](https://github.com/Grow-with-Open-Source/Python-Projects/pull/8 "visit pr #8"), [#56](https://github.com/Grow-with-Open-Source/Python-Projects/pull/56 "visit pr #56") | [/Grow-with-Open-Source/Python-Projects/Animal-Guess/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Animal-Guess "view the result of Animal-Guess") | +| Animal-Guess | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile"), [Achi-Vyshnavi](https://github.com/Achi-Vyshnavi "goto Achi-Vyshnavi profile"), [Tithi234](https://github.com/Tithi234 "goto Tithi234 profile") | [#8](https://github.com/Grow-with-Open-Source/Python-Projects/pull/8 "visit pr #8"), [#56](https://github.com/Grow-with-Open-Source/Python-Projects/pull/56 "visit pr #56"), [#69](https://github.com/Grow-with-Open-Source/Python-Projects/pull/69 "visit pr #69") | [/Grow-with-Open-Source/Python-Projects/Animal-Guess/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Animal-Guess "view the result of Animal-Guess") | | Wine-Quality-Analysis | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#9](https://github.com/Grow-with-Open-Source/Python-Projects/pull/9 "visit pr #9") | [/Grow-with-Open-Source/Python-Projects/Wine-Quality-Analysis/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Wine-Quality-Analysis "view the result of Wine-Quality-Analysis") | | Spell-Checker | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#10](https://github.com/Grow-with-Open-Source/Python-Projects/pull/10 "visit pr #10") | [/Grow-with-Open-Source/Python-Projects/Spell-Checker/](Spell-Checker "view the result of Spell-Checker") | | QRCODE-Generator | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#11](https://github.com/Grow-with-Open-Source/Python-Projects/pull/11 "visit pr #11") | [/Grow-with-Open-Source/Python-Projects/QRCODE-Generator/](QRCODE-Generator "view the result of QRCODE-Generator") | From 4fe2b55fd510e6dc5a847a1da1ff36814b028f75 Mon Sep 17 00:00:00 2001 From: musharrafhamraz Date: Fri, 6 Feb 2026 12:35:09 +0500 Subject: [PATCH 078/121] feat(Bowling-Action-Tracking): Add bowling action tracking with pose detection - Add main.py with MediaPipe-based pose detection for bowling motion analysis - Implement wrist trajectory tracking using deque for motion history - Add gradient arc visualization for shoulder-elbow-wrist chain movement - Include video processing pipeline with frame resizing and RGB conversion - Add helper functions for coordinate conversion and gradient arc rendering - Include sample bowling video files (bowling.mp4 and bowling_action.mp4) - Enables real-time visualization of bowling arm motion with color-coded keypoints --- Bowling-Action-Tracking/main.py | 103 ++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 Bowling-Action-Tracking/main.py diff --git a/Bowling-Action-Tracking/main.py b/Bowling-Action-Tracking/main.py new file mode 100644 index 0000000..245fad8 --- /dev/null +++ b/Bowling-Action-Tracking/main.py @@ -0,0 +1,103 @@ +import cv2 +import mediapipe as mp +import numpy as np +from collections import deque + +# Initialize MediaPipe Pose +mp_pose = mp.solutions.pose +pose = mp_pose.Pose() +mp_drawing = mp.solutions.drawing_utils + +# Initialize video capture +cap = cv2.VideoCapture('bowling.mp4') + +# Store wrist trajectory and arcs +wrist_trajectory = deque(maxlen=100) # Main wrist trajectory +wrist_positions = deque(maxlen=20) # For arcs +elbow_positions = deque(maxlen=20) +shoulder_positions = deque(maxlen=20) + +# Function to convert normalized coordinates to pixel coordinates +def to_pixel_coords(landmark, frame): + return int(landmark.x * frame.shape[1]), int(landmark.y * frame.shape[0]) + +# Function to draw a gradient arc between two points +def draw_gradient_arc(frame, p1, p2, thickness, start_color, end_color): + num_segments = 50 + x_diff = (p2[0] - p1[0]) / num_segments + y_diff = (p2[1] - p1[1]) / num_segments + + for i in range(num_segments): + # Compute start and end points of each segment + start_point = (int(p1[0] + i * x_diff), int(p1[1] + i * y_diff)) + end_point = (int(p1[0] + (i + 1) * x_diff), int(p1[1] + (i + 1) * y_diff)) + + # Interpolate color between start and end + alpha = i / num_segments + color = ( + int(start_color[0] * (1 - alpha) + end_color[0] * alpha), + int(start_color[1] * (1 - alpha) + end_color[1] * alpha), + int(start_color[2] * (1 - alpha) + end_color[2] * alpha), + ) + cv2.line(frame, start_point, end_point, color, thickness) + +while cap.isOpened(): + ret, frame = cap.read() + if not ret: + break + frame = cv2.resize(frame, (1000, 600)) + # Convert the frame to RGB + rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + results = pose.process(rgb_frame) + + if results.pose_landmarks: + landmarks = results.pose_landmarks.landmark + + # Extract right-hand keypoints + right_shoulder = landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER] + right_elbow = landmarks[mp_pose.PoseLandmark.RIGHT_ELBOW] + right_wrist = landmarks[mp_pose.PoseLandmark.RIGHT_WRIST] + + # Convert normalized coordinates to pixel coordinates + shoulder_coords = to_pixel_coords(right_shoulder, frame) + elbow_coords = to_pixel_coords(right_elbow, frame) + wrist_coords = to_pixel_coords(right_wrist, frame) + + # Add wrist coordinates to the deque for trajectory + wrist_trajectory.append(wrist_coords) + + # Add coordinates for arcs + shoulder_positions.append(shoulder_coords) + elbow_positions.append(elbow_coords) + wrist_positions.append(wrist_coords) + + # Draw the wrist trajectory (main line) + for i in range(1, len(wrist_trajectory)): + cv2.line(frame, wrist_trajectory[i - 1], wrist_trajectory[i], (0, 255, 255), 3) + + # Draw dynamic arcs for the last few positions + for i in range(1, len(shoulder_positions)): + # Fade effect using index + thickness = max(2, 10 - (len(shoulder_positions) - i)) + + # Gradient arc for shoulder-to-elbow + draw_gradient_arc(frame, shoulder_positions[i - 1], elbow_positions[i - 1], thickness, (0, 255, 0), (255, 0, 0)) + # Gradient arc for elbow-to-wrist + draw_gradient_arc(frame, elbow_positions[i - 1], wrist_positions[i - 1], thickness, (255, 0, 0), (0, 0, 255)) + + # Draw keypoints + cv2.circle(frame, shoulder_coords, 10, (0, 255, 0), -1) # Shoulder + cv2.circle(frame, elbow_coords, 10, (255, 0, 0), -1) # Elbow + cv2.circle(frame, wrist_coords, 10, (0, 0, 255), -1) # Wrist + + # Display the frame + cv2.imshow('Dynamic Bowling Trajectory', frame) + + if cv2.waitKey(10) & 0xFF == ord('q'): + break + +cap.release() +cv2.destroyAllWindows() + + + From 152f53e0141d5d24b2d4d937f0418c3b96b27726 Mon Sep 17 00:00:00 2001 From: Tithi Joshi Date: Mon, 16 Feb 2026 11:26:16 +0530 Subject: [PATCH 079/121] refactor: improve structure and safety of auto clicker script --- Auto-Clicker/auto_clicker.py | 57 +++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/Auto-Clicker/auto_clicker.py b/Auto-Clicker/auto_clicker.py index c0c9d23..76f698b 100644 --- a/Auto-Clicker/auto_clicker.py +++ b/Auto-Clicker/auto_clicker.py @@ -2,33 +2,48 @@ import keyboard import time -clicking = False +def run_auto_clicker(delay: float = 0.01) -> None: + """ + Runs an auto clicker that can be controlled with keyboard hotkeys. -def start_clicking(): - global clicking - clicking = True - print("Auto clicker started") + Controls: + - Press 'S' to start clicking + - Press 'E' to stop clicking + - Press 'Q' to quit + """ + clicking = False + def start_clicking(): + nonlocal clicking + clicking = True + print("βœ… Auto clicker started") -def stop_clicking(): - global clicking - clicking = False - print("Auto clicker stopped") + def stop_clicking(): + nonlocal clicking + clicking = False + print("⏹ Auto clicker stopped") + + keyboard.add_hotkey("s", start_clicking) + keyboard.add_hotkey("e", stop_clicking) + + print("Press 'S' to start clicking") + print("Press 'E' to stop clicking") + print("Press 'Q' to quit") + try: + while True: + if clicking: + pyautogui.click() + time.sleep(delay) -keyboard.add_hotkey("s", start_clicking) -keyboard.add_hotkey("e", stop_clicking) + if keyboard.is_pressed("q"): + print("πŸ‘‹ Exiting program") + break -print("Press 'S' to start clicking") -print("Press 'E' to stop clicking") -print("Press 'Q' to quit") + except KeyboardInterrupt: + print("\nProgram interrupted by user.") -while True: - if clicking: - pyautogui.click() - time.sleep(0.001) - if keyboard.is_pressed("q"): - print("Exiting program") - break +if __name__ == "__main__": + run_auto_clicker() From 5c12671766b863f73b91743da75c99b1b13f9af7 Mon Sep 17 00:00:00 2001 From: Tyler Date: Fri, 20 Feb 2026 11:41:31 +0300 Subject: [PATCH 080/121] Initil commit. This is a script used to either generate a password or check strength of your password. --- securepass/README.md | 117 ++++++++++++++++++++++++++++++++++++++ securepass/password.py | 126 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 securepass/README.md create mode 100644 securepass/password.py diff --git a/securepass/README.md b/securepass/README.md new file mode 100644 index 0000000..e79364b --- /dev/null +++ b/securepass/README.md @@ -0,0 +1,117 @@ +# Secure Password Manager + +A Python utility for generating secure passwords and checking the strength of existing passwords. + +## Features + +- **Password Generator**: Create secure passwords with multiple options + - Mix of numbers, letters, and symbols (Recommended) + - Numbers only + - Letters only + - Symbols only + - Customizable length (4-20 characters) + +- **Password Strength Checker**: Analyze password quality and get recommendations + - Check password length + - Analyze character composition (uppercase, lowercase, numbers, symbols) + - Receive suggestions for improvement + - Detailed password report + +## Installation + +No external dependencies are required. This script uses only Python standard library modules. + +```bash +# Clone or download the repository +# Navigate to the securepass directory +cd securepass + +# Run the script +python password.py +``` + +## Usage + +Run the script and follow the interactive prompts: + +```bash +python password.py +``` + +### Main Menu + +You'll be presented with two options: +1. **Generate a secure password** - Create a new password with custom specifications +2. **Check strength of my password** - Analyze an existing password + +### Generate Password Workflow + +1. Select the password type (1-4) +2. Enter desired length (4-20 characters) +3. View your generated password +4. Optionally get a detailed password report + +### Check Password Strength Workflow + +1. Enter the password you want to check +2. Receive a detailed report with recommendations + +## Example Output + +``` +What would you like to do: +1 Generate a secure password +2 Check strength of my password +> 1 + +Choose password type: +1 Mix of numbers, letters and symbols (Recommended) +2 Numbers only password +3 Letters only password +4 Symbols only password +> 1 + +Enter your desired length (between 4 and 20): 12 +Here is your password: aB3!xK9$mQ2@ + +Would you like a report for this password? (y/n): y +The password has a length of 12 characters, which meets or exceeds the recommended 8. +It has 2 uppercase letter(s), 2 lowercase letter(s), 3 number(s), and 3 symbol(s). +This password has a good mix of character types. +``` + +## Password Strength Criteria + +The password strength checker evaluates: + +- **Length**: Recommends a minimum of 8 characters +- **Character Diversity**: Checks for presence of: + - Uppercase letters + - Lowercase letters + - Numbers + - Symbols + +## Functions + +- `generate_number_only(length)` - Generates password with digits only +- `generate_letters_only(length)` - Generates password with letters only +- `generate_symbols_only(length)` - Generates password with symbols only +- `mix_of_all(length)` - Generates password with mix of all character types +- `password_report(password)` - Analyzes password strength and returns report + +## Requirements + +- Python 3.x +- No external packages required + +## Best Practices + +- Use "Mix of numbers, letters and symbols" for the strongest passwords +- Maintain a minimum length of 12 characters for sensitive accounts +- Store generated passwords securely (consider using a password manager) +- Regularly update passwords for important accounts +- Never share passwords or store them in plain text + +## License + +This project is part of the Python-Projects repository by Grow-with-Open-Source. diff --git a/securepass/password.py b/securepass/password.py new file mode 100644 index 0000000..e617355 --- /dev/null +++ b/securepass/password.py @@ -0,0 +1,126 @@ +import random +import string + + +def main(): + while True: + option = input( + "What would you like to do:\n" + "1 Generate a secure password\n" + "2 Check strength of my password\n> " + ) + if option not in ("1", "2"): + print("Please choose 1 or 2.") + continue + break + + if option == "1": + while True: + try: + choice = int( + input( + "Choose password type:\n" + "1 Mix of numbers, letters and symbols (Recommended)\n" + "2 Numbers only password\n" + "3 Letters only password\n" + "4 Symbols only password\n> " + ) + ) + length = int(input("Enter your desired length (between 4 and 20): ")) + except ValueError: + print("Invalid input, enter numbers only.") + continue + + if choice not in (1, 2, 3, 4) or length not in range(4, 21): + print("Invalid input, try again.") + continue + break + + if choice == 1: + passwd = mix_of_all(length) + elif choice == 2: + passwd = generate_number_only(length) + elif choice == 3: + passwd = generate_letters_only(length) + else: + passwd = generate_symbols_only(length) + + print("Here is your password:", passwd) + + if input("Would you like a report for this password? (y/n): ").lower() == "y": + print(password_report(passwd)) + + else: # option == "2" + existing = input("Enter the password you want to check: ") + print(password_report(existing)) + + +def generate_number_only(length): + digits = string.digits + return "".join(random.choice(digits) for _ in range(length)) + + +def generate_letters_only(length): + letters = string.ascii_letters + return "".join(random.choice(letters) for _ in range(length)) + + +def generate_symbols_only(length): + symbols = "!@#$%^&*()-_=+[]{};:,.<>?/\\|" + return "".join(random.choice(symbols) for _ in range(length)) + + +def mix_of_all(length): + pool = string.ascii_letters + string.digits + "!@#$%^&*()-_=+[]{};:,.<>?/\\|" + return "".join(random.choice(pool) for _ in range(length)) + + +def password_report(password: str) -> str: + recommended_length = 8 + + length = len(password) + upper = sum(1 for ch in password if ch.isupper()) + lower = sum(1 for ch in password if ch.islower()) + digits = sum(1 for ch in password if ch.isdigit()) + symbols = sum(1 for ch in password if not ch.isalnum()) + + parts = [] + + # Length report + diff = recommended_length - length + if diff > 0: + parts.append( + f"The password has a length of {length} characters, {diff} less than the recommended {recommended_length}." + ) + else: + parts.append( + f"The password has a length of {length} characters, which meets or exceeds the recommended {recommended_length}." + ) + + # Composition report + parts.append( + f"It has {upper} uppercase letter(s), {lower} lowercase letter(s), {digits} number(s), and {symbols} symbol(s)." + ) + + suggestions = [] + if upper == 0: + suggestions.append("add at least one uppercase letter") + if lower == 0: + suggestions.append("add at least one lowercase letter") + if digits == 0: + suggestions.append("add at least one number") + if symbols == 0: + suggestions.append("add a symbol for extra strength") + + if suggestions: + parts.append( + "To improve this password, you could " + ", ".join(suggestions) + "." + ) + else: + parts.append("This password has a good mix of character types.") + + return " ".join(parts) + + +if __name__ == '__main__': + main() From cd3c6611044fbcc16532dd0124a96918b2694849 Mon Sep 17 00:00:00 2001 From: Tithi Joshi Date: Sat, 21 Feb 2026 16:45:48 +0530 Subject: [PATCH 081/121] Refactor Pomodoro timer with countdown function Removed fragile modulo-based break detection - Introduced modular countdown function - Implemented session-based break handling - Added input validation and negative value checks - Added graceful KeyboardInterrupt handling - Improved readability and maintainability --- Pomodoro-Timer/pomodoro.py | 99 ++++++++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 35 deletions(-) diff --git a/Pomodoro-Timer/pomodoro.py b/Pomodoro-Timer/pomodoro.py index 887c786..1b3cdf6 100644 --- a/Pomodoro-Timer/pomodoro.py +++ b/Pomodoro-Timer/pomodoro.py @@ -1,43 +1,72 @@ import time import datetime -# Create a function that acts as a countdown -def pomodoro_timer(task, h, m, s): - # Calculate the total number of seconds - total_seconds = h * 3600 + m * 60 + s - # Counter to keep track of the breaks - break_count = 0 +FOCUS_DURATION = 25 * 60 # 25 minutes +SHORT_BREAK = 5 * 60 # 5 minutes +LONG_BREAK = 20 * 60 # 20 minutes +SESSIONS_BEFORE_LONG_BREAK = 4 - while total_seconds > 0: - # Timer represents time left on the countdown - timer = datetime.timedelta(seconds=total_seconds) - # Prints the time left on the timer - print(f"Focusing on {task}... Session time left: {timer}", end="\r") - # Delays the program one second +def countdown(label, seconds): + """Runs a countdown timer.""" + while seconds > 0: + timer = datetime.timedelta(seconds=seconds) + print(f"{label} - Time left: {timer}", end="\r") time.sleep(1) + seconds -= 1 + print(f"\n{label} completed!\n") + + +def pomodoro_timer(task, total_seconds): + """Runs Pomodoro cycles based on total time.""" + sessions_completed = 0 + + while total_seconds > 0: + print(f"\nStarting focus session for: {task}") + session_time = min(FOCUS_DURATION, total_seconds) + countdown("Focus Session", session_time) + total_seconds -= session_time + sessions_completed += 1 + + if total_seconds <= 0: + break + + if sessions_completed % SESSIONS_BEFORE_LONG_BREAK == 0: + print("Starting Long Break...") + countdown("Long Break", LONG_BREAK) + else: + print("Starting Short Break...") + countdown("Short Break", SHORT_BREAK) + + print("Task Completed Successfully πŸŽ‰") + + +def get_user_input(): + """Handles validated user input.""" + try: + task = input("Enter the task to focus on: ") + + h = int(input("Enter hours: ")) + m = int(input("Enter minutes: ")) + s = int(input("Enter seconds: ")) + + if h < 0 or m < 0 or s < 0: + raise ValueError("Time values cannot be negative.") + + return task, h * 3600 + m * 60 + s + + except ValueError as e: + print(f"Invalid input: {e}") + return None, None + + +if __name__ == "__main__": + try: + task, total_seconds = get_user_input() + + if task and total_seconds: + pomodoro_timer(task, total_seconds) - # Reduces total time by one second - total_seconds -= 1 - - # Check if it's time for a break (only for the first 4 breaks) - if total_seconds > 0 and break_count < 4 and total_seconds % 1500 == 0: - print("\nNow on a short break!") - time.sleep(300) # Short break for 5 minutes - break_count += 1 - - # Check if it's time for a long break (after 4 sessions) - elif total_seconds > 0 and break_count == 4 and total_seconds % 1500 == 0: - print("\nNow on a long break!") - time.sleep(1200) # Long break for 20 minutes - break_count = 0 # Reset the break count for the next cycle - - print("\nTask Completed") - -# Inputs for hours, minutes, and seconds on the timer -task = input("Enter the task to focus on: ") -h = int(input("Enter the time in hours: ")) -m = int(input("Enter the time in minutes: ")) -s = int(input("Enter the time in seconds: ")) -pomodoro_timer(task, h, m, s) + except KeyboardInterrupt: + print("\nTimer stopped manually.") From dd21662fa85c1bd2653025a3b3643ff43c0079bc Mon Sep 17 00:00:00 2001 From: moon Date: Mon, 23 Feb 2026 21:41:32 +0000 Subject: [PATCH 082/121] add encryption project --- Encrypt_Message/README.md | 6 ++++ Encrypt_Message/encryption/aes.py | 13 ++++++++ Encrypt_Message/encryption/sha.py | 5 +++ Encrypt_Message/main.py | 54 +++++++++++++++++++++++++++++++ Encrypt_Message/requirements.txt | 1 + 5 files changed, 79 insertions(+) create mode 100644 Encrypt_Message/README.md create mode 100644 Encrypt_Message/encryption/aes.py create mode 100644 Encrypt_Message/encryption/sha.py create mode 100644 Encrypt_Message/main.py create mode 100644 Encrypt_Message/requirements.txt diff --git a/Encrypt_Message/README.md b/Encrypt_Message/README.md new file mode 100644 index 0000000..93db246 --- /dev/null +++ b/Encrypt_Message/README.md @@ -0,0 +1,6 @@ +# Encrypt Message + +A bad made Encrypt code that can Encrypt with: +SHA-256 +AES-128 +More soon \ No newline at end of file diff --git a/Encrypt_Message/encryption/aes.py b/Encrypt_Message/encryption/aes.py new file mode 100644 index 0000000..9fccb38 --- /dev/null +++ b/Encrypt_Message/encryption/aes.py @@ -0,0 +1,13 @@ +from Crypto.Cipher import AES + +def aes_enc(message: str, key: bytes): + mess = message.encode() + cipher = AES.new(key, AES.MODE_EAX) + nonce = cipher.nonce + mess_cipher, tag = cipher.encrypt_and_digest(mess) + return mess_cipher, nonce + +def aes_desc(message: bytes, key: bytes, nonce: bytes): + + cipher = AES.new(key, AES.MODE_EAX, nonce=nonce) + return cipher.decrypt(message) \ No newline at end of file diff --git a/Encrypt_Message/encryption/sha.py b/Encrypt_Message/encryption/sha.py new file mode 100644 index 0000000..8d5eee3 --- /dev/null +++ b/Encrypt_Message/encryption/sha.py @@ -0,0 +1,5 @@ +import hashlib + +def sha256_enc(message: str): + + return hashlib.sha256(message.encode()).hexdigest() \ No newline at end of file diff --git a/Encrypt_Message/main.py b/Encrypt_Message/main.py new file mode 100644 index 0000000..30b282a --- /dev/null +++ b/Encrypt_Message/main.py @@ -0,0 +1,54 @@ +from encryption import sha, aes +import os + +def main(): + """ + This is the entry point of this Encryption Message + """ + + print("""Welcome to Encrypt Message!\n + Your options are:\n + 1 - AES\n + 2 - SHA\n +""") + + option = int(input()) + + match option: + case 1: + + print("""Do you want to encrypt or decrypt a message?\n + 1 - Encrypt + 2 - Decrypt""") + + enc_dec = int(input()) + + if enc_dec == 1: + + message = input("Which message you want to encrypt? ") + key = os.urandom(16) + + print(f"Your key is {key}\n*Save it!*") + + enc_mess, nonce = aes.aes_enc(message, key) + + print(f"{enc_mess}\n{nonce}\n*Save this*") + else: + message = eval(input("What's the message you want to decrypt? ")) + key = eval(input("What's the key? ")) + nonce = eval(input("What's the nonce? ")) + + desc_mess = aes.aes_desc(message, key, nonce) + + print(f"{desc_mess}") + + case 2: + message = input("What's the message you want to hide? (This method is unreversible)") + + enc_mess = sha.sha256_enc(message) + + print(f"{enc_mess}") + +if __name__ == "__main__": + main() + diff --git a/Encrypt_Message/requirements.txt b/Encrypt_Message/requirements.txt new file mode 100644 index 0000000..c21b6ec --- /dev/null +++ b/Encrypt_Message/requirements.txt @@ -0,0 +1 @@ +pycryptodome \ No newline at end of file From cbf6192cbaa0ff7847d3aa2dea95a170464a018e Mon Sep 17 00:00:00 2001 From: moon Date: Tue, 24 Feb 2026 19:09:21 +0000 Subject: [PATCH 083/121] added encryption project This is a project to make examples of how to encrypt using specific methods listed in the README --- Encrypt_Message/README.md | 6 -- Encrypt_Message/encryption/aes.py | 13 ----- Encrypt_Message/encryption/sha.py | 5 -- Encrypt_Message/main.py | 54 ------------------ Encrypt_Message/requirements.txt | 1 - Encryption_Project/README.md | 9 +++ Encryption_Project/encryption/aes.py | 22 ++++++++ Encryption_Project/encryption/base64.py | 16 ++++++ Encryption_Project/encryption/sha.py | 10 ++++ Encryption_Project/main.py | 73 +++++++++++++++++++++++++ Encryption_Project/requirements.txt | 1 + 11 files changed, 131 insertions(+), 79 deletions(-) delete mode 100644 Encrypt_Message/README.md delete mode 100644 Encrypt_Message/encryption/aes.py delete mode 100644 Encrypt_Message/encryption/sha.py delete mode 100644 Encrypt_Message/main.py delete mode 100644 Encrypt_Message/requirements.txt create mode 100644 Encryption_Project/README.md create mode 100644 Encryption_Project/encryption/aes.py create mode 100644 Encryption_Project/encryption/base64.py create mode 100644 Encryption_Project/encryption/sha.py create mode 100644 Encryption_Project/main.py create mode 100644 Encryption_Project/requirements.txt diff --git a/Encrypt_Message/README.md b/Encrypt_Message/README.md deleted file mode 100644 index 93db246..0000000 --- a/Encrypt_Message/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Encrypt Message - -A bad made Encrypt code that can Encrypt with: -SHA-256 -AES-128 -More soon \ No newline at end of file diff --git a/Encrypt_Message/encryption/aes.py b/Encrypt_Message/encryption/aes.py deleted file mode 100644 index 9fccb38..0000000 --- a/Encrypt_Message/encryption/aes.py +++ /dev/null @@ -1,13 +0,0 @@ -from Crypto.Cipher import AES - -def aes_enc(message: str, key: bytes): - mess = message.encode() - cipher = AES.new(key, AES.MODE_EAX) - nonce = cipher.nonce - mess_cipher, tag = cipher.encrypt_and_digest(mess) - return mess_cipher, nonce - -def aes_desc(message: bytes, key: bytes, nonce: bytes): - - cipher = AES.new(key, AES.MODE_EAX, nonce=nonce) - return cipher.decrypt(message) \ No newline at end of file diff --git a/Encrypt_Message/encryption/sha.py b/Encrypt_Message/encryption/sha.py deleted file mode 100644 index 8d5eee3..0000000 --- a/Encrypt_Message/encryption/sha.py +++ /dev/null @@ -1,5 +0,0 @@ -import hashlib - -def sha256_enc(message: str): - - return hashlib.sha256(message.encode()).hexdigest() \ No newline at end of file diff --git a/Encrypt_Message/main.py b/Encrypt_Message/main.py deleted file mode 100644 index 30b282a..0000000 --- a/Encrypt_Message/main.py +++ /dev/null @@ -1,54 +0,0 @@ -from encryption import sha, aes -import os - -def main(): - """ - This is the entry point of this Encryption Message - """ - - print("""Welcome to Encrypt Message!\n - Your options are:\n - 1 - AES\n - 2 - SHA\n -""") - - option = int(input()) - - match option: - case 1: - - print("""Do you want to encrypt or decrypt a message?\n - 1 - Encrypt - 2 - Decrypt""") - - enc_dec = int(input()) - - if enc_dec == 1: - - message = input("Which message you want to encrypt? ") - key = os.urandom(16) - - print(f"Your key is {key}\n*Save it!*") - - enc_mess, nonce = aes.aes_enc(message, key) - - print(f"{enc_mess}\n{nonce}\n*Save this*") - else: - message = eval(input("What's the message you want to decrypt? ")) - key = eval(input("What's the key? ")) - nonce = eval(input("What's the nonce? ")) - - desc_mess = aes.aes_desc(message, key, nonce) - - print(f"{desc_mess}") - - case 2: - message = input("What's the message you want to hide? (This method is unreversible)") - - enc_mess = sha.sha256_enc(message) - - print(f"{enc_mess}") - -if __name__ == "__main__": - main() - diff --git a/Encrypt_Message/requirements.txt b/Encrypt_Message/requirements.txt deleted file mode 100644 index c21b6ec..0000000 --- a/Encrypt_Message/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -pycryptodome \ No newline at end of file diff --git a/Encryption_Project/README.md b/Encryption_Project/README.md new file mode 100644 index 0000000..1992a90 --- /dev/null +++ b/Encryption_Project/README.md @@ -0,0 +1,9 @@ +# Encryption Project + +For now this project can only encrypt messages using the following methods: + +* SHA-256 +* AES-128 +* Base64 + +*If you want to understand how this works, this project comments all around it to make sure you understand what's happening* diff --git a/Encryption_Project/encryption/aes.py b/Encryption_Project/encryption/aes.py new file mode 100644 index 0000000..c284215 --- /dev/null +++ b/Encryption_Project/encryption/aes.py @@ -0,0 +1,22 @@ +# Let's start with AES +# AES is a type of encryption used mainly for things that require certain level of safety and later will be used +# There is a specific thing you shouldn't encrypt with AES that's Passkeys +# For passkeys you will use a method showed later that are the Hash Methods + +# In this example we using pycryptodome + +from Crypto.Cipher import AES + +def encrypt_aes(message: str, key: str): + encrypt_cipher = AES.new(key, AES.MODE_EAX) # This is the "configuration" for the encryption + + nonce = encrypt_cipher.nonce # Nonce aka Number Once is basically what makes the key random + + encrypted_message = encrypt_cipher.encrypt_and_digest(message) # Here we are truly encrypting the message + + return encrypted_message, nonce + +def decrypt_aes(message: bytes, key: bytes, nonce: bytes): + decrypt_cipher = AES.new(key, AES.MODE_EAX, nonce=nonce) # Here we are Saying what's the key, type of encryption and nonce + decrypted_message = decrypt_cipher.decrypt(message) + return decrypted_message diff --git a/Encryption_Project/encryption/base64.py b/Encryption_Project/encryption/base64.py new file mode 100644 index 0000000..8bf15b4 --- /dev/null +++ b/Encryption_Project/encryption/base64.py @@ -0,0 +1,16 @@ +# Let's start +# Base64 is NOT a safe way to encrypt important things, it's used to encrypt binary data to text and doesn't have a key such as AES +# If you pretend encrypting something that you need to know later you should use AES +# Else you just want to encrypt something and doesn't care about the privacy Base64 might be a good choice + +# In this example we are using a pre-installed module from Python called "base64" + +import base64 + +def encrypt_base64(message: str): + encrypt_message = base64.b64encode(message.encode()).decode() + return encrypt_message + +def decrypt_base64(message: str): + decrypt_message = base64.b64decode(message).decode('utf-8') + return decrypt_message \ No newline at end of file diff --git a/Encryption_Project/encryption/sha.py b/Encryption_Project/encryption/sha.py new file mode 100644 index 0000000..1892060 --- /dev/null +++ b/Encryption_Project/encryption/sha.py @@ -0,0 +1,10 @@ +# Let's start with Hashes! (more specifically SHA256) +# Hashes are a way to encrypt thing in a irreversible way +# This means if you encrypt something using Hash Methods there is no way to know the content + +# In this example we using pre-installed module called "hashlib" + +import hashlib + +def encryption_sha(message: str): + return hashlib.sha256().digest() # This makes the encryption in SHA-256. This is the usually how your passkeys are encrypted diff --git a/Encryption_Project/main.py b/Encryption_Project/main.py new file mode 100644 index 0000000..d0b72d7 --- /dev/null +++ b/Encryption_Project/main.py @@ -0,0 +1,73 @@ +from encryption import sha, aes, base64 +import os + +def main(): + while True: + print("""Welcome to Encryption Project\n + 1 - SHA256\n + 2 - AES\n + 3 - Base64\n + 4 - Quit\n + More soon!\n""") + + encrypt_choice = int(input()) + + match encrypt_choice: + case 1: + message_to_encrypt = input("What's the message you want to encrypt?: ") + + encrypted_message = sha.encryption_sha(message_to_encrypt) + + print(f"The encrypted message is: {encrypted_message}") + + case 2: + aes_choice = int(input("1 - Encrypt\n2 - Decrypt\n")) + + if aes_choice == 1: + message_to_encrypt = input("What's the message you want to encrypt? ").encode('utf-8') + key = os.urandom(16) # if you are confused, this just guarantee the key will have 16 bytes + + encrypted_message, nonce = aes.encrypt_aes(message_to_encrypt, key) + + print(f"Encrypted message: {encrypted_message}\nnonce: {nonce}\nkey: {key}\n *Save those!*") + elif aes_choice == 2: + message_to_decrypt = eval(input("What's the message to decrypt? ")) + key = eval(input("What's the key? ")) + nonce = eval(input("What's the nonce? ")) + + decrypted_message = aes.decrypt_aes(message_to_decrypt, key, nonce) + + print(f"Message: {decrypted_message}") + + else: + print("Option does not exist") + + case 3: + base_choice = int(input("1 - Encrypt\n2 - Decrypt\n")) + + if base_choice == 1: + message_to_encrypt = input("What's the message you want to encrypt? ") + + encrypted_message = base64.encrypt_base64(message_to_encrypt) + + print(f"Message: {encrypted_message}") + + elif base_choice == 2: + message_to_decrypt = input("What's the message to decrypt? ") + + decrypted_message = base64.decrypt_base64(message_to_decrypt) + + print(f"Message: {decrypted_message}") + + else: + print("Option does not exist") + + case 4: + print("Bye!") + break + + case _: + print("This option is not available") + +if __name__ == "__main__": + main() diff --git a/Encryption_Project/requirements.txt b/Encryption_Project/requirements.txt new file mode 100644 index 0000000..acdfd20 --- /dev/null +++ b/Encryption_Project/requirements.txt @@ -0,0 +1 @@ +pycryptodome From f83bd28eb5f9e83b8e4c3953007c047328441697 Mon Sep 17 00:00:00 2001 From: Tithi Joshi Date: Wed, 25 Feb 2026 14:41:48 +0530 Subject: [PATCH 084/121] Add scoreboard feature and improve game state handling Refactor Tic Tac Toe game code to improve structure and add score tracking. --- TIC_TAC_TOE/TIC_TAC_TOE.py | 195 +++++++++++++++++++------------------ 1 file changed, 101 insertions(+), 94 deletions(-) diff --git a/TIC_TAC_TOE/TIC_TAC_TOE.py b/TIC_TAC_TOE/TIC_TAC_TOE.py index c71c8c4..c49d3f9 100644 --- a/TIC_TAC_TOE/TIC_TAC_TOE.py +++ b/TIC_TAC_TOE/TIC_TAC_TOE.py @@ -1,117 +1,124 @@ from tkinter import * -import tkinter.messagebox +import tkinter.messagebox as messagebox tk = Tk() tk.title("Tic Tac Toe") tk.configure(bg='yellow') +# Player names p1 = StringVar() p2 = StringVar() -player1_name = Entry(textvariable=p1, bd=5, bg='white', width=40) +player1_name = Entry(tk, textvariable=p1, bd=5, bg='white', width=40) player1_name.grid(row=1, column=1, columnspan=8) player2_name = Entry(tk, textvariable=p2, bd=5, bg='white', width=40) player2_name.grid(row=2, column=1, columnspan=8) -bclick = True -flag = 0 -current_player_name = p1.get() if p1.get() else 'X' - - -def disableButton(): - for i in range(3): - for j in range(3): - buttons[i][j].configure(state=DISABLED) - - -def checkForWin(): - for i in range(3): - if buttons[i][0]["text"] == buttons[i][1]["text"] == buttons[i][2]["text"] != " ": - buttons[i][0].config(bg="green") - buttons[i][1].config(bg="green") - buttons[i][2].config(bg="green") - disableButton() - winner_name = p1.get( - ) if buttons[i][0]['text'] == 'X' else p2.get() - if not winner_name: - winner_name = 'Player 1' if buttons[i][0]['text'] == 'X' else 'Player 2' - tkinter.messagebox.showinfo("Tic-Tac-Toe", f"{winner_name} wins!") +# Score tracking +score_p1 = 0 +score_p2 = 0 + +current_player = "X" +moves_count = 0 + + +def update_scoreboard(): + score_label.config( + text=f"{p1.get() or 'Player 1'} (X): {score_p1} | {p2.get() or 'Player 2'} (O): {score_p2}" + ) + + +def disable_buttons(): + for row in buttons: + for button in row: + button.config(state=DISABLED) + + +def check_winner(): + global score_p1, score_p2 + + win_positions = [ + [(0, 0), (0, 1), (0, 2)], + [(1, 0), (1, 1), (1, 2)], + [(2, 0), (2, 1), (2, 2)], + [(0, 0), (1, 0), (2, 0)], + [(0, 1), (1, 1), (2, 1)], + [(0, 2), (1, 2), (2, 2)], + [(0, 0), (1, 1), (2, 2)], + [(0, 2), (1, 1), (2, 0)] + ] + + for combo in win_positions: + if buttons[combo[0][0]][combo[0][1]]["text"] == \ + buttons[combo[1][0]][combo[1][1]]["text"] == \ + buttons[combo[2][0]][combo[2][1]]["text"] != " ": + + winner_symbol = buttons[combo[0][0]][combo[0][1]]["text"] + + if winner_symbol == "X": + score_p1 += 1 + winner_name = p1.get() or "Player 1" + else: + score_p2 += 1 + winner_name = p2.get() or "Player 2" + + update_scoreboard() + disable_buttons() + messagebox.showinfo("Winner", f"{winner_name} wins!") + return True + + return False + + +def button_click(row, col): + global current_player, moves_count + + if buttons[row][col]["text"] == " ": + buttons[row][col]["text"] = current_player + moves_count += 1 + + if check_winner(): return - if buttons[0][i]["text"] == buttons[1][i]["text"] == buttons[2][i]["text"] != " ": - buttons[0][i].config(bg="green") - buttons[1][i].config(bg="green") - buttons[2][i].config(bg="green") - disableButton() - winner_name = p1.get( - ) if buttons[0][i]['text'] == 'X' else p2.get() - if not winner_name: - winner_name = 'Player 1' if buttons[0][i]['text'] == 'X' else 'Player 2' - tkinter.messagebox.showinfo("Tic-Tac-Toe", f"{winner_name} wins!") + + if moves_count == 9: + messagebox.showinfo("Tie", "It's a Tie!") return - if buttons[0][0]["text"] == buttons[1][1]["text"] == buttons[2][2]["text"] != " ": - buttons[0][0].config(bg="green") - buttons[1][1].config(bg="green") - buttons[2][2].config(bg="green") - disableButton() - winner_name = p1.get() if buttons[0][0]['text'] == 'X' else p2.get() - if not winner_name: - winner_name = 'Player 1' if buttons[0][0]['text'] == 'X' else 'Player 2' - tkinter.messagebox.showinfo("Tic-Tac-Toe", f"{winner_name} wins!") - return - if buttons[0][2]["text"] == buttons[1][1]["text"] == buttons[2][0]["text"] != " ": - buttons[0][2].config(bg="green") - buttons[1][1].config(bg="green") - buttons[2][0].config(bg="green") - disableButton() - winner_name = p1.get() if buttons[0][2]['text'] == 'X' else p2.get() - if not winner_name: - winner_name = 'Player 1' if buttons[0][2]['text'] == 'X' else 'Player 2' - tkinter.messagebox.showinfo("Tic-Tac-Toe", f"{winner_name} wins!") - return - if flag == 8: - tkinter.messagebox.showinfo("Tic-Tac-Toe", "It's a Tie") - - -def resetGame(): - global bclick, flag, current_player_name - for i in range(3): - for j in range(3): - buttons[i][j]["text"] = " " - buttons[i][j].config(bg='black', state=NORMAL) - bclick = True - flag = 0 - current_player_name = p1.get() if p1.get() else 'X' - - -Label(tk, text="Player 1:", font='Times 20 bold', bg='yellow', - fg='black', height=1, width=8).grid(row=1, column=0) -Label(tk, text="Player 2:", font='Times 20 bold', bg='yellow', - fg='black', height=1, width=8).grid(row=2, column=0) -buttons = [[Button(tk, text=' ', font='Times 20 bold', bg='black', - fg='white', height=4, width=8) for _ in range(3)] for _ in range(3)] - - -def btnClick(button): - global bclick, flag - if button["text"] == " ": - if bclick: - button["text"] = "X" - else: - button["text"] = "O" - bclick = not bclick - flag += 1 - checkForWin() + + current_player = "O" if current_player == "X" else "X" else: - tkinter.messagebox.showinfo("Tic-Tac-Toe", "Button already Clicked!") + messagebox.showinfo("Invalid Move", "Button already clicked!") + + +def reset_game(): + global current_player, moves_count + current_player = "X" + moves_count = 0 + for row in buttons: + for button in row: + button.config(text=" ", state=NORMAL) + + +Label(tk, text="Player 1:", font='Times 20 bold', bg='yellow').grid(row=1, column=0) +Label(tk, text="Player 2:", font='Times 20 bold', bg='yellow').grid(row=2, column=0) + +buttons = [[Button(tk, text=" ", font='Times 20 bold', bg='black', + fg='white', height=4, width=8, + command=lambda r=i, c=j: button_click(r, c)) + for j in range(3)] for i in range(3)] for i in range(3): for j in range(3): - buttons[i][j].configure(command=lambda row=i, - col=j: btnClick(buttons[row][col])) buttons[i][j].grid(row=i + 3, column=j) -reset_button = Button(tk, text="Reset Game", font='Times 16 bold', - bg='white', fg='black', command=resetGame) -reset_button.grid(row=6, column=0, columnspan=3) + +score_label = Label(tk, text="", font='Times 14 bold', bg='yellow') +score_label.grid(row=6, column=0, columnspan=3) + +update_scoreboard() + +reset_btn = Button(tk, text="Reset Game", font='Times 16 bold', + command=reset_game) +reset_btn.grid(row=7, column=0, columnspan=3) + tk.mainloop() From 30fbcbf9f64b93d2554798bfca2deeb34ee8659f Mon Sep 17 00:00:00 2001 From: Tannistha Pal Date: Sat, 7 Mar 2026 22:16:11 +0530 Subject: [PATCH 085/121] Add move history panel to track game moves --- TIC_TAC_TOE/TIC_TAC_TOE.py | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/TIC_TAC_TOE/TIC_TAC_TOE.py b/TIC_TAC_TOE/TIC_TAC_TOE.py index c71c8c4..714acf0 100644 --- a/TIC_TAC_TOE/TIC_TAC_TOE.py +++ b/TIC_TAC_TOE/TIC_TAC_TOE.py @@ -16,6 +16,8 @@ bclick = True flag = 0 +move_history = [] +move_number = 1 current_player_name = p1.get() if p1.get() else 'X' @@ -74,11 +76,17 @@ def checkForWin(): def resetGame(): - global bclick, flag, current_player_name + global bclick, flag, current_player_name, move_history, move_number + for i in range(3): for j in range(3): buttons[i][j]["text"] = " " buttons[i][j].config(bg='black', state=NORMAL) + + move_history = [] + move_number = 1 + history_box.delete(1.0, END) + bclick = True flag = 0 current_player_name = p1.get() if p1.get() else 'X' @@ -92,16 +100,31 @@ def resetGame(): fg='white', height=4, width=8) for _ in range(3)] for _ in range(3)] -def btnClick(button): - global bclick, flag +def btnClick(row, col): + global bclick, flag, move_number + + button = buttons[row][col] + if button["text"] == " ": + if bclick: button["text"] = "X" + player = p1.get() if p1.get() else "Player 1" else: button["text"] = "O" + player = p2.get() if p2.get() else "Player 2" + + # cleaner move history + move = f"{move_number}. {player} -> ({row+1},{col+1})" + move_history.append(move) + history_box.insert(END, move + "\n") + + move_number += 1 + bclick = not bclick flag += 1 checkForWin() + else: tkinter.messagebox.showinfo("Tic-Tac-Toe", "Button already Clicked!") @@ -109,9 +132,13 @@ def btnClick(button): for i in range(3): for j in range(3): buttons[i][j].configure(command=lambda row=i, - col=j: btnClick(buttons[row][col])) + col=j: btnClick(row, col)) buttons[i][j].grid(row=i + 3, column=j) reset_button = Button(tk, text="Reset Game", font='Times 16 bold', bg='white', fg='black', command=resetGame) reset_button.grid(row=6, column=0, columnspan=3) +Label(tk, text="Move History", font='Times 16 bold', bg='yellow').grid(row=0, column=10, padx=80) + +history_box = Text(tk, height=18, width=28, font=("Consolas", 11)) +history_box.grid(row=1, column=10, rowspan=6, padx=80) tk.mainloop() From 303f68aa47e59bb93682811c71de32cb96a5d80e Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 19 Mar 2026 15:54:54 +0000 Subject: [PATCH 086/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index ceebeab..2ec53f4 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -309,5 +309,14 @@ "62" ], "demo-path": "Motion-Detection" + }, + "Bowling-Action-Tracking": { + "contributor-name": [ + "musharrafhamraz" + ], + "pull-request-number": [ + "67" + ], + "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Bowling-Action-Tracking" } } \ No newline at end of file diff --git a/index.md b/index.md index b781402..0dc8ba6 100644 --- a/index.md +++ b/index.md @@ -41,6 +41,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Auto-Clicker | [BasselDar](https://github.com/BasselDar "goto BasselDar profile") | [#54](https://github.com/Grow-with-Open-Source/Python-Projects/pull/54 "visit pr #54") | [/Grow-with-Open-Source/Python-Projects/Auto-Clicker/](Auto-Clicker "view the result of Auto-Clicker") | | Number-Plate-Detection | [iamdevdhanush](https://github.com/iamdevdhanush "goto iamdevdhanush profile") | [#58](https://github.com/Grow-with-Open-Source/Python-Projects/pull/58 "visit pr #58") | [/Grow-with-Open-Source/Python-Projects/Number-Plate-Detection/](Number-Plate-Detection "view the result of Number-Plate-Detection") | | Motion-Detection | [iamdevdhanush](https://github.com/iamdevdhanush "goto iamdevdhanush profile") | [#62](https://github.com/Grow-with-Open-Source/Python-Projects/pull/62 "visit pr #62") | [/Grow-with-Open-Source/Python-Projects/Motion-Detection/](Motion-Detection "view the result of Motion-Detection") | +| Bowling-Action-Tracking | [musharrafhamraz](https://github.com/musharrafhamraz "goto musharrafhamraz profile") | [#67](https://github.com/Grow-with-Open-Source/Python-Projects/pull/67 "visit pr #67") | [/Grow-with-Open-Source/Python-Projects/Bowling-Action-Tracking/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Bowling-Action-Tracking "view the result of Bowling-Action-Tracking") | From a559f062f06eef278aa33ba2358b76bb015b1ada Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 19 Mar 2026 15:57:47 +0000 Subject: [PATCH 087/121] Updated Contributors Details --- .github/data/contributors-log.json | 6 ++++-- index.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 2ec53f4..12feda1 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -285,10 +285,12 @@ }, "Auto-Clicker": { "contributor-name": [ - "BasselDar" + "BasselDar", + "Tithi234" ], "pull-request-number": [ - "54" + "54", + "70" ], "demo-path": "Auto-Clicker" }, diff --git a/index.md b/index.md index 0dc8ba6..0b30152 100644 --- a/index.md +++ b/index.md @@ -38,7 +38,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Temperature_Converter | [omprakash0702](https://github.com/omprakash0702 "goto omprakash0702 profile") | [#40](https://github.com/Grow-with-Open-Source/Python-Projects/pull/40 "visit pr #40") | [/Grow-with-Open-Source/Python-Projects/Temperature_Converter/](Temperature_Converter "view the result of Temperature_Converter") | | Image_watermark_Adder | [ramanuj-droid](https://github.com/ramanuj-droid "goto ramanuj-droid profile") | [#45](https://github.com/Grow-with-Open-Source/Python-Projects/pull/45 "visit pr #45") | [/Grow-with-Open-Source/Python-Projects/Image_watermark_Adder/](Image_watermark_Adder "view the result of Image_watermark_Adder") | | Binary-Gene-Classifier-Model | [venkamita](https://github.com/venkamita "goto venkamita profile") | [#49](https://github.com/Grow-with-Open-Source/Python-Projects/pull/49 "visit pr #49") | [/Grow-with-Open-Source/Python-Projects/Binary-Gene-Classifier-Model/](Binary-Gene-Classifier-Model "view the result of Binary-Gene-Classifier-Model") | -| Auto-Clicker | [BasselDar](https://github.com/BasselDar "goto BasselDar profile") | [#54](https://github.com/Grow-with-Open-Source/Python-Projects/pull/54 "visit pr #54") | [/Grow-with-Open-Source/Python-Projects/Auto-Clicker/](Auto-Clicker "view the result of Auto-Clicker") | +| Auto-Clicker | [BasselDar](https://github.com/BasselDar "goto BasselDar profile"), [Tithi234](https://github.com/Tithi234 "goto Tithi234 profile") | [#54](https://github.com/Grow-with-Open-Source/Python-Projects/pull/54 "visit pr #54"), [#70](https://github.com/Grow-with-Open-Source/Python-Projects/pull/70 "visit pr #70") | [/Grow-with-Open-Source/Python-Projects/Auto-Clicker/](Auto-Clicker "view the result of Auto-Clicker") | | Number-Plate-Detection | [iamdevdhanush](https://github.com/iamdevdhanush "goto iamdevdhanush profile") | [#58](https://github.com/Grow-with-Open-Source/Python-Projects/pull/58 "visit pr #58") | [/Grow-with-Open-Source/Python-Projects/Number-Plate-Detection/](Number-Plate-Detection "view the result of Number-Plate-Detection") | | Motion-Detection | [iamdevdhanush](https://github.com/iamdevdhanush "goto iamdevdhanush profile") | [#62](https://github.com/Grow-with-Open-Source/Python-Projects/pull/62 "visit pr #62") | [/Grow-with-Open-Source/Python-Projects/Motion-Detection/](Motion-Detection "view the result of Motion-Detection") | | Bowling-Action-Tracking | [musharrafhamraz](https://github.com/musharrafhamraz "goto musharrafhamraz profile") | [#67](https://github.com/Grow-with-Open-Source/Python-Projects/pull/67 "visit pr #67") | [/Grow-with-Open-Source/Python-Projects/Bowling-Action-Tracking/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Bowling-Action-Tracking "view the result of Bowling-Action-Tracking") | From 195b740fe3b766fab22b69b44437551942a1541c Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 19 Mar 2026 16:44:34 +0000 Subject: [PATCH 088/121] Updated Contributors Details --- .github/data/contributors-log.json | 6 ++++-- index.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 12feda1..b99d3ce 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -150,10 +150,12 @@ }, "Pomodoro-Timer": { "contributor-name": [ - "adedayoprcs" + "adedayoprcs", + "Tithi234" ], "pull-request-number": [ - "21" + "21", + "73" ], "demo-path": "Pomodoro-Timer" }, diff --git a/index.md b/index.md index 0b30152..4130bc4 100644 --- a/index.md +++ b/index.md @@ -24,7 +24,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | File_Organizer | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile") | [#15](https://github.com/Grow-with-Open-Source/Python-Projects/pull/15 "visit pr #15") | [/Grow-with-Open-Source/Python-Projects/File_Organizer/](File_Organizer "view the result of File_Organizer") | | Number-Guess | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile"), [sheylaghost](https://github.com/sheylaghost "goto sheylaghost profile"), [Achi-Vyshnavi](https://github.com/Achi-Vyshnavi "goto Achi-Vyshnavi profile") | [#19](https://github.com/Grow-with-Open-Source/Python-Projects/pull/19 "visit pr #19"), [#43](https://github.com/Grow-with-Open-Source/Python-Projects/pull/43 "visit pr #43"), [#57](https://github.com/Grow-with-Open-Source/Python-Projects/pull/57 "visit pr #57") | [/Grow-with-Open-Source/Python-Projects/Number-Guess/](Number-Guess "view the result of Number-Guess") | | Remove-Empty-Lines | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile") | [#20](https://github.com/Grow-with-Open-Source/Python-Projects/pull/20 "visit pr #20") | [/Grow-with-Open-Source/Python-Projects/Remove-Empty-Lines/](Remove-Empty-Lines "view the result of Remove-Empty-Lines") | -| Pomodoro-Timer | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile") | [#21](https://github.com/Grow-with-Open-Source/Python-Projects/pull/21 "visit pr #21") | [/Grow-with-Open-Source/Python-Projects/Pomodoro-Timer/](Pomodoro-Timer "view the result of Pomodoro-Timer") | +| Pomodoro-Timer | [adedayoprcs](https://github.com/adedayoprcs "goto adedayoprcs profile"), [Tithi234](https://github.com/Tithi234 "goto Tithi234 profile") | [#21](https://github.com/Grow-with-Open-Source/Python-Projects/pull/21 "visit pr #21"), [#73](https://github.com/Grow-with-Open-Source/Python-Projects/pull/73 "visit pr #73") | [/Grow-with-Open-Source/Python-Projects/Pomodoro-Timer/](Pomodoro-Timer "view the result of Pomodoro-Timer") | | {others} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#22](https://github.com/Grow-with-Open-Source/Python-Projects/pull/22 "visit pr #22") | [/Grow-with-Open-Source/Python-Projects/.github](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/.github "view the result of {others}") | | 3d_pose_estimation | [mahipalimkar](https://github.com/mahipalimkar "goto mahipalimkar profile") | [#23](https://github.com/Grow-with-Open-Source/Python-Projects/pull/23 "visit pr #23") | [/Grow-with-Open-Source/Python-Projects/3d_pose_estimation/](3d_pose_estimation "view the result of 3d_pose_estimation") | | Coin-Poison | [niharikah005](https://github.com/niharikah005 "goto niharikah005 profile"), [Achi-Vyshnavi](https://github.com/Achi-Vyshnavi "goto Achi-Vyshnavi profile") | [#24](https://github.com/Grow-with-Open-Source/Python-Projects/pull/24 "visit pr #24"), [#55](https://github.com/Grow-with-Open-Source/Python-Projects/pull/55 "visit pr #55") | [/Grow-with-Open-Source/Python-Projects/Coin-Poison/](Coin-Poison "view the result of Coin-Poison") | From 643dabc3abd821f405562dd2a0339d90fdeeb2a5 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 19 Mar 2026 16:53:32 +0000 Subject: [PATCH 089/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index b99d3ce..82159f6 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -322,5 +322,14 @@ "67" ], "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Bowling-Action-Tracking" + }, + "Encryption_Project": { + "contributor-name": [ + "moonabys" + ], + "pull-request-number": [ + "75" + ], + "demo-path": "Encryption_Project" } } \ No newline at end of file diff --git a/index.md b/index.md index 4130bc4..4a79546 100644 --- a/index.md +++ b/index.md @@ -42,6 +42,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Number-Plate-Detection | [iamdevdhanush](https://github.com/iamdevdhanush "goto iamdevdhanush profile") | [#58](https://github.com/Grow-with-Open-Source/Python-Projects/pull/58 "visit pr #58") | [/Grow-with-Open-Source/Python-Projects/Number-Plate-Detection/](Number-Plate-Detection "view the result of Number-Plate-Detection") | | Motion-Detection | [iamdevdhanush](https://github.com/iamdevdhanush "goto iamdevdhanush profile") | [#62](https://github.com/Grow-with-Open-Source/Python-Projects/pull/62 "visit pr #62") | [/Grow-with-Open-Source/Python-Projects/Motion-Detection/](Motion-Detection "view the result of Motion-Detection") | | Bowling-Action-Tracking | [musharrafhamraz](https://github.com/musharrafhamraz "goto musharrafhamraz profile") | [#67](https://github.com/Grow-with-Open-Source/Python-Projects/pull/67 "visit pr #67") | [/Grow-with-Open-Source/Python-Projects/Bowling-Action-Tracking/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Bowling-Action-Tracking "view the result of Bowling-Action-Tracking") | +| Encryption_Project | [moonabys](https://github.com/moonabys "goto moonabys profile") | [#75](https://github.com/Grow-with-Open-Source/Python-Projects/pull/75 "visit pr #75") | [/Grow-with-Open-Source/Python-Projects/Encryption_Project/](Encryption_Project "view the result of Encryption_Project") | From a781572a74b948ecdd3d6aea2ca70d51663002c8 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 19 Mar 2026 16:55:47 +0000 Subject: [PATCH 090/121] Updated Contributors Details --- .github/data/contributors-log.json | 6 ++++-- index.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 82159f6..6751d73 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -19,10 +19,12 @@ }, "TIC_TAC_TOE": { "contributor-name": [ - "06RAVI06" + "06RAVI06", + "Tithi234" ], "pull-request-number": [ - "7" + "7", + "77" ], "demo-path": "TIC_TAC_TOE" }, diff --git a/index.md b/index.md index 4a79546..a272ccc 100644 --- a/index.md +++ b/index.md @@ -11,7 +11,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | --- | --- | --- | --- | | {init} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#1](https://github.com/Grow-with-Open-Source/Python-Projects/pull/1 "visit pr #1") | [/Grow-with-Open-Source/Python-Projects/](https://github.com/Grow-with-Open-Source/Python-Projects "view the result of {init}") | | Language_Detector | [rahulch-1](https://github.com/rahulch-1 "goto rahulch-1 profile") | [#4](https://github.com/Grow-with-Open-Source/Python-Projects/pull/4 "visit pr #4") | [/Grow-with-Open-Source/Python-Projects/Language_Detector/](Language_Detector "view the result of Language_Detector") | -| TIC_TAC_TOE | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#7](https://github.com/Grow-with-Open-Source/Python-Projects/pull/7 "visit pr #7") | [/Grow-with-Open-Source/Python-Projects/TIC_TAC_TOE/](TIC_TAC_TOE "view the result of TIC_TAC_TOE") | +| TIC_TAC_TOE | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile"), [Tithi234](https://github.com/Tithi234 "goto Tithi234 profile") | [#7](https://github.com/Grow-with-Open-Source/Python-Projects/pull/7 "visit pr #7"), [#77](https://github.com/Grow-with-Open-Source/Python-Projects/pull/77 "visit pr #77") | [/Grow-with-Open-Source/Python-Projects/TIC_TAC_TOE/](TIC_TAC_TOE "view the result of TIC_TAC_TOE") | | Weather_Forecasting | [rahulch-1](https://github.com/rahulch-1 "goto rahulch-1 profile") | [#6](https://github.com/Grow-with-Open-Source/Python-Projects/pull/6 "visit pr #6") | [/Grow-with-Open-Source/Python-Projects/Weather_Forecasting/](Weather_Forecasting "view the result of Weather_Forecasting") | | Animal-Guess | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile"), [Achi-Vyshnavi](https://github.com/Achi-Vyshnavi "goto Achi-Vyshnavi profile"), [Tithi234](https://github.com/Tithi234 "goto Tithi234 profile") | [#8](https://github.com/Grow-with-Open-Source/Python-Projects/pull/8 "visit pr #8"), [#56](https://github.com/Grow-with-Open-Source/Python-Projects/pull/56 "visit pr #56"), [#69](https://github.com/Grow-with-Open-Source/Python-Projects/pull/69 "visit pr #69") | [/Grow-with-Open-Source/Python-Projects/Animal-Guess/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Animal-Guess "view the result of Animal-Guess") | | Wine-Quality-Analysis | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#9](https://github.com/Grow-with-Open-Source/Python-Projects/pull/9 "visit pr #9") | [/Grow-with-Open-Source/Python-Projects/Wine-Quality-Analysis/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Wine-Quality-Analysis "view the result of Wine-Quality-Analysis") | From 6e339f3c5308d4e4a85e59d169296a58f83c7c40 Mon Sep 17 00:00:00 2001 From: TANNISTHA PAL Date: Fri, 20 Mar 2026 00:00:04 +0530 Subject: [PATCH 091/121] minor enahncements --- TIC_TAC_TOE/TIC_TAC_TOE.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/TIC_TAC_TOE/TIC_TAC_TOE.py b/TIC_TAC_TOE/TIC_TAC_TOE.py index 0b893f2..bf0e07e 100644 --- a/TIC_TAC_TOE/TIC_TAC_TOE.py +++ b/TIC_TAC_TOE/TIC_TAC_TOE.py @@ -54,6 +54,10 @@ def check_winner(): buttons[combo[1][0]][combo[1][1]]["text"] == \ buttons[combo[2][0]][combo[2][1]]["text"] != " ": + # βœ… Highlight winning cells + for pos in combo: + buttons[pos[0]][pos[1]].config(bg="green") + winner_symbol = buttons[combo[0][0]][combo[0][1]]["text"] if winner_symbol == "X": @@ -75,7 +79,7 @@ def button_click(row, col): if buttons[row][col]["text"] == " ": buttons[row][col]["text"] = current_player - # βœ… YOUR FEATURE (Move History) + # βœ… Move History player = p1.get() if current_player == "X" else p2.get() player = player or ("Player 1" if current_player == "X" else "Player 2") @@ -83,8 +87,6 @@ def button_click(row, col): history_box.insert(END, move + "\n") move_number += 1 - - # βœ… GAME LOGIC moves_count += 1 if check_winner(): @@ -92,10 +94,14 @@ def button_click(row, col): if moves_count == 9: messagebox.showinfo("Tie", "It's a Tie!") + disable_buttons() # βœ… prevent extra clicks return current_player = "O" if current_player == "X" else "X" + else: + messagebox.showinfo("Invalid Move", "Button already clicked!") + def reset_game(): global current_player, moves_count, move_number @@ -107,9 +113,9 @@ def reset_game(): for i in range(3): for j in range(3): - buttons[i][j].config(text=" ", state=NORMAL) + buttons[i][j].config(text=" ", bg="black", state=NORMAL) -# Create buttons +# Create buttons (safe version) for i in range(3): for j in range(3): buttons[i][j] = Button( From 9ef154864cf454c08c3bc2a78973ebf9e538b917 Mon Sep 17 00:00:00 2001 From: Tyler Date: Fri, 20 Mar 2026 07:20:59 +0300 Subject: [PATCH 092/121] Update securepass/password.py Co-authored-by: Shamith Nakka --- securepass/password.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/securepass/password.py b/securepass/password.py index e617355..b68678c 100644 --- a/securepass/password.py +++ b/securepass/password.py @@ -79,10 +79,16 @@ def password_report(password: str) -> str: recommended_length = 8 length = len(password) - upper = sum(1 for ch in password if ch.isupper()) - lower = sum(1 for ch in password if ch.islower()) - digits = sum(1 for ch in password if ch.isdigit()) - symbols = sum(1 for ch in password if not ch.isalnum()) + upper = lower = digits = symbols = 0 + for letter in password: + if letter.isupper(): + upper += 1 + elif letter.islower(): + lower += 1 + elif letter.isdigit(): + digits += 1 + else: + symbols += 1 parts = [] From 18ec27d64a77db32166a99b86afe9d9aa9da5600 Mon Sep 17 00:00:00 2001 From: Tyler Date: Fri, 20 Mar 2026 07:21:14 +0300 Subject: [PATCH 093/121] Update securepass/password.py Co-authored-by: Shamith Nakka --- securepass/password.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/securepass/password.py b/securepass/password.py index b68678c..9dc51f9 100644 --- a/securepass/password.py +++ b/securepass/password.py @@ -3,16 +3,15 @@ def main(): - while True: + option = "" + while option not in ("1", "2"): option = input( "What would you like to do:\n" "1 Generate a secure password\n" - "2 Check strength of my password\n> " + "2 Check the strength of my password\n> " ) if option not in ("1", "2"): print("Please choose 1 or 2.") - continue - break if option == "1": while True: From 2ab490a508450c5cde75a0fdbf50d4bc0660ca68 Mon Sep 17 00:00:00 2001 From: Tyler Date: Fri, 20 Mar 2026 07:21:30 +0300 Subject: [PATCH 094/121] Update securepass/password.py Co-authored-by: Shamith Nakka --- securepass/password.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/securepass/password.py b/securepass/password.py index 9dc51f9..f663f47 100644 --- a/securepass/password.py +++ b/securepass/password.py @@ -14,7 +14,9 @@ def main(): print("Please choose 1 or 2.") if option == "1": - while True: + choice = None + length = None + while choice not in (1, 2, 3, 4): try: choice = int( input( @@ -25,15 +27,17 @@ def main(): "4 Symbols only password\n> " ) ) + if choice not in (1, 2, 3, 4): + print("Invalid choice, try again.") + except ValueError: + print("Invalid input, enter numbers only.") + while length not in range(4, 21): + try: length = int(input("Enter your desired length (between 4 and 20): ")) + if length not in range(4, 21): + print("Invalid length, try again.") except ValueError: print("Invalid input, enter numbers only.") - continue - - if choice not in (1, 2, 3, 4) or length not in range(4, 21): - print("Invalid input, try again.") - continue - break if choice == 1: passwd = mix_of_all(length) From 8d45c09d9e9a4cd3414a812c5eaa0f01a4dced21 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 20 Mar 2026 14:43:48 +0000 Subject: [PATCH 095/121] Updated Contributors Details --- .github/data/contributors-log.json | 6 ++++-- index.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 6751d73..43011b6 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -20,11 +20,13 @@ "TIC_TAC_TOE": { "contributor-name": [ "06RAVI06", - "Tithi234" + "Tithi234", + "paltannistha" ], "pull-request-number": [ "7", - "77" + "77", + "80" ], "demo-path": "TIC_TAC_TOE" }, diff --git a/index.md b/index.md index a272ccc..2287182 100644 --- a/index.md +++ b/index.md @@ -11,7 +11,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | --- | --- | --- | --- | | {init} | [iamwatchdogs](https://github.com/iamwatchdogs "goto iamwatchdogs profile") | [#1](https://github.com/Grow-with-Open-Source/Python-Projects/pull/1 "visit pr #1") | [/Grow-with-Open-Source/Python-Projects/](https://github.com/Grow-with-Open-Source/Python-Projects "view the result of {init}") | | Language_Detector | [rahulch-1](https://github.com/rahulch-1 "goto rahulch-1 profile") | [#4](https://github.com/Grow-with-Open-Source/Python-Projects/pull/4 "visit pr #4") | [/Grow-with-Open-Source/Python-Projects/Language_Detector/](Language_Detector "view the result of Language_Detector") | -| TIC_TAC_TOE | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile"), [Tithi234](https://github.com/Tithi234 "goto Tithi234 profile") | [#7](https://github.com/Grow-with-Open-Source/Python-Projects/pull/7 "visit pr #7"), [#77](https://github.com/Grow-with-Open-Source/Python-Projects/pull/77 "visit pr #77") | [/Grow-with-Open-Source/Python-Projects/TIC_TAC_TOE/](TIC_TAC_TOE "view the result of TIC_TAC_TOE") | +| TIC_TAC_TOE | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile"), [Tithi234](https://github.com/Tithi234 "goto Tithi234 profile"), [paltannistha](https://github.com/paltannistha "goto paltannistha profile") | [#7](https://github.com/Grow-with-Open-Source/Python-Projects/pull/7 "visit pr #7"), [#77](https://github.com/Grow-with-Open-Source/Python-Projects/pull/77 "visit pr #77"), [#80](https://github.com/Grow-with-Open-Source/Python-Projects/pull/80 "visit pr #80") | [/Grow-with-Open-Source/Python-Projects/TIC_TAC_TOE/](TIC_TAC_TOE "view the result of TIC_TAC_TOE") | | Weather_Forecasting | [rahulch-1](https://github.com/rahulch-1 "goto rahulch-1 profile") | [#6](https://github.com/Grow-with-Open-Source/Python-Projects/pull/6 "visit pr #6") | [/Grow-with-Open-Source/Python-Projects/Weather_Forecasting/](Weather_Forecasting "view the result of Weather_Forecasting") | | Animal-Guess | [ShashiNova](https://github.com/ShashiNova "goto ShashiNova profile"), [Achi-Vyshnavi](https://github.com/Achi-Vyshnavi "goto Achi-Vyshnavi profile"), [Tithi234](https://github.com/Tithi234 "goto Tithi234 profile") | [#8](https://github.com/Grow-with-Open-Source/Python-Projects/pull/8 "visit pr #8"), [#56](https://github.com/Grow-with-Open-Source/Python-Projects/pull/56 "visit pr #56"), [#69](https://github.com/Grow-with-Open-Source/Python-Projects/pull/69 "visit pr #69") | [/Grow-with-Open-Source/Python-Projects/Animal-Guess/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Animal-Guess "view the result of Animal-Guess") | | Wine-Quality-Analysis | [06RAVI06](https://github.com/06RAVI06 "goto 06RAVI06 profile") | [#9](https://github.com/Grow-with-Open-Source/Python-Projects/pull/9 "visit pr #9") | [/Grow-with-Open-Source/Python-Projects/Wine-Quality-Analysis/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Wine-Quality-Analysis "view the result of Wine-Quality-Analysis") | From a27c251bb7bf0f3650c73bbf175e3d21aca27ed9 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 20 Mar 2026 15:33:40 +0000 Subject: [PATCH 096/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 43011b6..62f9014 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -335,5 +335,14 @@ "75" ], "demo-path": "Encryption_Project" + }, + "securepass": { + "contributor-name": [ + "Lampard7crypt" + ], + "pull-request-number": [ + "72" + ], + "demo-path": "securepass" } } \ No newline at end of file diff --git a/index.md b/index.md index 2287182..cea0fd5 100644 --- a/index.md +++ b/index.md @@ -43,6 +43,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Motion-Detection | [iamdevdhanush](https://github.com/iamdevdhanush "goto iamdevdhanush profile") | [#62](https://github.com/Grow-with-Open-Source/Python-Projects/pull/62 "visit pr #62") | [/Grow-with-Open-Source/Python-Projects/Motion-Detection/](Motion-Detection "view the result of Motion-Detection") | | Bowling-Action-Tracking | [musharrafhamraz](https://github.com/musharrafhamraz "goto musharrafhamraz profile") | [#67](https://github.com/Grow-with-Open-Source/Python-Projects/pull/67 "visit pr #67") | [/Grow-with-Open-Source/Python-Projects/Bowling-Action-Tracking/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Bowling-Action-Tracking "view the result of Bowling-Action-Tracking") | | Encryption_Project | [moonabys](https://github.com/moonabys "goto moonabys profile") | [#75](https://github.com/Grow-with-Open-Source/Python-Projects/pull/75 "visit pr #75") | [/Grow-with-Open-Source/Python-Projects/Encryption_Project/](Encryption_Project "view the result of Encryption_Project") | +| securepass | [Lampard7crypt](https://github.com/Lampard7crypt "goto Lampard7crypt profile") | [#72](https://github.com/Grow-with-Open-Source/Python-Projects/pull/72 "visit pr #72") | [/Grow-with-Open-Source/Python-Projects/securepass/](securepass "view the result of securepass") | From e762bb4f4f3576c1787ea70ecf2df900ee048dd5 Mon Sep 17 00:00:00 2001 From: moonabys <255314530+moonabys@users.noreply.github.com> Date: Sat, 28 Mar 2026 15:56:39 +0000 Subject: [PATCH 097/121] fixed small issue fixed sha.py not having proper function call --- Encryption_Project/encryption/sha.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Encryption_Project/encryption/sha.py b/Encryption_Project/encryption/sha.py index 1892060..d91a0b6 100644 --- a/Encryption_Project/encryption/sha.py +++ b/Encryption_Project/encryption/sha.py @@ -7,4 +7,5 @@ import hashlib def encryption_sha(message: str): - return hashlib.sha256().digest() # This makes the encryption in SHA-256. This is the usually how your passkeys are encrypted + encoded_message = message.encode() + return hashlib.sha256(encoded_message).digest() # This makes the encryption in SHA-256. This is the usually how your passkeys are encrypted From ad5ae9a1e7fabc344f212ca049d7ded85a85b075 Mon Sep 17 00:00:00 2001 From: Princee Date: Fri, 10 Apr 2026 12:15:17 +0545 Subject: [PATCH 098/121] Add input validation and success messaging --- Spell-Checker/Spell-Checker.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Spell-Checker/Spell-Checker.py b/Spell-Checker/Spell-Checker.py index 936b59d..bc64cb6 100644 --- a/Spell-Checker/Spell-Checker.py +++ b/Spell-Checker/Spell-Checker.py @@ -3,8 +3,14 @@ def check_spelling(): input_text = input_entry.get() - corrected_text = TextBlob(input_text).correct() - result_label.config(text="Corrected text: " + str(corrected_text)) + if not input_text: + result_label.config(text="TextBox cannot be empty!", fg="red") + return + corrected_text = str(TextBlob(input_text).correct()) + if input_text!=corrected_text: + result_label.config(text="Corrected text: " + corrected_text, fg="black") + return + result_label.config(text=f"{input_text} is Correct!", fg="green") def reset(): input_entry.delete(0, tk.END) From b2a9176984acbc9463764d1eab9c0bf8e6386be4 Mon Sep 17 00:00:00 2001 From: Princee Date: Fri, 10 Apr 2026 12:42:15 +0545 Subject: [PATCH 099/121] Relocate project to dedicated folder and restore default files --- Spell-Checker/Spell-Checker.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Spell-Checker/Spell-Checker.py b/Spell-Checker/Spell-Checker.py index bc64cb6..936b59d 100644 --- a/Spell-Checker/Spell-Checker.py +++ b/Spell-Checker/Spell-Checker.py @@ -3,14 +3,8 @@ def check_spelling(): input_text = input_entry.get() - if not input_text: - result_label.config(text="TextBox cannot be empty!", fg="red") - return - corrected_text = str(TextBlob(input_text).correct()) - if input_text!=corrected_text: - result_label.config(text="Corrected text: " + corrected_text, fg="black") - return - result_label.config(text=f"{input_text} is Correct!", fg="green") + corrected_text = TextBlob(input_text).correct() + result_label.config(text="Corrected text: " + str(corrected_text)) def reset(): input_entry.delete(0, tk.END) From 5e1c7748e33cc39d25c11721583d0a7c6356b6fe Mon Sep 17 00:00:00 2001 From: Princee Date: Fri, 10 Apr 2026 13:03:15 +0545 Subject: [PATCH 100/121] feat: implement core spell-checking logic using TextBlob --- Spell-Sense/logic.py | 12 ++++++++++++ Spell-Sense/requirements.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 Spell-Sense/logic.py create mode 100644 Spell-Sense/requirements.txt diff --git a/Spell-Sense/logic.py b/Spell-Sense/logic.py new file mode 100644 index 0000000..38bd71c --- /dev/null +++ b/Spell-Sense/logic.py @@ -0,0 +1,12 @@ +from textblob import TextBlob + +def get_correction(text): + if not text.strip(): + return None, False + + blob = TextBlob(text) + corrected = str(blob.correct()) + + # Check if the original matches the corrected version + is_correct = text.lower().strip() == corrected.lower().strip() + return corrected, is_correct \ No newline at end of file diff --git a/Spell-Sense/requirements.txt b/Spell-Sense/requirements.txt new file mode 100644 index 0000000..3f42dc3 --- /dev/null +++ b/Spell-Sense/requirements.txt @@ -0,0 +1 @@ +textblob \ No newline at end of file From 39f0fa6b566257c98d9361c6561dc5844133d55c Mon Sep 17 00:00:00 2001 From: Princee Date: Fri, 10 Apr 2026 13:04:01 +0545 Subject: [PATCH 101/121] feat: add Tkinter UI with keyboard bindings and color feedback --- Spell-Sense/main.py | 55 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 Spell-Sense/main.py diff --git a/Spell-Sense/main.py b/Spell-Sense/main.py new file mode 100644 index 0000000..0ab991f --- /dev/null +++ b/Spell-Sense/main.py @@ -0,0 +1,55 @@ +import tkinter as tk +from logic import get_correction + +class SpellCheckerApp: + def __init__(self, root): + self.root = root + self.root.title("Spell Checker Pro") + self.root.geometry("400x300") + self.root.configure(bg='#f7f7f7') + + self.setup_ui() + self.root.bind('', lambda e: self.process_text()) + + def setup_ui(self): + # Label + tk.Label(self.root, text="Pro Spell Checker", font=("Arial", 14, "bold"), bg='#f7f7f7').pack(pady=10) + + # Entry + self.input_entry = tk.Entry(self.root, font=("Arial", 12), width=30) + self.input_entry.pack(pady=10) + self.input_entry.focus_set() + + # Buttons + btn_frame = tk.Frame(self.root, bg='#f7f7f7') + btn_frame.pack(pady=10) + + self.check_btn = tk.Button(btn_frame, text="Check", command=self.process_text, bg='#4CAF50', fg='white', width=10) + self.check_btn.pack(side=tk.LEFT, padx=5) + + self.reset_btn = tk.Button(btn_frame, text="Reset", command=self.reset_fields, bg='#f44336', fg='white', width=10) + self.reset_btn.pack(side=tk.LEFT, padx=5) + + # Result display + self.result_label = tk.Label(self.root, text="", font=("Arial", 11), bg='#f7f7f7', wraplength=350) + self.result_label.pack(pady=20) + + def process_text(self): + text = self.input_entry.get() + corrected, is_correct = get_correction(text) + + if corrected is None: + self.result_label.config(text="Please enter a word!", fg="orange") + elif is_correct: + self.result_label.config(text=f"βœ” '{text}' is spelled correctly!", fg="green") + else: + self.result_label.config(text=f"βœ– Suggestion: {corrected}", fg="blue") + + def reset_fields(self): + self.input_entry.delete(0, tk.END) + self.result_label.config(text="") + +if __name__ == "__main__": + root = tk.Tk() + app = SpellCheckerApp(root) + root.mainloop() \ No newline at end of file From d1fba5bdbb47f21a6be629dfe3e355263a1ab26e Mon Sep 17 00:00:00 2001 From: Princee Date: Fri, 10 Apr 2026 13:22:35 +0545 Subject: [PATCH 102/121] docs: add comprehensive documentation for Spell-Sense --- Spell-Sense/README.md | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 Spell-Sense/README.md diff --git a/Spell-Sense/README.md b/Spell-Sense/README.md new file mode 100644 index 0000000..2716c58 --- /dev/null +++ b/Spell-Sense/README.md @@ -0,0 +1,49 @@ +# Spell-Sense: Advanced Spell Checker + +Spell-Sense is a professional, modular Python application that provides real-time spelling suggestions. Built with `Tkinter` and `TextBlob`, it improves upon basic implementations by adding robust error handling and a clean, user-friendly interface. + +## Features +- Smart Validation: Detects empty inputs and prevents unnecessary processing. +- Color-Coded Feedback: Uses visual cues (Green for success, Blue for suggestions, Red for errors). +- Modular Design: Separation of concerns between UI logic and processing logic. +- Keyboard Friendly: Press `Enter` to check spelling instantly without clicking. +- Auto-Focus: The cursor starts in the input box for immediate typing. + +## Project Structure +/Spell-Sense/ +β”œβ”€β”€ main.py # Entry point and UI (Tkinter) +β”œβ”€β”€ logic.py # Core NLP logic (TextBlob) +β”œβ”€β”€ requirements.txt # Dependency list +└── README.md # Documentation + +πŸš€ Installation & Setup +Prerequisites +Python 3.6 or higher + +Setup +Navigate to your project directory: + +Bash +cd Spell-Sense +Install the required dependencies: + +Bash +pip install -r requirements.txt +πŸ› οΈ Usage +Run the script from your terminal: + +Bash +python main.py +Type a word into the input box. + +Press Enter or click Check. + +View the suggestion or success message below. + +Click Reset to clear the fields. + +πŸ“ Credits +This project was developed as an enhanced alternative to basic scripts in this repository. It focuses on modularity, error handling, and improved User Experience (UX). + +License +This project is open-source and intended for educational use. \ No newline at end of file From b6ee9094a8a029ae601b54d32a1aedcc0750e1b3 Mon Sep 17 00:00:00 2001 From: cmodevcodes Date: Sat, 11 Apr 2026 14:25:04 -0400 Subject: [PATCH 103/121] Add new project --- Biosimilars_Finder/README.md | 48 +++++++ Biosimilars_Finder/biosimilars.py | 190 +++++++++++++++++++++++++ Biosimilars_Finder/requirements.txt | 5 + Biosimilars_Finder/test_biosimilars.py | 103 ++++++++++++++ 4 files changed, 346 insertions(+) create mode 100644 Biosimilars_Finder/README.md create mode 100644 Biosimilars_Finder/biosimilars.py create mode 100644 Biosimilars_Finder/requirements.txt create mode 100644 Biosimilars_Finder/test_biosimilars.py diff --git a/Biosimilars_Finder/README.md b/Biosimilars_Finder/README.md new file mode 100644 index 0000000..80ea0ca --- /dev/null +++ b/Biosimilars_Finder/README.md @@ -0,0 +1,48 @@ +# Biosimilars Finder +#### Description: + +Query FDA open API database as well as the most recent csv file posted on FDA's +Purple book website for available biologics and biosimilars information + +Class Drug is used to store particular information of a drug and available biosimilars info" + +get_brand() queries the FDA API for the openfda data associated with a drug and store info into the drug instance + +As the FDA API doesn't take RE query or provide detailed functionalities for drug brand name check, rf"^{name}(?![\w-])" is used to do a second round check if a drug is a valid drug given the FDA database. This helps to filter out cases like "name-xxxx" or "XX name" or "xxxnamexxx" + + Parameters: + drug (Class Drug): Drug class instance that is used to store FDA query results + name (str): User input drug name to be checked with FDA database + + Raises: + KeyError: If a drug's brand name exist FDA's database but info such as generic/molecule name, route, or moa is not available (e.g. Humira) + ValueError : if the FDA API cannot be accessed + +get_biologics () finds the most recent PurpleBook CSV file and identify if a drug is biologics and if it has biosimilar. Retrieve biosimilars info and store in the Drug class instance if available" + +It automatically checks 24 months starting from the current month to find the most recent Purple Book csv. The function should be called only after get_brand () is called. + +It determines if a drug is biologics based on PurpleBook's cvs field - Proprietary Name. + +It determines if a drug is biologics based on PurpleBook's cvs field - Ref. Product Proprietary Name + +Class Drug property self._biosimilars stores all fields of the FDA PurpleBook csv file of a drug's biosimilars as Pandas DataFrame, not just the ones that prn_biosim() prints + + Parameters: + drug (Class Drug): Drug class instance that is used to store FDA query results + name (str): User input drug name to be checked with the PurpleBook csv fil + + +prn_biosim() print a Drug instance's selected biosimilars information. Class Drug property self._biosimilars stores all fields of the FDA PurpleBook csv file of a drug's biosimilars as Pandas DataFrame, not just the ones that prn_biosim() prints so more info can be easily added to the output table if needed + + Parameters: + drug (Class Drug): Drug class instance that is used to store FDA query results + + ValueError : if the drug is not biologics or if it doesn't have biosimilars info + + + +TODO +1. The three key functions - get_brand(), get_biologics(), prn_biosim() can be easily changed to instance method as they were designed as method for the class Drug +2. One can sometime access a PurpleBook CSV when it is not even officially posted on FDA's PurpleBook website. For example, in March, 2026. The most updated file should be February, 2026 based on PurpleBook's website but March, 2026 csv can already be accessed. This may/may not be the intended behavior of the program +3. The reason that the program has to automatically download the PurpleBook csv, hold it in memory for query etc is because FDA doesn't currently have an API for PurpleBook. It would be great to change the PurpleBook query to an APL query once an API is available diff --git a/Biosimilars_Finder/biosimilars.py b/Biosimilars_Finder/biosimilars.py new file mode 100644 index 0000000..7a78f1f --- /dev/null +++ b/Biosimilars_Finder/biosimilars.py @@ -0,0 +1,190 @@ +import requests +import pandas as pnds +import re +import time +from tabulate import tabulate +from datetime import datetime +from dateutil.relativedelta import relativedelta +from io import StringIO + + +def main () : + + """ + Query FDA open API database as well as the most recent csv data file on FDA's + Purple book website for available biologics and biosimilars information + + """ + + name= input("Brand Name: ").strip() + drug=Drug(name) + get_brand(drug,name) + if drug.is_drug : + get_biologics(drug,name) + print (drug) + + if drug.is_biologics and drug.has_biosimilar: + biosim= input ("Do you want more biosimilars info?[Y/N] ").lower().strip() + if biosim == "y" or biosim == "yes" : + prn_biosim(drug) + + +class Drug: + + """ + Class Drug is used to store particular information of a drug and available biosimilars info" + """ + + def __init__(self, brand_name=[]): + self.brand_name=brand_name + self.is_drug = False + self._generic_name="" + self._route="" + self._moa="" + self.is_biologics= False + self.has_biosimilar = False + self._biosimilars = [] + + def __str__(self): + if not self.is_drug : + s= f"{self.brand_name} is not a brand drug based on the FDA database" + else : + s= f"\nBrand Name: {self.brand_name}\n" + s += f"Molecule Name: {self._generic_name}\n" + s += f"Route: {self._route}\n" + s += f"Mechnism of Action: {self._moa}\n" + s += "Biologics: Yes\n" if self.is_biologics else "Biologics: N/A\n" + s += "Biosimilars: Yes" if self.has_biosimilar else "Biosimilars: N/A" + return s + + +def get_brand (drug,name): + + """ + Query the FDA API for the openfda data associated with a drug and store info into the drug instance + + Parameters: + drug (Class Drug): Drug class instance that is used to store FDA query results + name (str): User input drug name to be checked with FDA database + + Raises: + KeyError: If a drug's brand name exist FDA's database but info such as generic/molecule + name, route, or moa is not available (e.g. Humira) + ValueError : if the FDA API cannot be accessed + """ + + url= f'https://api.fda.gov/drug/label.json?search=openfda.brand_name:"{name}"&limit=1' + + try : + r = requests.get(url, timeout=10) + except requests.exceptions.RequestException as e: + print("FDA API not avaialble") + + if r.status_code ==200 : + response = r.json() + results = response.get("results", []) + openfda = results[0].get("openfda",{}) + + fda_name = openfda["brand_name"][0].strip().capitalize () + pattern=rf"^{name}(?![\w-])" + + if match :=re.search(pattern,fda_name,re.I) : + drug.brand_name = fda_name.capitalize() + drug.is_drug=True + + try : + drug._generic_name=openfda["generic_name"][0].capitalize() + except KeyError : + drug._generic_name ="N/A" + try : + drug._route=openfda["route"][0].capitalize() + except KeyError : + drug._route ="N/A" + try : + drug._moa=openfda["pharm_class_moa"][0].capitalize()[:-6] # removing " [moa]" at the end of the return string + except KeyError : + drug._moa ="N/A" + else : + drug.is_drug=False + else : + drug.is_drug=False + + +def get_biologics (drug,name): + + """ + Finds the most recent PurpleBook CSV file and identify if a drug is biologics + and if it has biosimilar. Retrieve biosimilars info and store in the Drug class + instance if available" + + Automatically check 24 months starting from the current month to find the most + recent Purple Book csv + + Parameters: + drug (Class Drug): Drug class instance that is used to store FDA query results + name (str): User input drug name to be checked with the PurpleBook csv file + """ + + now = datetime.now() + # number of months back from current to search purplebook data + num_months= 24 + base_url = "https://www.accessdata.fda.gov/drugsatfda_docs/PurpleBook" + headers = {"user-Agent": "Mozilla/5.0"} + + for i in range (num_months) : + past = now - relativedelta(months=i) + month_name = past.strftime("%B").capitalize() + year=str(past.year) + filename = f"purplebook-search-{month_name}-data-download.csv" + url = f"{base_url}/{year}/{filename}" + pb_read_success = False + # + try: + r= requests.get(url, headers=headers) + if r.status_code ==200 : + pb_read_success = True + pb = pnds.read_csv(StringIO(r.text), skiprows=3) + break + else : + time.sleep(2) + except Exception as e: + time.sleep(2) + + if pb_read_success : + biologics_matches = pb[pb["Proprietary Name"].str.contains(name, case=False, na=False)] + drug.is_biologics = not biologics_matches.empty + + if drug.is_biologics : + biosimilars_matches = pb[pb["Ref. Product Proprietary Name"].str.contains(name, case=False, na=False)] + drug.has_biosimilar = not biosimilars_matches.empty + if drug.has_biosimilar : + drug._biosimilars=biosimilars_matches + + +def prn_biosim(drug) : + + """ + Print a Drug instance's selected biosimilars information + + Parameters: + drug (Class Drug): Drug class instance that is used to store FDA query results + + ValueError : if the drug is not biologics or if it doesn't have biosimilars info + + """ + + if drug.has_biosimilar == True and drug.is_biologics == True : + print(tabulate( + drug._biosimilars[['Proprietary Name','Proper Name','Strength','Applicant','Approval Date']], + headers=['Brand Name','Molecule Name','Strength','Applicant','Approval Date'], + tablefmt="grid", + showindex=False, + maxcolwidths=[None,None,None,15,None] + )) + else : + raise ValueError ("The drug does not have biosimlar") + + +if __name__ == "__main__" : + main () + diff --git a/Biosimilars_Finder/requirements.txt b/Biosimilars_Finder/requirements.txt new file mode 100644 index 0000000..67890c1 --- /dev/null +++ b/Biosimilars_Finder/requirements.txt @@ -0,0 +1,5 @@ +requests +pandas +python-dateutil +tabulate +pytest diff --git a/Biosimilars_Finder/test_biosimilars.py b/Biosimilars_Finder/test_biosimilars.py new file mode 100644 index 0000000..28c21a7 --- /dev/null +++ b/Biosimilars_Finder/test_biosimilars.py @@ -0,0 +1,103 @@ +import pytest +import pandas as pnds +from biosimilars import Drug, get_brand, get_biologics, prn_biosim + + +def test_init () : + drug = Drug ("remicade") + assert drug.brand_name == "remicade" + assert drug.is_drug == False + assert drug.is_biologics == False + assert drug.has_biosimilar == False + with pytest.raises(TypeError) : + jar= Drug ("remicade", "rituximab") + + +def test_get_brand () : + drug = Drug () + get_brand (drug, "rituxan") + assert drug.is_drug == True + assert drug._generic_name == "Rituximab and hyaluronidase" + + drug = Drug () + get_brand (drug, "remicade") + assert drug.is_drug == True + + drug = Drug () + get_brand (drug, "HumiRa") + assert drug.is_drug == True + + drug = Drug () + get_brand (drug, "KeyTruda") + assert drug.is_drug == True + + drug = Drug () + get_brand (drug, "r") + assert drug.is_drug == False + + drug = Drug () + get_brand (drug, "re") + assert drug.is_drug == False + + drug = Drug () + get_brand (drug, "Not a Drug") + assert drug.is_drug == False + + drug = Drug () + get_brand (drug, "") + assert drug.is_drug == False + + with pytest.raises(TypeError) : + get_brand (drug) + with pytest.raises(TypeError) : + get_brand (drug,"xxx","XXX") + + +def test_get_biologics () : + drug = Drug () + get_brand (drug, "rituxan") + get_biologics (drug, "rituxan") + assert drug.is_biologics == True + assert drug.has_biosimilar == True + + drug = Drug () + get_brand (drug, "HuMira") + get_biologics (drug, "Humira") + assert drug.is_biologics == True + assert drug.has_biosimilar == True + assert isinstance(drug._biosimilars, pnds.DataFrame) + + drug = Drug () + get_brand (drug,"Remicade") + get_biologics (drug,"reMIcade") + assert drug.is_biologics == True + assert drug.has_biosimilar == True + assert isinstance(drug._biosimilars, pnds.DataFrame) + + drug = Drug () + get_brand (drug,"KeyTruda") + get_biologics (drug, "keytruda") + assert drug.is_biologics == True + assert drug.has_biosimilar == False + assert not isinstance(drug._biosimilars, pnds.DataFrame) + + drug = Drug () + get_brand (drug, "lipitor") + get_biologics (drug, "lipitor") + assert drug.is_biologics == False + assert drug.has_biosimilar == False + assert not isinstance(drug._biosimilars, pnds.DataFrame) + + with pytest.raises(TypeError) : + get_biologics (drug) + with pytest.raises(TypeError) : + get_biologics (drug,"xxx","XXX") + +def test_prn_biosim() : + with pytest.raises(TypeError) : + prn_biosim() + drug = Drug () + get_brand (drug, "lipitor") + get_biologics (drug, "lipitor") + with pytest.raises(ValueError) : + prn_biosim(drug) From 693f5fe4461ec9f0152200651350be2706781336 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 16 Apr 2026 01:39:24 +0000 Subject: [PATCH 104/121] Updated Contributors Details --- .github/data/contributors-log.json | 6 ++++-- index.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 62f9014..e230295 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -329,10 +329,12 @@ }, "Encryption_Project": { "contributor-name": [ - "moonabys" + "moonabys", + "yumicce" ], "pull-request-number": [ - "75" + "75", + "81" ], "demo-path": "Encryption_Project" }, diff --git a/index.md b/index.md index cea0fd5..e5325a3 100644 --- a/index.md +++ b/index.md @@ -42,7 +42,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Number-Plate-Detection | [iamdevdhanush](https://github.com/iamdevdhanush "goto iamdevdhanush profile") | [#58](https://github.com/Grow-with-Open-Source/Python-Projects/pull/58 "visit pr #58") | [/Grow-with-Open-Source/Python-Projects/Number-Plate-Detection/](Number-Plate-Detection "view the result of Number-Plate-Detection") | | Motion-Detection | [iamdevdhanush](https://github.com/iamdevdhanush "goto iamdevdhanush profile") | [#62](https://github.com/Grow-with-Open-Source/Python-Projects/pull/62 "visit pr #62") | [/Grow-with-Open-Source/Python-Projects/Motion-Detection/](Motion-Detection "view the result of Motion-Detection") | | Bowling-Action-Tracking | [musharrafhamraz](https://github.com/musharrafhamraz "goto musharrafhamraz profile") | [#67](https://github.com/Grow-with-Open-Source/Python-Projects/pull/67 "visit pr #67") | [/Grow-with-Open-Source/Python-Projects/Bowling-Action-Tracking/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Bowling-Action-Tracking "view the result of Bowling-Action-Tracking") | -| Encryption_Project | [moonabys](https://github.com/moonabys "goto moonabys profile") | [#75](https://github.com/Grow-with-Open-Source/Python-Projects/pull/75 "visit pr #75") | [/Grow-with-Open-Source/Python-Projects/Encryption_Project/](Encryption_Project "view the result of Encryption_Project") | +| Encryption_Project | [moonabys](https://github.com/moonabys "goto moonabys profile"), [yumicce](https://github.com/yumicce "goto yumicce profile") | [#75](https://github.com/Grow-with-Open-Source/Python-Projects/pull/75 "visit pr #75"), [#81](https://github.com/Grow-with-Open-Source/Python-Projects/pull/81 "visit pr #81") | [/Grow-with-Open-Source/Python-Projects/Encryption_Project/](Encryption_Project "view the result of Encryption_Project") | | securepass | [Lampard7crypt](https://github.com/Lampard7crypt "goto Lampard7crypt profile") | [#72](https://github.com/Grow-with-Open-Source/Python-Projects/pull/72 "visit pr #72") | [/Grow-with-Open-Source/Python-Projects/securepass/](securepass "view the result of securepass") | From 11d5d14facc49cbd47d3463f8bcef1ca79a5d816 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 16 Apr 2026 01:42:48 +0000 Subject: [PATCH 105/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index e230295..81ef3e3 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -346,5 +346,14 @@ "72" ], "demo-path": "securepass" + }, + "Biosimilars_Finder": { + "contributor-name": [ + "cmodevcodes" + ], + "pull-request-number": [ + "83" + ], + "demo-path": "Biosimilars_Finder" } } \ No newline at end of file diff --git a/index.md b/index.md index e5325a3..29034bf 100644 --- a/index.md +++ b/index.md @@ -44,6 +44,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Bowling-Action-Tracking | [musharrafhamraz](https://github.com/musharrafhamraz "goto musharrafhamraz profile") | [#67](https://github.com/Grow-with-Open-Source/Python-Projects/pull/67 "visit pr #67") | [/Grow-with-Open-Source/Python-Projects/Bowling-Action-Tracking/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Bowling-Action-Tracking "view the result of Bowling-Action-Tracking") | | Encryption_Project | [moonabys](https://github.com/moonabys "goto moonabys profile"), [yumicce](https://github.com/yumicce "goto yumicce profile") | [#75](https://github.com/Grow-with-Open-Source/Python-Projects/pull/75 "visit pr #75"), [#81](https://github.com/Grow-with-Open-Source/Python-Projects/pull/81 "visit pr #81") | [/Grow-with-Open-Source/Python-Projects/Encryption_Project/](Encryption_Project "view the result of Encryption_Project") | | securepass | [Lampard7crypt](https://github.com/Lampard7crypt "goto Lampard7crypt profile") | [#72](https://github.com/Grow-with-Open-Source/Python-Projects/pull/72 "visit pr #72") | [/Grow-with-Open-Source/Python-Projects/securepass/](securepass "view the result of securepass") | +| Biosimilars_Finder | [cmodevcodes](https://github.com/cmodevcodes "goto cmodevcodes profile") | [#83](https://github.com/Grow-with-Open-Source/Python-Projects/pull/83 "visit pr #83") | [/Grow-with-Open-Source/Python-Projects/Biosimilars_Finder/](Biosimilars_Finder "view the result of Biosimilars_Finder") | From 4d9084069087f77fecf2a67fc11a9002daace246 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 16 Apr 2026 01:44:06 +0000 Subject: [PATCH 106/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 81ef3e3..43ae688 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -355,5 +355,14 @@ "83" ], "demo-path": "Biosimilars_Finder" + }, + "Spell-Sense": { + "contributor-name": [ + "princechaudhary007" + ], + "pull-request-number": [ + "82" + ], + "demo-path": "Spell-Sense" } } \ No newline at end of file diff --git a/index.md b/index.md index 29034bf..d5226db 100644 --- a/index.md +++ b/index.md @@ -45,6 +45,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Encryption_Project | [moonabys](https://github.com/moonabys "goto moonabys profile"), [yumicce](https://github.com/yumicce "goto yumicce profile") | [#75](https://github.com/Grow-with-Open-Source/Python-Projects/pull/75 "visit pr #75"), [#81](https://github.com/Grow-with-Open-Source/Python-Projects/pull/81 "visit pr #81") | [/Grow-with-Open-Source/Python-Projects/Encryption_Project/](Encryption_Project "view the result of Encryption_Project") | | securepass | [Lampard7crypt](https://github.com/Lampard7crypt "goto Lampard7crypt profile") | [#72](https://github.com/Grow-with-Open-Source/Python-Projects/pull/72 "visit pr #72") | [/Grow-with-Open-Source/Python-Projects/securepass/](securepass "view the result of securepass") | | Biosimilars_Finder | [cmodevcodes](https://github.com/cmodevcodes "goto cmodevcodes profile") | [#83](https://github.com/Grow-with-Open-Source/Python-Projects/pull/83 "visit pr #83") | [/Grow-with-Open-Source/Python-Projects/Biosimilars_Finder/](Biosimilars_Finder "view the result of Biosimilars_Finder") | +| Spell-Sense | [princechaudhary007](https://github.com/princechaudhary007 "goto princechaudhary007 profile") | [#82](https://github.com/Grow-with-Open-Source/Python-Projects/pull/82 "visit pr #82") | [/Grow-with-Open-Source/Python-Projects/Spell-Sense/](Spell-Sense "view the result of Spell-Sense") | From 2de6829c102454b83c06296b5dc675c2c9511a11 Mon Sep 17 00:00:00 2001 From: AdyaTech Date: Thu, 7 May 2026 16:35:48 +0530 Subject: [PATCH 107/121] Contributing my Python project by the name of Story Generator in this repository. --- Story-Generator/story.py | 51 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 Story-Generator/story.py diff --git a/Story-Generator/story.py b/Story-Generator/story.py new file mode 100644 index 0000000..627ff7c --- /dev/null +++ b/Story-Generator/story.py @@ -0,0 +1,51 @@ +import random +when = [ + "A few years ago", + "Yesterday", + "Last night", + "A long time ago", + "On 20th January" +] +who = [ + "a rabbit", + "an elephant", + "a mouse", + "a turtle", + "a cat" +] +names = [ + "Ali", + "Miriam", + "Daniel", + "Houuk", + "Starwalker" +] +places = [ + "Barcelona", + "India", + "Germany", + "Venice", + "England" +] +went_to = [ + "cinema", + "university", + "seminar", + "school", + "laundry" +] +happened = [ + "made a lot of friends", + "ate a burger", + "found a secret key", + "solved a mystery", + "wrote a book" +] +story = ( + f"{random.choice(when)}, " + f"{random.choice(names)} the {random.choice(who)} " + f"from {random.choice(places)} went to the " + f"{random.choice(went_to)} and " + f"{random.choice(happened)}." +) +print(story) \ No newline at end of file From 54956ca9f60d5ec361a90a0af70170ca20204b0f Mon Sep 17 00:00:00 2001 From: AdyaTech Date: Thu, 7 May 2026 16:25:01 +0530 Subject: [PATCH 108/121] Contributing my Python project by the name of Dog Age Calculator in this repository. --- Dog-Age-Calculator/dogage.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Dog-Age-Calculator/dogage.py diff --git a/Dog-Age-Calculator/dogage.py b/Dog-Age-Calculator/dogage.py new file mode 100644 index 0000000..19026bf --- /dev/null +++ b/Dog-Age-Calculator/dogage.py @@ -0,0 +1,15 @@ +def calculate_dog_age(human_age): + if human_age < 0: + return None + if human_age <= 2: + return human_age * 10.5 + return 21 + (human_age - 2) * 4 + + +human_age = int(input("Enter a dog's age in human years: ")) +dog_age = calculate_dog_age(human_age) + +if dog_age is None: + print("Age must be a positive number.") +else: + print(f"The dog's age in dog years is {dog_age}.") \ No newline at end of file From b85de52efb5069386755a7356796e28a43c3880f Mon Sep 17 00:00:00 2001 From: AdyaTech Date: Wed, 6 May 2026 14:35:15 +0530 Subject: [PATCH 109/121] Contributing my Python project by the name of Birthday Paradox in this repository. --- Birthday-Paradox/birthdayparadox.py | 102 ++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 Birthday-Paradox/birthdayparadox.py diff --git a/Birthday-Paradox/birthdayparadox.py b/Birthday-Paradox/birthdayparadox.py new file mode 100644 index 0000000..2ecf9e2 --- /dev/null +++ b/Birthday-Paradox/birthdayparadox.py @@ -0,0 +1,102 @@ +import datetime, random + + +def getBirthdays(numberOfBirthdays): + """Returns a list of number random date objects for birthdays.""" + birthdays = [] + for i in range(numberOfBirthdays): + # The year is unimportant for our simulation, as long as all + # birthdays have the same year. + startOfYear = datetime.date(2001, 1, 1) + + # Get a random day into the year: + randomNumberOfDays = datetime.timedelta(random.randint(0, 364)) + birthday = startOfYear + randomNumberOfDays + birthdays.append(birthday) + return birthdays + + +def getMatch(birthdays): + """Returns the date object of a birthday that occurs more than once + in the birthdays list.""" + if len(birthdays) == len(set(birthdays)): + return None # All birthdays are unique, so return None. + + # Compare each birthday to every other birthday: + for a, birthdayA in enumerate(birthdays): + for b, birthdayB in enumerate(birthdays[a + 1 :]): + if birthdayA == birthdayB: + return birthdayA # Return the matching birthday. + + +# Display the intro: +print('''Birthday Paradox, by Al Sweigart al@inventwithpython.com + +The birthday paradox shows us that in a group of N people, the odds +that two of them have matching birthdays is surprisingly large. +This program does a Monte Carlo simulation (that is, repeated random +simulations) to explore this concept. + +(It's not actually a paradox, it's just a surprising result.) +''') + +# Set up a tuple of month names in order: +MONTHS = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec') + +while True: # Keep asking until the user enters a valid amount. + print('How many birthdays shall I generate? (Max 100)') + response = input('> ') + if response.isdecimal() and (0 < int(response) <= 100): + numBDays = int(response) + break # User has entered a valid amount. +print() + +# Generate and display the birthdays: +print('Here are', numBDays, 'birthdays:') +birthdays = getBirthdays(numBDays) +for i, birthday in enumerate(birthdays): + if i != 0: + # Display a comma for each birthday after the first birthday. + print(', ', end='') + monthName = MONTHS[birthday.month - 1] + dateText = '{} {}'.format(monthName, birthday.day) + print(dateText, end='') +print() +print() + +# Determine if there are two birthdays that match. +match = getMatch(birthdays) + +# Display the results: +print('In this simulation, ', end='') +if match != None: + monthName = MONTHS[match.month - 1] + dateText = '{} {}'.format(monthName, match.day) + print('multiple people have a birthday on', dateText) +else: + print('there are no matching birthdays.') +print() + +# Run through 100,000 simulations: +print('Generating', numBDays, 'random birthdays 100,000 times...') +input('Press Enter to begin...') + +print('Let\'s run another 100,000 simulations.') +simMatch = 0 # How many simulations had matching birthdays in them. +for i in range(100000): + # Report on the progress every 10,000 simulations: + if i % 10000 == 0: + print(i, 'simulations run...') + birthdays = getBirthdays(numBDays) + if getMatch(birthdays) != None: + simMatch = simMatch + 1 +print('100,000 simulations run.') + +# Display simulation results: +probability = round(simMatch / 100000 * 100, 2) +print('Out of 100,000 simulations of', numBDays, 'people, there was a') +print('matching birthday in that group', simMatch, 'times. This means') +print('that', numBDays, 'people have a', probability, '% chance of') +print('having a matching birthday in their group.') +print('That\'s probably more than you would think!') From 5bebfb94caabd01e591f1dcd98afdb24a95680c3 Mon Sep 17 00:00:00 2001 From: AdyaTech Date: Wed, 6 May 2026 14:54:41 +0530 Subject: [PATCH 110/121] Contributing my Python project by the name of Bitmap Message in this repository. --- BitMap-Message/bitmapmessage.py | 47 +++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 BitMap-Message/bitmapmessage.py diff --git a/BitMap-Message/bitmapmessage.py b/BitMap-Message/bitmapmessage.py new file mode 100644 index 0000000..59961fa --- /dev/null +++ b/BitMap-Message/bitmapmessage.py @@ -0,0 +1,47 @@ +import sys + +# (!) Try changing this multiline string to any image you like: + +# There are 68 periods along the top and bottom of this string: +# (You can also copy and paste this string from +# https://inventwithpython.com/bitmapworld.txt) +bitmap = """ +.................................................................... + ************** * *** ** * ****************************** + ********************* ** ** * * ****************************** * + ** ***************** ****************************** + ************* ** * **** ** ************** * + ********* ******* **************** * * + ******** *************************** * + * * **** *** *************** ****** ** * + **** * *************** *** *** * + ****** ************* ** ** * + ******** ************* * ** *** + ******** ******** * *** **** + ********* ****** * **** ** * ** + ********* ****** * * *** * * + ****** ***** ** ***** * + ***** **** * ******** + ***** **** ********* + **** ** ******* * + *** * * + ** * * +....................................................................""" + +print('Bitmap Message') +print('Enter the message to display with the bitmap.') +message = input('> ') +if message == '': + sys.exit() + +# Loop over each line in the bitmap: +for line in bitmap.splitlines(): + # Loop over each character in the line: + for i, bit in enumerate(line): + if bit == ' ': + # Print an empty space since there's a space in the bitmap: + print(' ', end='') + else: + # Print a character from the message: + print(message[i % len(message)], end='') + print() # Print a newline. From cdafca71524f2d55ac562ad04b799c0b7faeef0a Mon Sep 17 00:00:00 2001 From: AdyaTech Date: Thu, 7 May 2026 16:19:47 +0530 Subject: [PATCH 111/121] Contributing my Python project by the name of BouncingDVD in this repository. --- Bouncing-DVD/bouncingdvd.py | 140 ++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 Bouncing-DVD/bouncingdvd.py diff --git a/Bouncing-DVD/bouncingdvd.py b/Bouncing-DVD/bouncingdvd.py new file mode 100644 index 0000000..4e82ce8 --- /dev/null +++ b/Bouncing-DVD/bouncingdvd.py @@ -0,0 +1,140 @@ +import sys, random, time + +try: + import bext +except ImportError: + print('This program requires the bext module, which you') + print('can install by following the instructions at') + print('https://pypi.org/project/Bext/') + sys.exit() + +# Set up the constants: +WIDTH, HEIGHT = bext.size() +# We can't print to the last column on Windows without it adding a +# newline automatically, so reduce the width by one: +WIDTH -= 1 + +NUMBER_OF_LOGOS = 5 # (!) Try changing this to 1 or 100. +PAUSE_AMOUNT = 0.2 # (!) Try changing this to 1.0 or 0.0. +# (!) Try changing this list to fewer colors: +COLORS = ['red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'] + +UP_RIGHT = 'ur' +UP_LEFT = 'ul' +DOWN_RIGHT = 'dr' +DOWN_LEFT = 'dl' +DIRECTIONS = (UP_RIGHT, UP_LEFT, DOWN_RIGHT, DOWN_LEFT) + +# Key names for logo dictionaries: +COLOR = 'color' +X = 'x' +Y = 'y' +DIR = 'direction' + + +def main(): + bext.clear() + + # Generate some logos. + logos = [] + for i in range(NUMBER_OF_LOGOS): + logos.append({COLOR: random.choice(COLORS), + X: random.randint(1, WIDTH - 4), + Y: random.randint(1, HEIGHT - 4), + DIR: random.choice(DIRECTIONS)}) + if logos[-1][X] % 2 == 1: + # Make sure X is even so it can hit the corner. + logos[-1][X] -= 1 + + cornerBounces = 0 # Count how many times a logo hits a corner. + while True: # Main program loop. + for logo in logos: # Handle each logo in the logos list. + # Erase the logo's current location: + bext.goto(logo[X], logo[Y]) + print(' ', end='') # (!) Try commenting this line out. + + originalDirection = logo[DIR] + + # See if the logo bounces off the corners: + if logo[X] == 0 and logo[Y] == 0: + logo[DIR] = DOWN_RIGHT + cornerBounces += 1 + elif logo[X] == 0 and logo[Y] == HEIGHT - 1: + logo[DIR] = UP_RIGHT + cornerBounces += 1 + elif logo[X] == WIDTH - 3 and logo[Y] == 0: + logo[DIR] = DOWN_LEFT + cornerBounces += 1 + elif logo[X] == WIDTH - 3 and logo[Y] == HEIGHT - 1: + logo[DIR] = UP_LEFT + cornerBounces += 1 + + # See if the logo bounces off the left edge: + elif logo[X] == 0 and logo[DIR] == UP_LEFT: + logo[DIR] = UP_RIGHT + elif logo[X] == 0 and logo[DIR] == DOWN_LEFT: + logo[DIR] = DOWN_RIGHT + + # See if the logo bounces off the right edge: + # (WIDTH - 3 because 'DVD' has 3 letters.) + elif logo[X] == WIDTH - 3 and logo[DIR] == UP_RIGHT: + logo[DIR] = UP_LEFT + elif logo[X] == WIDTH - 3 and logo[DIR] == DOWN_RIGHT: + logo[DIR] = DOWN_LEFT + + # See if the logo bounces off the top edge: + elif logo[Y] == 0 and logo[DIR] == UP_LEFT: + logo[DIR] = DOWN_LEFT + elif logo[Y] == 0 and logo[DIR] == UP_RIGHT: + logo[DIR] = DOWN_RIGHT + + # See if the logo bounces off the bottom edge: + elif logo[Y] == HEIGHT - 1 and logo[DIR] == DOWN_LEFT: + logo[DIR] = UP_LEFT + elif logo[Y] == HEIGHT - 1 and logo[DIR] == DOWN_RIGHT: + logo[DIR] = UP_RIGHT + + if logo[DIR] != originalDirection: + # Change color when the logo bounces: + logo[COLOR] = random.choice(COLORS) + + # Move the logo. (X moves by 2 because the terminal + # characters are twice as tall as they are wide.) + if logo[DIR] == UP_RIGHT: + logo[X] += 2 + logo[Y] -= 1 + elif logo[DIR] == UP_LEFT: + logo[X] -= 2 + logo[Y] -= 1 + elif logo[DIR] == DOWN_RIGHT: + logo[X] += 2 + logo[Y] += 1 + elif logo[DIR] == DOWN_LEFT: + logo[X] -= 2 + logo[Y] += 1 + + # Display number of corner bounces: + bext.goto(5, 0) + bext.fg('white') + print('Corner bounces:', cornerBounces, end='') + + for logo in logos: + # Draw the logos at their new location: + bext.goto(logo[X], logo[Y]) + bext.fg(logo[COLOR]) + print('DVD', end='') + + bext.goto(0, 0) + + sys.stdout.flush() # (Required for bext-using programs.) + time.sleep(PAUSE_AMOUNT) + + +# If this program was run (instead of imported), run the game: +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + print() + print('Bouncing DVD Logo, by Al Sweigart') + sys.exit() # When Ctrl-C is pressed, end the program. From 460b21d3138baa4d299e0b3ec7eafb9244733448 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 22 Jun 2026 12:19:37 +0000 Subject: [PATCH 112/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 43ae688..0c43400 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -364,5 +364,14 @@ "82" ], "demo-path": "Spell-Sense" + }, + "Birthday-Paradox": { + "contributor-name": [ + "AdyaTech" + ], + "pull-request-number": [ + "86" + ], + "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Birthday-Paradox" } } \ No newline at end of file diff --git a/index.md b/index.md index d5226db..80e8c8a 100644 --- a/index.md +++ b/index.md @@ -46,6 +46,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | securepass | [Lampard7crypt](https://github.com/Lampard7crypt "goto Lampard7crypt profile") | [#72](https://github.com/Grow-with-Open-Source/Python-Projects/pull/72 "visit pr #72") | [/Grow-with-Open-Source/Python-Projects/securepass/](securepass "view the result of securepass") | | Biosimilars_Finder | [cmodevcodes](https://github.com/cmodevcodes "goto cmodevcodes profile") | [#83](https://github.com/Grow-with-Open-Source/Python-Projects/pull/83 "visit pr #83") | [/Grow-with-Open-Source/Python-Projects/Biosimilars_Finder/](Biosimilars_Finder "view the result of Biosimilars_Finder") | | Spell-Sense | [princechaudhary007](https://github.com/princechaudhary007 "goto princechaudhary007 profile") | [#82](https://github.com/Grow-with-Open-Source/Python-Projects/pull/82 "visit pr #82") | [/Grow-with-Open-Source/Python-Projects/Spell-Sense/](Spell-Sense "view the result of Spell-Sense") | +| Birthday-Paradox | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#86](https://github.com/Grow-with-Open-Source/Python-Projects/pull/86 "visit pr #86") | [/Grow-with-Open-Source/Python-Projects/Birthday-Paradox/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Birthday-Paradox "view the result of Birthday-Paradox") | From 8eb29df625f25e719025aa9765a534cfe457b8b0 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 22 Jun 2026 12:23:08 +0000 Subject: [PATCH 113/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 0c43400..6f8c913 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -373,5 +373,14 @@ "86" ], "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Birthday-Paradox" + }, + "BitMap-Message": { + "contributor-name": [ + "AdyaTech" + ], + "pull-request-number": [ + "87" + ], + "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/BitMap-Message" } } \ No newline at end of file diff --git a/index.md b/index.md index 80e8c8a..1d1b52f 100644 --- a/index.md +++ b/index.md @@ -47,6 +47,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Biosimilars_Finder | [cmodevcodes](https://github.com/cmodevcodes "goto cmodevcodes profile") | [#83](https://github.com/Grow-with-Open-Source/Python-Projects/pull/83 "visit pr #83") | [/Grow-with-Open-Source/Python-Projects/Biosimilars_Finder/](Biosimilars_Finder "view the result of Biosimilars_Finder") | | Spell-Sense | [princechaudhary007](https://github.com/princechaudhary007 "goto princechaudhary007 profile") | [#82](https://github.com/Grow-with-Open-Source/Python-Projects/pull/82 "visit pr #82") | [/Grow-with-Open-Source/Python-Projects/Spell-Sense/](Spell-Sense "view the result of Spell-Sense") | | Birthday-Paradox | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#86](https://github.com/Grow-with-Open-Source/Python-Projects/pull/86 "visit pr #86") | [/Grow-with-Open-Source/Python-Projects/Birthday-Paradox/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Birthday-Paradox "view the result of Birthday-Paradox") | +| BitMap-Message | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#87](https://github.com/Grow-with-Open-Source/Python-Projects/pull/87 "visit pr #87") | [/Grow-with-Open-Source/Python-Projects/BitMap-Message/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/BitMap-Message "view the result of BitMap-Message") | From 7255ddeef27452b3ad2e96a63b7f1d3a5b313c29 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 22 Jun 2026 12:27:05 +0000 Subject: [PATCH 114/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 6f8c913..d84be49 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -382,5 +382,14 @@ "87" ], "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/BitMap-Message" + }, + "Bouncing-DVD": { + "contributor-name": [ + "AdyaTech" + ], + "pull-request-number": [ + "89" + ], + "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Bouncing-DVD" } } \ No newline at end of file diff --git a/index.md b/index.md index 1d1b52f..d4eacf8 100644 --- a/index.md +++ b/index.md @@ -48,6 +48,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Spell-Sense | [princechaudhary007](https://github.com/princechaudhary007 "goto princechaudhary007 profile") | [#82](https://github.com/Grow-with-Open-Source/Python-Projects/pull/82 "visit pr #82") | [/Grow-with-Open-Source/Python-Projects/Spell-Sense/](Spell-Sense "view the result of Spell-Sense") | | Birthday-Paradox | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#86](https://github.com/Grow-with-Open-Source/Python-Projects/pull/86 "visit pr #86") | [/Grow-with-Open-Source/Python-Projects/Birthday-Paradox/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Birthday-Paradox "view the result of Birthday-Paradox") | | BitMap-Message | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#87](https://github.com/Grow-with-Open-Source/Python-Projects/pull/87 "visit pr #87") | [/Grow-with-Open-Source/Python-Projects/BitMap-Message/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/BitMap-Message "view the result of BitMap-Message") | +| Bouncing-DVD | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#89](https://github.com/Grow-with-Open-Source/Python-Projects/pull/89 "visit pr #89") | [/Grow-with-Open-Source/Python-Projects/Bouncing-DVD/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Bouncing-DVD "view the result of Bouncing-DVD") | From 0ed45db2721b5ad6be68908c454aa212f60efa65 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 22 Jun 2026 12:29:02 +0000 Subject: [PATCH 115/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index d84be49..1499c25 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -391,5 +391,14 @@ "89" ], "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Bouncing-DVD" + }, + "Dog-Age-Calculator": { + "contributor-name": [ + "AdyaTech" + ], + "pull-request-number": [ + "90" + ], + "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Dog-Age-Calculator" } } \ No newline at end of file diff --git a/index.md b/index.md index d4eacf8..247a282 100644 --- a/index.md +++ b/index.md @@ -49,6 +49,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Birthday-Paradox | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#86](https://github.com/Grow-with-Open-Source/Python-Projects/pull/86 "visit pr #86") | [/Grow-with-Open-Source/Python-Projects/Birthday-Paradox/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Birthday-Paradox "view the result of Birthday-Paradox") | | BitMap-Message | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#87](https://github.com/Grow-with-Open-Source/Python-Projects/pull/87 "visit pr #87") | [/Grow-with-Open-Source/Python-Projects/BitMap-Message/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/BitMap-Message "view the result of BitMap-Message") | | Bouncing-DVD | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#89](https://github.com/Grow-with-Open-Source/Python-Projects/pull/89 "visit pr #89") | [/Grow-with-Open-Source/Python-Projects/Bouncing-DVD/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Bouncing-DVD "view the result of Bouncing-DVD") | +| Dog-Age-Calculator | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#90](https://github.com/Grow-with-Open-Source/Python-Projects/pull/90 "visit pr #90") | [/Grow-with-Open-Source/Python-Projects/Dog-Age-Calculator/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Dog-Age-Calculator "view the result of Dog-Age-Calculator") | From 6a5aa140a1fdc8e84a2797156c7ba4cffe721361 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 22 Jun 2026 12:30:24 +0000 Subject: [PATCH 116/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 1499c25..44d1f9c 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -400,5 +400,14 @@ "90" ], "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Dog-Age-Calculator" + }, + "Story-Generator": { + "contributor-name": [ + "AdyaTech" + ], + "pull-request-number": [ + "91" + ], + "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Story-Generator" } } \ No newline at end of file diff --git a/index.md b/index.md index 247a282..b1e4e22 100644 --- a/index.md +++ b/index.md @@ -50,6 +50,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | BitMap-Message | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#87](https://github.com/Grow-with-Open-Source/Python-Projects/pull/87 "visit pr #87") | [/Grow-with-Open-Source/Python-Projects/BitMap-Message/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/BitMap-Message "view the result of BitMap-Message") | | Bouncing-DVD | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#89](https://github.com/Grow-with-Open-Source/Python-Projects/pull/89 "visit pr #89") | [/Grow-with-Open-Source/Python-Projects/Bouncing-DVD/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Bouncing-DVD "view the result of Bouncing-DVD") | | Dog-Age-Calculator | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#90](https://github.com/Grow-with-Open-Source/Python-Projects/pull/90 "visit pr #90") | [/Grow-with-Open-Source/Python-Projects/Dog-Age-Calculator/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Dog-Age-Calculator "view the result of Dog-Age-Calculator") | +| Story-Generator | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#91](https://github.com/Grow-with-Open-Source/Python-Projects/pull/91 "visit pr #91") | [/Grow-with-Open-Source/Python-Projects/Story-Generator/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Story-Generator "view the result of Story-Generator") | From b928fdd2700d6a3ef7a867403a1c57d252cbc707 Mon Sep 17 00:00:00 2001 From: Stella Nyamekye Anyebayaaka Appiok Date: Mon, 22 Jun 2026 23:24:22 +0000 Subject: [PATCH 117/121] Adding code file --- slot-machine/slot_machine.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 slot-machine/slot_machine.py diff --git a/slot-machine/slot_machine.py b/slot-machine/slot_machine.py new file mode 100644 index 0000000..e69de29 From 2509bd7e8921fe5c071ffa67727812468f111e8f Mon Sep 17 00:00:00 2001 From: Stella Nyamekye Anyebayaaka Appiok Date: Mon, 22 Jun 2026 23:25:47 +0000 Subject: [PATCH 118/121] Adding ReadMe file with requirement and Project description --- slot-machine/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 slot-machine/README.md diff --git a/slot-machine/README.md b/slot-machine/README.md new file mode 100644 index 0000000..e69de29 From 13fbc086606fc05435e4a6bf39372934589fdf31 Mon Sep 17 00:00:00 2001 From: Stella Nyamekye Anyebayaaka Appiok Date: Mon, 22 Jun 2026 23:28:07 +0000 Subject: [PATCH 119/121] Finished code --- slot-machine/slot_machine.py | 162 +++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/slot-machine/slot_machine.py b/slot-machine/slot_machine.py index e69de29..69bd93b 100644 --- a/slot-machine/slot_machine.py +++ b/slot-machine/slot_machine.py @@ -0,0 +1,162 @@ +import random + +MAX_LINES = 3 +MAX_BET = 10 +MIN_BET = 1 + +ROWS = 3 +COLS = 3 + +symbol_count = { + "A": 2, + "B": 4, + "C": 6, + "D": 8 +} + +symbol_value = { + "A": 5, + "B": 4, + "C": 3, + "D": 2 +} + + +def deposit(): + while True: + amount = input("What would you like to deposit? $") + + if amount.isdigit(): + amount = int(amount) + + if amount > 0: + return amount + + print("Please enter a valid amount.") + + +def get_number_of_lines(): + while True: + lines = input(f"Enter the number of lines to bet on (1-{MAX_LINES}): ") + + if lines.isdigit(): + lines = int(lines) + + if 1 <= lines <= MAX_LINES: + return lines + + print("Enter a valid number.") + + +def get_bet(): + while True: + amount = input("What would you like to bet on each line? $") + + if amount.isdigit(): + amount = int(amount) + + if MIN_BET <= amount <= MAX_BET: + return amount + + print(f"Amount must be between ${MIN_BET} and ${MAX_BET}.") + + +def get_slot_machine_spin(rows, cols, symbols): + all_symbols = [] + + for symbol, symbol_count in symbols.items(): + for _ in range(symbol_count): + all_symbols.append(symbol) + + columns = [] + + for _ in range(cols): + current_symbols = all_symbols[:] + column = [] + + for _ in range(rows): + value = random.choice(current_symbols) + current_symbols.remove(value) + column.append(value) + + columns.append(column) + + return columns + + +def print_slot_machine(columns): + for row in range(len(columns[0])): + for i, column in enumerate(columns): + if i != len(columns) - 1: + print(column[row], end=" | ") + else: + print(column[row], end="") + + print() + + +def check_winnings(columns, lines, bet, values): + winnings = 0 + winning_lines = [] + + for line in range(lines): + symbol = columns[0][line] + + for column in columns: + symbol_to_check = column[line] + + if symbol != symbol_to_check: + break + else: + winnings += values[symbol] * bet + winning_lines.append(line + 1) + + return winnings, winning_lines + + +def spin(balance): + lines = get_number_of_lines() + + while True: + bet = get_bet() + total_bet = bet * lines + + if total_bet > balance: + print( + f"You do not have enough balance. Current balance: ${balance}") + else: + break + + print(f"\nYou are betting ${bet} on {lines} lines.") + print(f"Total bet: ${total_bet}\n") + + slots = get_slot_machine_spin(ROWS, COLS, symbol_count) + + print_slot_machine(slots) + + winnings, winning_lines = check_winnings( + slots, lines, bet, symbol_value) + + print(f"\nYou won ${winnings}.") + print(f"Winning lines: {winning_lines}") + + return winnings - total_bet + + +def main(): + balance = deposit() + + while True: + print(f"\nCurrent balance: ${balance}") + + answer = input("Press Enter to spin (q to quit): ") + + if answer.lower() == "q": + break + + balance += spin(balance) + + print(f"\nYou left with ${balance}") + + +main() From d303810d3fed482a6125575b289ad55703685166 Mon Sep 17 00:00:00 2001 From: Stella Nyamekye Anyebayaaka Appiok Date: Mon, 22 Jun 2026 23:31:31 +0000 Subject: [PATCH 120/121] Addind project requirement and description in the ReadMe fil --- slot-machine/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/slot-machine/README.md b/slot-machine/README.md index e69de29..5ec4360 100644 --- a/slot-machine/README.md +++ b/slot-machine/README.md @@ -0,0 +1,11 @@ +## What it does + +A fully functional terminal-based slot machine game. The player deposits +money, chooses how many lines to bet on (up to 3), sets a bet amount per +line, and spins. The machine generates a random 3x3 grid of symbols and +pays out based on matching symbols across each line. The game tracks +balance across multiple spins until the player quits. + +## Requirements + +None β€” uses only built-in random module From ef5523476d0de2192704c980be99c0a35d3adea2 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 24 Jun 2026 16:23:00 +0000 Subject: [PATCH 121/121] Updated Contributors Details --- .github/data/contributors-log.json | 9 +++++++++ index.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 44d1f9c..3bba434 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -409,5 +409,14 @@ "91" ], "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Story-Generator" + }, + "slot-machine": { + "contributor-name": [ + "appiokstella" + ], + "pull-request-number": [ + "93" + ], + "demo-path": "slot-machine" } } \ No newline at end of file diff --git a/index.md b/index.md index b1e4e22..4b27d11 100644 --- a/index.md +++ b/index.md @@ -51,6 +51,7 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | Bouncing-DVD | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#89](https://github.com/Grow-with-Open-Source/Python-Projects/pull/89 "visit pr #89") | [/Grow-with-Open-Source/Python-Projects/Bouncing-DVD/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Bouncing-DVD "view the result of Bouncing-DVD") | | Dog-Age-Calculator | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#90](https://github.com/Grow-with-Open-Source/Python-Projects/pull/90 "visit pr #90") | [/Grow-with-Open-Source/Python-Projects/Dog-Age-Calculator/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Dog-Age-Calculator "view the result of Dog-Age-Calculator") | | Story-Generator | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#91](https://github.com/Grow-with-Open-Source/Python-Projects/pull/91 "visit pr #91") | [/Grow-with-Open-Source/Python-Projects/Story-Generator/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Story-Generator "view the result of Story-Generator") | +| slot-machine | [appiokstella](https://github.com/appiokstella "goto appiokstella profile") | [#93](https://github.com/Grow-with-Open-Source/Python-Projects/pull/93 "visit pr #93") | [/Grow-with-Open-Source/Python-Projects/slot-machine/](slot-machine "view the result of slot-machine") |